779 lines
25 KiB
Python
779 lines
25 KiB
Python
from __future__ import unicode_literals
|
|
|
|
from django.db import models
|
|
from django.contrib.auth.models import User
|
|
from django import forms
|
|
from django.forms import ModelForm
|
|
from django.dispatch import receiver
|
|
from django.forms.widgets import SplitDateTimeWidget
|
|
from django.forms.formsets import BaseFormSet
|
|
from datetimewidget.widgets import DateTimeWidget
|
|
from django.core.validators import validate_email
|
|
import os
|
|
import twitter
|
|
import re
|
|
|
|
from django.conf import settings
|
|
from sqlalchemy import create_engine
|
|
import sqlalchemy as sa
|
|
from sqlite3 import OperationalError
|
|
from django.utils import timezone
|
|
import datetime
|
|
|
|
from rowers.rows import validate_file_extension
|
|
|
|
from rowsandall_app.settings import (
|
|
TWEET_ACCESS_TOKEN_KEY,
|
|
TWEET_ACCESS_TOKEN_SECRET,
|
|
TWEET_CONSUMER_KEY,
|
|
TWEET_CONSUMER_SECRET,
|
|
)
|
|
|
|
tweetapi = twitter.Api(consumer_key=TWEET_CONSUMER_KEY,
|
|
consumer_secret=TWEET_CONSUMER_SECRET,
|
|
access_token_key=TWEET_ACCESS_TOKEN_KEY,
|
|
access_token_secret=TWEET_ACCESS_TOKEN_SECRET)
|
|
|
|
user = settings.DATABASES['default']['USER']
|
|
password = settings.DATABASES['default']['PASSWORD']
|
|
database_name = settings.DATABASES['default']['NAME']
|
|
host = settings.DATABASES['default']['HOST']
|
|
port = settings.DATABASES['default']['PORT']
|
|
|
|
database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
|
|
user=user,
|
|
password=password,
|
|
database_name=database_name,
|
|
host=host,
|
|
port=port,
|
|
)
|
|
|
|
if settings.DEBUG or user=='':
|
|
database_url = 'sqlite:///db.sqlite3'
|
|
|
|
|
|
# model for Power Zone names
|
|
class PowerZonesField(models.TextField):
|
|
# __metaclass__ = models.SubfieldBase
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
self.token = kwargs.pop('token',',')
|
|
super(PowerZonesField, self).__init__(*args, **kwargs)
|
|
|
|
def to_python(self, value):
|
|
if not value: return
|
|
if isinstance(value, list):
|
|
return value
|
|
# remove double quotes and brackets
|
|
value = re.sub(r'u\"','',value)
|
|
value = re.sub(r'u\'','',value)
|
|
value = re.sub(r'\\','',value)
|
|
value = re.sub(r'\"','',value)
|
|
value = re.sub(r'\'','',value)
|
|
value = re.sub(r'\[','',value)
|
|
value = re.sub(r'\]','',value)
|
|
value = re.sub(r'\[\[','[',value)
|
|
value = re.sub(r'\]\]',']',value)
|
|
value = re.sub(r'\ \ ',' ',value)
|
|
value = re.sub(r', ',',',value)
|
|
|
|
return value.split(self.token)
|
|
|
|
def from_db_value(self,value, expression, connection, context):
|
|
if value is None:
|
|
return value
|
|
if isinstance(value, list):
|
|
return value
|
|
return value.split(self.token)
|
|
|
|
def get_db_prep_value(self, value, connection, prepared=False):
|
|
if not value: return
|
|
assert(isinstance(value, list) or isinstance(value, tuple))
|
|
return self.token.join([unicode(s) for s in value])
|
|
|
|
def value_to_string(self, obj):
|
|
value = self._get_val_from_obj(obj)
|
|
return self.get_deb_prep_value(value)
|
|
|
|
# For future Team functionality
|
|
class Team(models.Model):
|
|
choices = (
|
|
('private','private'),
|
|
('open','open'),
|
|
)
|
|
name = models.CharField(max_length=150,unique=True)
|
|
notes = models.CharField(blank=True,max_length=200)
|
|
manager = models.ForeignKey(User)
|
|
private = models.CharField(max_length=30,choices=choices,default='open')
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
class TeamInvite(models.Model):
|
|
team = models.ForeignKey(Team)
|
|
user = models.ForeignKey(User,null=True)
|
|
issuedate = models.DateField(default=timezone.now)
|
|
code = models.CharField(max_length=150,unique=True)
|
|
email = models.CharField(max_length=150,null=True,blank=True)
|
|
|
|
# Extension of User with rowing specific data
|
|
class Rower(models.Model):
|
|
weightcategories = (
|
|
('hwt','heavy-weight'),
|
|
('lwt','light-weight'),
|
|
)
|
|
user = models.OneToOneField(User)
|
|
|
|
# Heart Rate Zone data
|
|
max = models.IntegerField(default=192,verbose_name="Max Heart Rate")
|
|
rest = models.IntegerField(default=48,verbose_name="Resting Heart Rate")
|
|
ut2 = models.IntegerField(default=105,verbose_name="UT2 band lower HR")
|
|
ut1 = models.IntegerField(default=146,verbose_name="UT1 band lower HR")
|
|
at = models.IntegerField(default=160,verbose_name="AT band lower HR")
|
|
tr = models.IntegerField(default=167,verbose_name="TR band lower HR")
|
|
an = models.IntegerField(default=180,verbose_name="AN band lower HR")
|
|
|
|
# Weight Category (for sync to C2)
|
|
weightcategory = models.CharField(default="hwt",
|
|
max_length=30,
|
|
choices=weightcategories)
|
|
|
|
# Power Zone Data
|
|
ftp = models.IntegerField(default=226,verbose_name="Functional Threshold Power")
|
|
|
|
pw_ut2 = models.IntegerField(default=124,verbose_name="UT2 Power")
|
|
pw_ut1 = models.IntegerField(default=171,verbose_name="UT1 Power")
|
|
pw_at = models.IntegerField(default=203,verbose_name="AT Power")
|
|
pw_tr = models.IntegerField(default=237,verbose_name="TR Power")
|
|
pw_an = models.IntegerField(default=273,verbose_name="AN Power")
|
|
|
|
powerzones = PowerZonesField(default=['Rest',
|
|
'Pwr UT2',
|
|
'Pwr UT1',
|
|
'Pwr AT',
|
|
'Pwr TR',
|
|
'Pwr AN'])
|
|
|
|
# Access tokens
|
|
c2token = models.CharField(default='',max_length=200,blank=True,null=True)
|
|
tokenexpirydate = models.DateTimeField(blank=True,null=True)
|
|
c2refreshtoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
|
sporttrackstoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
|
sporttrackstokenexpirydate = models.DateTimeField(blank=True,null=True)
|
|
sporttracksrefreshtoken = models.CharField(default='',max_length=200,
|
|
blank=True,null=True)
|
|
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
|
|
|
|
# Plan
|
|
plans = (
|
|
('basic','basic'),
|
|
('pro','pro'),
|
|
('coach','coach')
|
|
)
|
|
|
|
rowerplan = models.CharField(default='basic',max_length=30,
|
|
choices=plans)
|
|
|
|
planexpires = models.DateField(default=timezone.now)
|
|
teamplanexpires = models.DateField(default=timezone.now)
|
|
clubsize = models.IntegerField(default=0)
|
|
|
|
# Friends/Team
|
|
friends = models.ManyToManyField("self",blank=True)
|
|
|
|
team = models.ManyToManyField(Team,blank=True)
|
|
|
|
def __str__(self):
|
|
return self.user.username
|
|
|
|
# Saving a chart as a favorite chart
|
|
class FavoriteChart(models.Model):
|
|
y1params = (
|
|
('hr','Heart Rate'),
|
|
('pace','Pace'),
|
|
('spm','SPM'),
|
|
('driveenergy','Work per Stroke'),
|
|
('power','Power'),
|
|
('drivelength','Drivelength'),
|
|
('averageforce','Average Force'),
|
|
('peakforce','Peak Force'),
|
|
('forceratio','Average/Peak Force Ratio'),
|
|
('drivespeed','Drive Speed'),
|
|
('wash','Wash'),
|
|
('slip','Slip'),
|
|
('catch','Catch Angle'),
|
|
('finish','Finish Angle'),
|
|
('peakforceangle','Peak Force Angle')
|
|
)
|
|
|
|
y2params = (
|
|
('hr','Heart Rate'),
|
|
('spm','SPM'),
|
|
('driveenergy','Work per Stroke'),
|
|
('power','Power'),
|
|
('drivelength','Drivelength'),
|
|
('averageforce','Average Force'),
|
|
('peakforce','Peak Force'),
|
|
('forceratio','Average/Peak Force Ratio'),
|
|
('drivespeed','Drive Speed'),
|
|
('wash','Wash'),
|
|
('slip','Slip'),
|
|
('catch','Catch Angle'),
|
|
('finish','Finish Angle'),
|
|
('peakforceangle','Peak Force Angle'),
|
|
('None','None')
|
|
)
|
|
|
|
xparams = (
|
|
('time','Time'),
|
|
('distance','Distance'),
|
|
('hr','Heart Rate'),
|
|
('spm','SPM'),
|
|
('driveenergy','Work per Stroke'),
|
|
('power','Power'),
|
|
('drivelength','Drivelength'),
|
|
('averageforce','Average Force'),
|
|
('peakforce','Peak Force'),
|
|
('forceratio','Average/Peak Force Ratio'),
|
|
('drivespeed','Drive Speed'),
|
|
('wash','Wash'),
|
|
('slip','Slip'),
|
|
('catch','Catch Angle'),
|
|
('finish','Finish Angle'),
|
|
('peakforceangle','Peak Force Angle'),
|
|
)
|
|
|
|
workouttypechoices = (
|
|
('ote','Erg/SkiErg'),
|
|
('otw','On The Water'),
|
|
('both','both')
|
|
)
|
|
|
|
plottypes = (
|
|
('line','Line Chart'),
|
|
('scatter','Scatter Chart')
|
|
)
|
|
|
|
yparam1 = models.CharField(max_length=50,choices=y1params,verbose_name='Y1')
|
|
yparam2 = models.CharField(max_length=50,choices=y2params,verbose_name='Y2',default='None',blank=True)
|
|
xparam = models.CharField(max_length=50,choices=xparams,verbose_name='X')
|
|
plottype = models.CharField(max_length=50,choices=plottypes,
|
|
default='line',
|
|
verbose_name='Chart Type')
|
|
workouttype = models.CharField(max_length=50,choices=workouttypechoices,
|
|
default='both',
|
|
verbose_name='Workout Type')
|
|
reststrokes = models.BooleanField(default=True,verbose_name="Incl. Rest")
|
|
user = models.ForeignKey(Rower)
|
|
|
|
|
|
class FavoriteForm(ModelForm):
|
|
class Meta:
|
|
model = FavoriteChart
|
|
fields = ['xparam','yparam1','yparam2',
|
|
'plottype','workouttype','reststrokes']
|
|
|
|
# To generate favorite chart forms on the fly
|
|
class BaseFavoriteFormSet(BaseFormSet):
|
|
def clean(self):
|
|
if any(self.errors):
|
|
return
|
|
|
|
for form in self.forms:
|
|
if form.cleaned_data:
|
|
xparam = form.cleaned_data['xparam']
|
|
yparam1 = form.cleaned_data['yparam1']
|
|
yparam2 = form.cleaned_data['yparam2']
|
|
plottype = form.cleaned_data['plottype']
|
|
reststrokes = form.cleaned_data['reststrokes']
|
|
|
|
if not xparam:
|
|
raise forms.ValidationError(
|
|
'Must have x parameter.',
|
|
code='missing_xparam'
|
|
)
|
|
|
|
if not yparam1:
|
|
raise forms.ValidationError(
|
|
'Must have Y1 parameter.',
|
|
code='missing_yparam1'
|
|
)
|
|
|
|
if not yparam2:
|
|
yparam2 = 'None'
|
|
|
|
# Workout
|
|
class Workout(models.Model):
|
|
workouttypes = (
|
|
('water','On-water'),
|
|
('rower','Indoor Rower'),
|
|
('skierg','Ski Erg'),
|
|
('dynamic','Dynamic Indoor Rower'),
|
|
('slides','Indoor Rower on Slides'),
|
|
('paddle','Paddle Adapter'),
|
|
('snow','On-snow'),
|
|
('other','Other'),
|
|
)
|
|
|
|
boattypes = (
|
|
('1x', '1x (single)'),
|
|
('2x', '2x (double)'),
|
|
('2-', '2- (pair)'),
|
|
('4x', '4x (quad)'),
|
|
('4-', '4- (four)'),
|
|
('8+', '8+ (eight)'),
|
|
)
|
|
|
|
user = models.ForeignKey(Rower)
|
|
team = models.ManyToManyField(Team,blank=True)
|
|
name = models.CharField(max_length=150)
|
|
date = models.DateField()
|
|
workouttype = models.CharField(choices=workouttypes,max_length=50)
|
|
boattype = models.CharField(choices=boattypes,max_length=50,
|
|
default='1x (single)',
|
|
verbose_name = 'Boat Type')
|
|
starttime = models.TimeField(blank=True,null=True)
|
|
startdatetime = models.DateTimeField(blank=True,null=True)
|
|
distance = models.IntegerField(default=0,blank=True)
|
|
duration = models.TimeField(default=1,blank=True)
|
|
weightcategory = models.CharField(default="hwt",max_length=10)
|
|
weightvalue = models.FloatField(default=80.0,blank=True,verbose_name = 'Average Crew Weight (kg)')
|
|
csvfilename = models.CharField(blank=True,max_length=150)
|
|
uploadedtoc2 = models.IntegerField(default=0)
|
|
averagehr = models.IntegerField(blank=True,null=True)
|
|
maxhr = models.IntegerField(blank=True,null=True)
|
|
uploadedtostrava = models.IntegerField(default=0)
|
|
uploadedtosporttracks = models.IntegerField(default=0)
|
|
notes = models.CharField(blank=True,null=True,max_length=200)
|
|
summary = models.TextField(blank=True)
|
|
|
|
def __str__(self):
|
|
|
|
date = self.date
|
|
name = self.name
|
|
|
|
str = date.strftime('%Y-%m-%d')+'_'+name
|
|
|
|
return str
|
|
|
|
# delete files belonging to workout instance
|
|
# related GraphImage objects should be deleted automatically
|
|
@receiver(models.signals.post_delete,sender=Workout)
|
|
def auto_delete_file_on_delete(sender, instance, **kwargs):
|
|
# delete CSV file
|
|
if instance.csvfilename:
|
|
if os.path.isfile(instance.csvfilename):
|
|
os.remove(instance.csvfilename)
|
|
if instance.csvfilename+'.gz':
|
|
if os.path.isfile(instance.csvfilename+'.gz'):
|
|
os.remove(instance.csvfilename+'.gz')
|
|
|
|
# Delete stroke data from the database when a workout is deleted
|
|
@receiver(models.signals.post_delete,sender=Workout)
|
|
def auto_delete_strokedata_on_delete(sender, instance, **kwargs):
|
|
if instance.id:
|
|
query = sa.text('DELETE FROM strokedata WHERE workoutid={id};'.format(
|
|
id=instance.id,
|
|
))
|
|
engine = create_engine(database_url, echo=False)
|
|
with engine.connect() as conn, conn.begin():
|
|
try:
|
|
result = conn.execute(query)
|
|
except:
|
|
print "Database Locked"
|
|
conn.close()
|
|
engine.dispose()
|
|
|
|
# Model of StrokeData table
|
|
# the definition here is used only to enable easy Django migration
|
|
# when the StrokeData are expanded.
|
|
# No Django Instances of this model are managed. Strokedata table is
|
|
# accesssed directly with SQL commands
|
|
class StrokeData(models.Model):
|
|
class Meta:
|
|
db_table = 'strokedata'
|
|
index_together = ['workoutid']
|
|
|
|
workoutid = models.IntegerField(null=True)
|
|
time = models.FloatField(null=True,verbose_name='Time')
|
|
hr = models.IntegerField(null=True,verbose_name='Heart Rate')
|
|
pace = models.FloatField(null=True,verbose_name='Pace')
|
|
workoutstate = models.IntegerField(null=True,default=1)
|
|
spm = models.FloatField(null=True,verbose_name='Stroke Rate')
|
|
cumdist = models.FloatField(null=True,verbose_name='Cumulative Distance')
|
|
ftime = models.CharField(max_length=30)
|
|
fpace = models.CharField(max_length=30)
|
|
driveenergy = models.FloatField(null=True,verbose_name='Work per Stroke')
|
|
power = models.FloatField(null=True,verbose_name='Power')
|
|
averageforce = models.FloatField(null=True,verbose_name='Average Force')
|
|
drivelength = models.FloatField(null=True,verbose_name='Drive Length')
|
|
peakforce = models.FloatField(null=True,verbose_name='Peak Force')
|
|
forceratio = models.FloatField(null=True,verbose_name='Average/Peak Force Ratio')
|
|
distance = models.FloatField(null=True,verbose_name='Distance')
|
|
drivespeed = models.FloatField(null=True,verbose_name='Drive Speed')
|
|
hr_ut2 = models.IntegerField(null=True)
|
|
hr_ut1 = models.IntegerField(null=True)
|
|
hr_at = models.IntegerField(null=True)
|
|
hr_tr = models.IntegerField(null=True)
|
|
hr_an = models.IntegerField(null=True)
|
|
hr_max = models.IntegerField(null=True)
|
|
hr_bottom = models.IntegerField(null=True)
|
|
x_right = models.FloatField(null=True)
|
|
ergpace = models.FloatField(null=True)
|
|
nowindpace = models.FloatField(null=True)
|
|
equivergpower = models.FloatField(null=True)
|
|
fergpace = models.CharField(max_length=30)
|
|
fnowindpace = models.CharField(max_length=30)
|
|
catch = models.FloatField(default=0,null=True,verbose_name='Catch Angle')
|
|
slip = models.FloatField(default=0,null=True,verbose_name='Slip')
|
|
finish = models.FloatField(default=0,null=True,verbose_name='Finish Angle')
|
|
wash = models.FloatField(default=0,null=True,verbose_name='Wash')
|
|
peakforceangle = models.FloatField(default=0,null=True,verbose_name='Peak Force Angle')
|
|
rhythm = models.FloatField(default=1.0,null=True,verbose_name='Rhythm')
|
|
|
|
# A wrapper around the png files
|
|
class GraphImage(models.Model):
|
|
filename = models.CharField(default='',max_length=150,blank=True,null=True)
|
|
creationdatetime = models.DateTimeField()
|
|
workout = models.ForeignKey(Workout)
|
|
|
|
def __str__(self):
|
|
return self.filename
|
|
|
|
# delete related file object when image is deleted
|
|
@receiver(models.signals.post_delete,sender=GraphImage)
|
|
def auto_delete_image_on_delete(sender,instance, **kwargs):
|
|
if instance.filename:
|
|
if os.path.isfile(instance.filename):
|
|
os.remove(instance.filename)
|
|
else:
|
|
print "couldn't find the file "+instance.filename
|
|
|
|
# Date input utility
|
|
class DateInput(forms.DateInput):
|
|
input_type = 'date'
|
|
|
|
# Form to update Workout data
|
|
class WorkoutForm(ModelForm):
|
|
duration = forms.TimeInput(format='%H:%M:%S.%f')
|
|
class Meta:
|
|
model = Workout
|
|
fields = ['name','date','starttime','duration','distance','workouttype','boattype','notes']
|
|
widgets = {
|
|
'date': DateInput(),
|
|
'notes': forms.Textarea,
|
|
'duration': forms.TimeInput(format='%H:%M:%S.%f'),
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(WorkoutForm, self).__init__(*args, **kwargs)
|
|
if self.instance.workouttype != 'water':
|
|
del self.fields['boattype']
|
|
|
|
# Used for the rowing physics calculations
|
|
class AdvancedWorkoutForm(ModelForm):
|
|
class Meta:
|
|
model = Workout
|
|
fields = ['boattype','weightvalue']
|
|
|
|
# Simple form to set rower's Functional Threshold Power
|
|
class RowerPowerForm(ModelForm):
|
|
class Meta:
|
|
model = Rower
|
|
fields = ['ftp']
|
|
|
|
# Form to set rower's Power zones, including test routines
|
|
# to enable consistency
|
|
class RowerPowerZonesForm(ModelForm):
|
|
|
|
powerzones = ['UT3','UT2','UT1','AT','TR','AN']
|
|
ut3name = forms.CharField(initial=powerzones[0])
|
|
ut2name = forms.CharField(initial=powerzones[1])
|
|
ut1name = forms.CharField(initial=powerzones[2])
|
|
atname = forms.CharField(initial=powerzones[3])
|
|
trname = forms.CharField(initial=powerzones[4])
|
|
anname = forms.CharField(initial=powerzones[5])
|
|
|
|
def __init__(self, *args,**kwargs):
|
|
super(RowerPowerZonesForm, self).__init__(*args, **kwargs)
|
|
|
|
if 'instance' in kwargs:
|
|
powerzones = kwargs['instance'].powerzones
|
|
else:
|
|
powerzones = ['UT3','UT2','UT1','AT','TR','AN']
|
|
|
|
self.fields['ut3name'].initial = powerzones[0]
|
|
self.fields['ut2name'].initial = powerzones[1]
|
|
self.fields['ut1name'].initial = powerzones[2]
|
|
self.fields['atname'].initial = powerzones[3]
|
|
self.fields['trname'].initial = powerzones[4]
|
|
self.fields['anname'].initial = powerzones[5]
|
|
|
|
class Meta:
|
|
model = Rower
|
|
fields = ['pw_ut2','pw_ut1','pw_at','pw_tr','pw_an']
|
|
|
|
def clean(self):
|
|
cleaned_data = super(RowerPowerZonesForm, self).clean()
|
|
try:
|
|
pw_ut2 = cleaned_data['pw_ut2']
|
|
except:
|
|
pw_ut2 = int(self.data['pw_ut2'])
|
|
try:
|
|
pw_ut1 = cleaned_data['pw_ut1']
|
|
except:
|
|
pw_ut1 = int(self.data['pw_ut1'])
|
|
try:
|
|
pw_at = cleaned_data['pw_at']
|
|
except:
|
|
pw_at = int(self.data['pw_at'])
|
|
try:
|
|
pw_tr = cleaned_data['pw_tr']
|
|
except:
|
|
pw_tr = int(self.data['pw_tr'])
|
|
try:
|
|
pw_an = cleaned_data['pw_an']
|
|
except:
|
|
pw_an = int(self.data['pw_an'])
|
|
|
|
try:
|
|
ut3name = cleaned_data['ut3name']
|
|
except:
|
|
ut2name = 'UT3'
|
|
cleaned_data['ut3name'] = 'UT3'
|
|
try:
|
|
ut2name = cleaned_data['ut2name']
|
|
except:
|
|
ut2name = 'UT2'
|
|
cleaned_data['ut2name'] = 'UT2'
|
|
try:
|
|
ut1name = cleaned_data['ut1name']
|
|
except:
|
|
ut1name = 'UT1'
|
|
cleaned_data['ut1name'] = 'UT1'
|
|
try:
|
|
atname = cleaned_data['atname']
|
|
except:
|
|
atname = 'AT'
|
|
cleaned_data['atname'] = 'AT'
|
|
try:
|
|
trname = cleaned_data['trname']
|
|
except:
|
|
trname = 'TR'
|
|
cleaned_data['ut1name'] = 'TR'
|
|
try:
|
|
anname = cleaned_data['anname']
|
|
except:
|
|
anname = 'AN'
|
|
cleaned_data['ut1name'] = 'AN'
|
|
|
|
|
|
if pw_ut1 <= pw_ut2:
|
|
e = "{ut1name} should be higher than {ut2name}".format(
|
|
ut1name = ut1name,
|
|
ut2name= ut2name,
|
|
)
|
|
raise forms.ValidationError(e)
|
|
if pw_at <= pw_ut1:
|
|
e = "{atname} should be higher than {ut1name}".format(
|
|
atname = atname,
|
|
ut1name= ut1name,
|
|
)
|
|
raise forms.ValidationError(e)
|
|
if pw_tr <= pw_at:
|
|
e = "{trname} should be higher than {atname}".format(
|
|
atname = atname,
|
|
trname= trname,
|
|
)
|
|
raise forms.ValidationError(e)
|
|
if pw_an <= pw_tr:
|
|
e = "{anname} should be higher than {trname}".format(
|
|
anname = anname,
|
|
trname= trname,
|
|
)
|
|
raise forms.ValidationError(e)
|
|
|
|
return cleaned_data
|
|
|
|
# Form to set rower's Email and Weight category
|
|
class AccountRowerForm(ModelForm):
|
|
class Meta:
|
|
model = Rower
|
|
fields = ['weightcategory']
|
|
|
|
class UserForm(ModelForm):
|
|
class Meta:
|
|
model = User
|
|
fields = ['first_name','last_name','email']
|
|
|
|
|
|
def clean(self):
|
|
cleaned_data = super(UserForm, self).clean()
|
|
|
|
# Form to set rower's Heart Rate zones, including test routines
|
|
# to enable consistency
|
|
class RowerForm(ModelForm):
|
|
class Meta:
|
|
model = Rower
|
|
fields = ['rest','ut2','ut1','at','tr','an','max']
|
|
|
|
|
|
def clean_rest(self):
|
|
rest = self.cleaned_data['rest']
|
|
|
|
if rest<10:
|
|
self.data['rest']=10
|
|
raise forms.ValidationError("Resting heart rate should be higher than 10 bpm")
|
|
|
|
|
|
if rest>250:
|
|
self.data['rest'] = 250
|
|
raise forms.ValidationError("Resting heart rate should be lower than 250 bpm")
|
|
|
|
|
|
return rest
|
|
|
|
def clean_ut2(self):
|
|
ut2 = self.cleaned_data['ut2']
|
|
|
|
if ut2<10:
|
|
raise forms.ValidationError("UT2 heart rate should be higher than 10 bpm")
|
|
|
|
if ut2>250:
|
|
raise forms.ValidationError("UT2 heart rate should be lower than 250 bpm")
|
|
|
|
return ut2
|
|
|
|
def clean_ut1(self):
|
|
ut1 = self.cleaned_data['ut1']
|
|
|
|
if ut1<10:
|
|
raise forms.ValidationError("UT1 heart rate should be higher than 10 bpm")
|
|
|
|
if ut1>250:
|
|
raise forms.ValidationError("Resting heart rate should be lower than 250 bpm")
|
|
|
|
return ut1
|
|
|
|
def clean_at(self):
|
|
at = self.cleaned_data['at']
|
|
|
|
if at<10:
|
|
raise forms.ValidationError("AT heart rate should be higher than 10 bpm")
|
|
|
|
if at>250:
|
|
raise forms.ValidationError("AT heart rate should be lower than 250 bpm")
|
|
|
|
return at
|
|
|
|
def clean_tr(self):
|
|
tr = self.cleaned_data['tr']
|
|
|
|
if tr<10:
|
|
raise forms.ValidationError("TR heart rate should be higher than 10 bpm")
|
|
|
|
if tr>250:
|
|
raise forms.ValidationError("TR heart rate should be lower than 250 bpm")
|
|
|
|
return tr
|
|
|
|
def clean_an(self):
|
|
an = self.cleaned_data['an']
|
|
|
|
if an<10:
|
|
raise forms.ValidationError("AN heart rate should be higher than 10 bpm")
|
|
|
|
if an>250:
|
|
raise forms.ValidationError("AN heart rate should be lower than 250 bpm")
|
|
|
|
return an
|
|
|
|
def clean_max(self):
|
|
max = self.cleaned_data['max']
|
|
|
|
if max<10:
|
|
raise forms.ValidationError("Max heart rate should be higher than 10 bpm")
|
|
|
|
if max>250:
|
|
raise forms.ValidationError("Max heart rate should be lower than 250 bpm")
|
|
|
|
return max
|
|
|
|
|
|
def clean(self):
|
|
|
|
|
|
try:
|
|
rest = self.cleaned_data['rest']
|
|
except:
|
|
rest = int(self.data['rest'])
|
|
|
|
try:
|
|
ut2 = self.cleaned_data['ut2']
|
|
except:
|
|
ut2 = self.data['ut2']
|
|
|
|
try:
|
|
ut1 = self.cleaned_data['ut1']
|
|
except:
|
|
ut1 = self.data['ut1']
|
|
|
|
try:
|
|
at = self.cleaned_data['at']
|
|
except:
|
|
at = self.data['at']
|
|
|
|
try:
|
|
an = self.cleaned_data['an']
|
|
except:
|
|
an = self.data['an']
|
|
|
|
try:
|
|
tr = self.cleaned_data['tr']
|
|
except:
|
|
tr = self.data['tr']
|
|
|
|
try:
|
|
max = self.cleaned_data['max']
|
|
except:
|
|
max = self.data['max']
|
|
|
|
if rest>=ut2:
|
|
raise forms.ValidationError("Resting heart rate should be lower than UT2")
|
|
if ut2>=ut1:
|
|
raise forms.ValidationError("UT2 should be lower than UT1")
|
|
if ut2>=ut1:
|
|
raise forms.ValidationError("UT2 should be lower than UT1")
|
|
if ut1>=at:
|
|
raise forms.ValidationError("UT1 should be lower than AT")
|
|
if at>=tr:
|
|
raise forms.ValidationError("AT should be lower than TR")
|
|
if tr>=an:
|
|
raise forms.ValidationError("TR should be lower than AN")
|
|
if an>=max:
|
|
raise forms.ValidationError("AN should be lower than Max")
|
|
|
|
# An announcement that goes to the right of the workouts list
|
|
# optionally sends a tweet to our twitter account
|
|
class SiteAnnouncement(models.Model):
|
|
created = models.DateField(default=timezone.now)
|
|
announcement = models.TextField(max_length=140)
|
|
expires = models.DateField(default=timezone.now)
|
|
modified = models.DateField(default=timezone.now)
|
|
dotweet = models.BooleanField(default=False)
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.id:
|
|
self.created = timezone.now()
|
|
self.expires = timezone.now()+datetime.timedelta(days=10)
|
|
self.modified = timezone.now()
|
|
if self.dotweet:
|
|
try:
|
|
status = tweetapi.PostUpdate(self.announcement)
|
|
except:
|
|
try:
|
|
status = tweetapi.PostUpdate(self.announcement[:135])
|
|
except:
|
|
pass
|
|
return super(SiteAnnouncement,self).save(*args, **kwargs)
|