Private
Public Access
1
0
Files
rowsandall/rowers/models.py
Sander Roosendaal 0d1884962b list view improved
2019-02-22 13:51:08 +01:00

3498 lines
113 KiB
Python

from __future__ import unicode_literals
from django.db import models,IntegrityError
from django.contrib.auth.models import User
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
from django import forms
from django.forms import ModelForm
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,AdminTimeWidget,AdminSplitDateTime
from datetimewidget.widgets import DateTimeWidget
from django.core.validators import validate_email
import os
import twitter
import re
import pytz
from django_countries.fields import CountryField
from scipy.interpolate import splprep, splev, CubicSpline
import numpy as np
from django.conf import settings
from sqlalchemy import create_engine
import sqlalchemy as sa
from sqlite3 import OperationalError
from django.utils import timezone
import pandas as pd
from dateutil import parser
import datetime
from rowers.rows import validate_file_extension
from collections import OrderedDict
from timezonefinder import TimezoneFinder
import mytypes
from matplotlib import path
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)
from rowers.database import *
timezones = (
(x,x) for x in pytz.common_timezones
)
def half_year_from_now():
return (datetime.datetime.now(tz=timezone.utc)+timezone.timedelta(days=182)).date()
def a_week_from_now():
return (datetime.datetime.now(tz=timezone.utc)+timezone.timedelta(days=7)).date()
def current_day():
return (datetime.datetime.now(tz=timezone.utc)).date()
def current_time():
return datetime.datetime.now(tz=timezone.utc)
class UserFullnameChoiceField(forms.ModelChoiceField):
def label_from_instance(self,obj):
return obj.get_full_name()
# model for configurable template field
class TemplateListField(models.TextField):
def __init__(self, *args, **kwargs):
self.token = kwargs.pop('token',',')
super(TemplateListField, 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)
# 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)
c2url = 'http://www.concept2.com/indoor-rowers/racing/records/world?machine=1&event=All&gender=All&age=All&weight=All'
def update_records(url=c2url,verbose=True):
try:
dfs = pd.read_html(url,attrs={'class':'views-table'})
df = dfs[0]
df.columns = df.columns.str.strip()
success = 1
except:
df = pd.DataFrame()
if not df.empty:
C2WorldClassAgePerformance.objects.all().delete()
df.Gender = df.Gender.apply(lambda x: 'male' if x=='M' else 'female')
df['Distance'] = df['Event']
df['Duration'] = 0
for nr,row in df.iterrows():
if 'm' in row['Record']:
df.ix[nr,'Distance'] = row['Record'][:-1]
df.ix[nr,'Duration'] = 60*row['Event']
else:
df.ix[nr,'Distance'] = row['Event']
try:
tobj = datetime.datetime.strptime(row['Record'],'%M:%S.%f')
except ValueError:
tobj = datetime.datetime.strptime(row['Record'],'%H:%M:%S.%f')
df.ix[nr,'Duration'] = 3600.*tobj.hour+60.*tobj.minute+tobj.second+tobj.microsecond/1.e6
for nr,row in df.iterrows():
try:
weightcategory = row.Weight.lower()
except AttributeError:
weightcategory = 'hwt'
sex = row.Gender
name = row.Name
age = int(row.Age)
distance = int(row.Distance)
duration = float(row.Duration)
season = int(row.Season)
velo = distance/duration
power = int(2.8*velo**3)
record = C2WorldClassAgePerformance(
age = age,
weightcategory = weightcategory,
sex=sex,
distance = distance,
duration = duration,
power = power,
season = season,
name = name,
)
try:
if verbose:
print record
record.save()
except:
if verbose:
print(record,'*')
else:
pass
class CalcAgePerformance(models.Model):
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
sexcategories = (
('male','male'),
('female','female'),
)
weightcategory = models.CharField(default="hwt",
max_length=30,
choices=weightcategories)
sex = models.CharField(default="female",
max_length=30,
choices=sexcategories)
age = models.IntegerField(default=19,verbose_name="Age")
duration = models.FloatField(default=1,blank=True)
power = models.IntegerField(default=200)
class Meta:
db_table = 'calcagegrouprecords'
class PowerTimeFitnessMetric(models.Model):
modechoices = (
('rower','Rower'),
('water','On the water')
)
date = models.DateField(default=current_day)
last_workout = models.IntegerField(default=0)
user = models.ForeignKey(User)
PowerFourMin = models.FloatField(default=0)
PowerTwoK = models.FloatField(default=0)
PowerOneHour = models.FloatField(default=0)
workoutmode = models.CharField(default='rower',choices=modechoices,
max_length=40)
class Meta:
db_table = 'powertimefitnessmetric'
class C2WorldClassAgePerformance(models.Model):
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
sexcategories = (
('male','male'),
('female','female'),
)
weightcategory = models.CharField(default="hwt",
max_length=30,
choices=weightcategories)
sex = models.CharField(default="female",
max_length=30,
choices=sexcategories)
age = models.IntegerField(default=19,verbose_name="Age")
distance = models.IntegerField(default=2000)
name = models.CharField(max_length=200,blank=True)
duration = models.FloatField(default=1,blank=True)
season = models.IntegerField(default=2013)
power = models.IntegerField(default=200)
class Meta:
unique_together = ('age','sex','weightcategory','distance')
def __unicode__(self):
thestring = '{s} {w} {n} age {a} ({season}) {distance}m {duration} seconds'.format(
s = self.sex,
w = self.weightcategory,
n = self.name,
a = self.age,
season = self.season,
distance = self.distance,
duration = self.duration,
)
return thestring
def is_not_basic(user):
if user.rower.rowerplan == 'basic':
raise ValidationError(
"Basic user cannot be team manager"
)
# For future Team functionality
class Team(models.Model):
choices = (
('private','private'),
('open','open'),
)
viewchoices = (
('coachonly','Coach Only'),
('allmembers','All Members')
)
name = models.CharField(max_length=150,unique=True,verbose_name='Team Name')
notes = models.CharField(blank=True,max_length=200,verbose_name='Team Purpose')
manager = models.ForeignKey(User, null=True,) # validators=[is_not_basic])
private = models.CharField(max_length=30,choices=choices,default='open',
verbose_name='Team Type')
viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior')
def __unicode__(self):
return self.name
def save(self, *args, **kwargs):
manager = self.manager
if manager.rower.rowerplan == 'basic':
if manager.rower.protrialexpires < datetime.date.today() and manager.rower.plantrialexpires < datetime.date.today():
raise ValidationError(
"Basic user cannot be team manager"
)
if manager.rower.rowerplan in ['plan','pro']:
otherteams = Team.objects.filter(manager=manager)
if len(otherteams) >= 1:
raise ValidationError(
"Pro and Self-Coach users cannot have more than one team"
)
super(Team, self).save(*args,**kwargs)
class TeamForm(ModelForm):
class Meta:
model = Team
fields = ['name','notes','private','viewing']
widgets = {
'notes': forms.Textarea,
}
class TeamInvite(models.Model):
team = models.ForeignKey(Team)
user = models.ForeignKey(User,null=True)
issuedate = models.DateField(default=current_day)
code = models.CharField(max_length=150,unique=True)
email = models.CharField(max_length=150,null=True,blank=True)
class TeamInviteForm(ModelForm):
user = UserFullnameChoiceField(queryset=User.objects.all(),required=False)
email = forms.EmailField(required=False)
class Meta:
model = TeamInvite
fields = ['user','email']
class TeamRequest(models.Model):
team = models.ForeignKey(Team)
user = models.ForeignKey(User,null=True)
issuedate = models.DateField(default=current_day)
code = models.CharField(max_length=150,unique=True)
from utils import (
workflowleftpanel,workflowmiddlepanel,
defaultleft,defaultmiddle,landingpages
)
from utils import geo_distance
def polygon_coord_center(polygon):
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
latitudes = pd.Series([p.latitude for p in points])
longitudes = pd.Series([p.longitude for p in points])
return latitudes.mean(), longitudes.mean()
def polygon_to_path(polygon):
points = GeoPoint.objects.filter(polygon=polygon).order_by("order_in_poly")
s = []
for point in points:
s.append([point.latitude,point.longitude])
p = path.Path(s[:-1])
return p
from rowers.courseutils import coordinate_in_path
def course_spline(coordinates):
latitudes = coordinates['latitude'].values
longitudes = coordinates['longitude'].values
# spline parameters
s = 1.0
k = min([5,len(latitudes)-1])
nest = -1
t = np.linspace(0,1,len(latitudes))
tnew = np.linspace(0,1,100)
latnew = CubicSpline(t,latitudes,bc_type='clamped')(tnew)
lonnew = CubicSpline(t,longitudes,bc_type='clamped')(tnew)
# latnew = CubicSpline(t,latitudes,bc_type='natural')(tnew)
# lonnew = CubicSpline(t,longitudes,bc_type='natural')(tnew)
# tckp,u = splprep([t,latitudes,longitudes],s=s,k=k,nest=nest)
# tnew,latnew,lonnew = splev(np.linspace(0,1,100),tckp)
newcoordinates = pd.DataFrame({
'latitude':latnew,
'longitude':lonnew,
})
return newcoordinates
def course_coord_center(course):
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
latitudes = []
longitudes = []
for p in polygons:
latitude,longitude = polygon_coord_center(p)
latitudes.append(latitude)
longitudes.append(longitude)
latitude = pd.Series(latitudes).median()
longitude = pd.Series(longitudes).median()
coordinates = pd.DataFrame({
'latitude':latitudes,
'longitude':longitudes,
})
return latitude,longitude,coordinates
def course_coord_maxmin(course):
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
latitudes = []
longitudes = []
for p in polygons:
latitude,longitude = polygon_coord_center(p)
latitudes.append(latitude)
longitudes.append(longitude)
lat_min = pd.Series(latitudes).min()
lat_max = pd.Series(latitudes).max()
long_min = pd.Series(longitudes).min()
long_max = pd.Series(longitudes).max()
return lat_min,lat_max,long_min,long_max
def get_dir_vector(polygon1,polygon2):
lat1,lon1 = polygon_coord_center(polygon1)
lat2,lon2 = polygon_coord_center(polygon2)
return [lat2-lat1,lon2-lon1]
def get_delta(vector,polygon):
x = pd.Series(range(10000))/9999.
vlat = vector[0]
vlon = vector[1]
lat1,lon1 = polygon_coord_center(polygon)
lat = x.apply(lambda x:lat1+x*vlat)
lon = x.apply(lambda x:lon1+x*vlon)
totdist,bearing = geo_distance(lat1,lon1,lat1+vlat,lon1+vlon)
dist = x*totdist
p = polygon_to_path(polygon)
f = lambda x: coordinate_in_path(x['lat'],x['lon'],p)
df = pd.DataFrame({'x':x,
'lat':lat,
'lon':lon,
'dist':dist,
})
df['inpolygon'] = df.apply(f,axis=1)
b = (~df['inpolygon']).shift(-1)+df['inpolygon']
if len(df[b==2]):
return 1.0e3*df[b==2]['dist'].min()
else:
return 0
def get_delta_start(course):
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
vector = get_dir_vector(polygons[0],polygons[1])
delta = get_delta(vector,polygons[0])
return delta
def get_delta_finish(course):
polygons = GeoPolygon.objects.filter(course=course).order_by("-order_in_course")
vector = get_dir_vector(polygons[0],polygons[1])
delta = get_delta(vector,polygons[0])
return delta
def course_length(course):
polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course")
totaldist = 0
if not polygons:
return 0
for i in range(len(polygons)-1):
latitude1,longitude1 = polygon_coord_center(polygons[i])
latitude2,longitude2 = polygon_coord_center(polygons[i+1])
dist = geo_distance(latitude1,longitude1,
latitude2,longitude2,)
totaldist += 1000.*dist[0]
vector = get_dir_vector(polygons[0],polygons[1])
deltastart = get_delta(vector,polygons[0])
polygons = polygons.reverse()
vector = get_dir_vector(polygons[0],polygons[1])
deltafinish = get_delta(vector,polygons[0])
return int(totaldist-deltastart-deltafinish)
sexcategories = (
('male','male'),
('female','female'),
('not specified','not specified'),
)
weightcategories = (
('hwt','heavy-weight'),
('lwt','light-weight'),
)
# Plan
plans = (
('basic','basic'),
('pro','pro'),
('plan','plan'),
('coach','coach')
)
paymenttypes = (
('single','single'),
('recurring','recurring')
)
paymentprocessors = (
('paypal','PayPal'),
('braintree','BrainTree')
)
class PaidPlan(models.Model):
shortname = models.CharField(max_length=50,choices=plans)
name = models.CharField(max_length=200)
external_id = models.CharField(blank=True,null=True,default=None,max_length=200)
price = models.FloatField(blank=True,null=True,default=None)
paymentprocessor = models.CharField(
max_length=50,choices=paymentprocessors,default='braintree')
paymenttype = models.CharField(
default='single',max_length=30,
verbose_name='Payment Type',
choices=paymenttypes,
)
active = models.BooleanField(default=True)
clubsize = models.IntegerField(default=0)
def __unicode__(self):
return '{name} - {shortname} at {price:.2f} EURO ({paymenttype} payment)'.format(
name = self.name,
shortname = self.shortname,
price = self.price,
paymenttype = self.paymenttype,
paymentprocessor = self.paymentprocessor,
)
class CoachingGroup(models.Model):
name = models.CharField(default='group',max_length=30,null=True,blank=True)
def __unicode__(self):
return 'Coaching Group {id}: {name}'.format(
id = self.pk,
name = self.name
)
# Extension of User with rowing specific data
class Rower(models.Model):
adaptivetypes = mytypes.adaptivetypes
stravatypes = (
('Ride','Ride'),
('Kitesurf','Kitesurf'),
('Run','Run'),
('NordicSki','NordicSki'),
('Swim','Swim'),
('RockClimbing','RockClimbing'),
('Hike','Hike'),
('RollerSki','RollerSki'),
('Walk','Walk'),
('Rowing','Rowing'),
('AlpineSki','AlpineSki'),
('Snowboard','Snowboard'),
('BackcountrySki','BackcountrySki'),
('Snowshoe','Snowshoe'),
('Canoeing','Canoeing'),
('StairStepper','StairStepper'),
('Crossfit','Crossfit'),
('StandUpPaddling','StandUpPaddling'),
('EBikeRide','EBikeRide'),
('Surfing','Surfing'),
('Elliptical','Elliptical'),
('VirtualRide','VirtualRide'),
('IceSkate','IceSkate'),
('WeightTraining','WeightTraining'),
('InlineSkate','InlineSkate'),
('Windsurf','Windsurf'),
('Kayaking','Kayaking'),
('Workout','Workout'),
('Yoga','Yoga'),
)
user = models.OneToOneField(User)
#billing details
country = CountryField(default=None, null=True, blank=True)
street_address = models.CharField(default='',blank=True,null=True,max_length=200)
city = models.CharField(default='',blank=True,null=True,max_length=200)
postal_code = models.CharField(default='',blank=True,null=True,max_length=200)
customer_id = models.CharField(default=None,null=True,blank=True,max_length=200)
subscription_id = models.CharField(default=None,null=True,
blank=True,max_length=200)
rowerplan = models.CharField(default='basic',max_length=30,
choices=plans)
paymenttype = models.CharField(
default='single',max_length=30,
verbose_name='Payment Type',
choices=paymenttypes,
)
paymentprocessor = models.CharField(max_length=50,
choices=paymentprocessors,
null=True,blank=True,
default='braintree')
paidplan = models.ForeignKey(PaidPlan,null=True,default=None)
planexpires = models.DateField(default=current_day)
teamplanexpires = models.DateField(default=current_day)
clubsize = models.IntegerField(default=0)
protrialexpires = models.DateField(default=datetime.date(1970,1,1))
plantrialexpires = models.DateField(default=datetime.date(1970,1,1))
offercoaching = models.BooleanField(default=False, verbose_name='Offer Remote Coaching')
# Privacy Data
gdproptin = models.BooleanField(default=False)
gdproptindate = models.DateTimeField(blank=True,null=True)
# 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")
hrftp = models.IntegerField(default=0,verbose_name="FTP heart rate")
# Weight Category (for sync to C2)
weightcategory = models.CharField(default="hwt",
max_length=30,
choices=weightcategories)
sex = models.CharField(default="not specified",
max_length=30,
choices=sexcategories)
adaptiveclass = models.CharField(choices=adaptivetypes,max_length=50,
default='None',
verbose_name='Adaptive Classification')
birthdate = models.DateField(null=True,blank=True)
# Power Zone Data
ftp = models.IntegerField(default=226,verbose_name="Functional Threshold Power")
p0 = models.FloatField(default=1.0,verbose_name="CP p1")
p1 = models.FloatField(default=1.0,verbose_name="CP p2")
p2 = models.FloatField(default=1.0,verbose_name="CP p3")
p3 = models.FloatField(default=1.0,verbose_name="CP p4")
cpratio = models.FloatField(default=1.0,verbose_name="CP fit ratio")
ep0 = models.FloatField(default=1.0,verbose_name="erg CP p1")
ep1 = models.FloatField(default=1.0,verbose_name="erg CP p2")
ep2 = models.FloatField(default=1.0,verbose_name="erg CP p3")
ep3 = models.FloatField(default=1.0,verbose_name="erg CP p4")
ecpratio = models.FloatField(default=1.0,verbose_name="erg CP fit ratio")
otwslack = models.IntegerField(default=0,verbose_name="OTW Power slack")
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'])
# Site Settings
workflowleftpanel = TemplateListField(default=defaultleft)
workflowmiddlepanel = TemplateListField(default=defaultmiddle)
defaultlandingpage = models.CharField(default='workout_edit_view',
max_length=200,
choices=landingpages,
verbose_name="Default Landing Page")
# 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)
c2_auto_export = models.BooleanField(default=False)
c2_auto_import = models.BooleanField(default=False)
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)
sporttracks_auto_export = models.BooleanField(default=False)
underarmourtoken = models.CharField(default='',max_length=200,blank=True,null=True)
underarmourtokenexpirydate = models.DateTimeField(blank=True,null=True)
underarmourrefreshtoken = models.CharField(default='',max_length=200,
blank=True,null=True)
mapmyfitness_auto_export = models.BooleanField(default=False)
tptoken = models.CharField(default='',max_length=1000,blank=True,null=True)
tptokenexpirydate = models.DateTimeField(blank=True,null=True)
tprefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True)
trainingpeaks_auto_export = models.BooleanField(default=False)
polartoken = models.CharField(default='',max_length=1000,blank=True,null=True)
polartokenexpirydate = models.DateTimeField(blank=True,null=True)
polarrefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True)
polaruserid = models.IntegerField(default=0)
polar_auto_import = models.BooleanField(default=False)
stravatoken = models.CharField(default='',max_length=200,blank=True,null=True)
stravatokenexpirydate = models.DateTimeField(blank=True,null=True)
stravarefreshtoken = models.CharField(default='',max_length=1000,
blank=True,null=True)
stravaexportas = models.CharField(default="Rowing",
max_length=30,
choices=stravatypes,
verbose_name="Export Workouts to Strava as")
strava_auto_export = models.BooleanField(default=False)
strava_auto_import = models.BooleanField(default=False)
runkeepertoken = models.CharField(default='',max_length=200,
blank=True,null=True)
runkeeper_auto_export = models.BooleanField(default=False)
privacychoices = (
('visible','Visible'),
('hidden','Hidden'),
)
getemailnotifications = models.BooleanField(default=False,
verbose_name='Receive email notifications')
emailbounced = models.BooleanField(default=False,
verbose_name='Email Address Bounced')
getimportantemails = models.BooleanField(default=True,
verbose_name='Get Important Emails')
# Friends/Team
friends = models.ManyToManyField("self",blank=True)
mycoachgroup = models.ForeignKey(CoachingGroup,related_name='coachingrole',null=True)
coachinggroups = models.ManyToManyField(CoachingGroup,related_name='coaches')
privacy = models.CharField(default='visible',max_length=30,
choices=privacychoices)
team = models.ManyToManyField(Team,blank=True)
# Export and Time Zone Settings
defaulttimezone = models.CharField(default='UTC',max_length=100,
choices=timezones,
verbose_name='Default Time Zone')
# Show flex chart notes
showfavoritechartnotes = models.BooleanField(default=True,
verbose_name='Show Notes for Favorite Charts')
def __str__(self):
return self.user.first_name+' '+self.user.last_name
def __unicode__(self):
return self.user.first_name+' '+self.user.last_name
def clean_email(self):
return self.user.email.lower()
class DeactivateUserForm(forms.ModelForm):
class Meta:
model = User
fields = ['is_active']
class DeleteUserForm(forms.ModelForm):
delete_user = forms.BooleanField(initial=False,
label='Remove my account and all data')
class Meta:
model = User
fields = []
# requestor is user
class CoachRequest(models.Model):
coach = models.ForeignKey(Rower)
user = models.ForeignKey(User,null=True)
issuedate = models.DateField(default=current_day)
code = models.CharField(max_length=150,unique=True)
# requestor is coach
class CoachOffer(models.Model):
coach = models.ForeignKey(Rower)
user = models.ForeignKey(User,null=True)
issuedate = models.DateField(default=current_day)
code = models.CharField(max_length=150,unique=True)
from django.db.models.signals import m2m_changed
def check_teams_on_change(sender, **kwargs):
instance = kwargs.pop('instance', None)
action = kwargs.pop('action', None)
pk_set = kwargs.pop('pk_set',None)
if action == 'pre_add' and instance.rowerplan=='basic':
if instance.protrialexpires < datetime.date.today() and instance.plantrialexpires < datetime.date.today():
for id in pk_set:
team = Team.objects.get(id=id)
if team.manager.rower.rowerplan not in ['coach']:
raise ValidationError(
"You cannot join a team led by a Pro or Self-Coach user"
)
m2m_changed.connect(check_teams_on_change, sender=Rower.team.through)
#@receiver(models.signals.post_save,sender=Rower)
#def auto_delete_teams_on_change(sender, instance, **kwargs):
# if instance.rowerplan != 'coach':
# teams = Team.objects.filter(manager=instance.user)
# for team in teams:
# team.delete()
from rowers.metrics import axlabels
favchartlabelsx = axlabels.copy()
favchartlabelsy1 = axlabels.copy()
favchartlabelsy2 = axlabels.copy()
favchartlabelsy1.pop('None')
parchoicesy1 = list(sorted(favchartlabelsy1.items(), key = lambda x:x[1]))
parchoicesy2 = list(sorted(favchartlabelsy2.items(), key = lambda x:x[1]))
parchoicesx = list(sorted(favchartlabelsx.items(), key = lambda x:x[1]))
# Saving a chart as a favorite chart
class FavoriteChart(models.Model):
workouttypechoices = [
('ote','Erg/SkiErg'),
('otw','On The Water'),
('all','All')
]
for workoutsource in mytypes.workoutsources:
workouttypechoices.append(workoutsource)
plottypes = (
('line','Line Chart'),
('scatter','Scatter Chart')
)
yparam1 = models.CharField(max_length=50,choices=parchoicesy1,verbose_name='Y1')
yparam2 = models.CharField(max_length=50,choices=parchoicesy2,verbose_name='Y2',default='None',blank=True)
xparam = models.CharField(max_length=50,choices=parchoicesx,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")
notes = models.CharField(max_length=300,verbose_name='Chart Notes',
default='Flex Chart Notes',blank=True)
user = models.ForeignKey(Rower)
class FavoriteForm(ModelForm):
class Meta:
model = FavoriteChart
fields = ['xparam','yparam1','yparam2',
'plottype','workouttype','reststrokes','notes']
# widgets = {
# 'notes': forms.Textarea,
# }
# 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'
class BasePlannedSessionFormSet(BaseFormSet):
def clean(self):
if any(self.serrors):
return
# Check if workout is owned by this user
def checkworkoutuser(user,workout):
if user.is_anonymous():
return False
try:
r = Rower.objects.get(user=user)
if workout.user == r:
return True
coaches = []
for group in workout.user.coachinggroups.all():
coach = Rower.objects.get(mycoachgroup=group)
coaches.append(coach)
for coach in coaches:
if user.rower == coach and workout.privacy == 'visible':
return True
else:
return False
except Rower.DoesNotExist:
return False
# Check if workout may be viewed by this user
def checkworkoutuserview(user,workout):
if user.is_anonymous():
return False
try:
r = Rower.objects.get(user=user)
if workout.user == r:
return True
teams = workout.user.team.all()
for team in teams:
if team in r.team.all():
return True
return False
except Rower.DoesNotExist:
return False
return False
def checkviewworkouts(user,rower):
try:
r = user.rower
if rower == r:
return True
teams = Team.objects.filter(manager=user)
print Rower.objects.filter(team__in=teams)
if rower in Rower.objects.filter(team__in=teams):
return True
except Rower.DoesNotExist:
return False
# check if user is plan and rower is in his group
def checkaccessplanuser(user,rower):
try:
r = Rower.objects.get(user=user)
if rower == r:
return True
team_managers = [t.manager for t in rower.team.all() if t.manager.rower.rowerplan in ['plan','coach']]
if user.rower.rowerplan != 'basic':
return user in team_managers
else:
return False
return False
except Rower.DoesNotExist:
return False
# Check if user is coach or rower
def checkaccessuser(user,rower):
try:
r = Rower.objects.get(user=user)
if rower == r:
return True
coaches = []
for group in rower.coachinggroups.all():
coach = Rower.objects.get(mycoachgroup=group)
coaches.append(coach)
for coach in coaches:
if user.rower == coach:
return True
else:
return False
except Rower.DoesNotExist:
return False
timezones = (
(x,x) for x in pytz.common_timezones
)
# models related to geo data (points, polygon, courses)
class GeoCourse(models.Model):
manager = models.ForeignKey(Rower)
distance = models.IntegerField(default=0)
name = models.CharField(max_length=150,blank=True)
country = models.CharField(max_length=150,blank=True)
notes = models.CharField(blank=True,max_length=200,verbose_name='Course Notes')
def __unicode__(self):
name = self.name
country = self.country
d = self.distance
if d == 0:
self.distance = course_length(self)
self.save()
d = self.distance
return u'{country} - {name} - {d}m'.format(
name=name,
country=country,
d = d,
)
class GeoCourseEditForm(ModelForm):
class Meta:
model = GeoCourse
fields = ['name','country','notes']
widgets = {
'notes': forms.Textarea,
}
class GeoPolygon(models.Model):
name = models.CharField(max_length=150,blank=True)
course = models.ForeignKey(GeoCourse, blank=True)
order_in_course = models.IntegerField(default=0)
def __unicode__(self):
name = self.name
coursename = self.course.name
return u'{coursename} - {name}'.format(
name=name,
coursename=coursename
)
# Need error checking to insert new polygons into existing course (all later polygons
# increase there order_in_course number
class GeoPoint(models.Model):
latitude = models.FloatField(default=0)
longitude = models.FloatField(default=0)
polygon = models.ForeignKey(GeoPolygon,blank=True)
order_in_poly = models.IntegerField(default=0)
# need error checking to "insert" new point into existing polygon? This affects order_in_poly
# of multiple GeoPoint instances
# models related to training planning - draft
# Do we need a separate class TestTarget?
class TrainingTarget(models.Model):
rowers = models.ManyToManyField(Rower, related_name='targetathletes',
verbose_name='Athletes')
manager = models.ForeignKey(Rower,related_name='targetmanager',null=True)
name = models.CharField(max_length=150,blank=True)
date = models.DateField(
default=half_year_from_now)
notes = models.TextField(max_length=300,blank=True)
def __unicode__(self):
date = self.date
name = self.name
id = self.pk
try:
ownerfirst = self.manager.user.first_name
ownerlast = self.manager.user.last_name
except AttributeError:
ownerfirst = ''
ownerlast = ''
stri = u'#{id}: {n} {d} {ownerfirst} {ownerlast}'.format(
ownerfirst = ownerfirst,
ownerlast = ownerlast,
d = date.strftime('%Y-%m-%d'),
n = name,
id = id,
)
return stri
class TrainingTargetForm(ModelForm):
class Meta:
model = TrainingTarget
fields = ['name','date','notes','rowers']
widgets = {
'date': AdminDateWidget()
}
def __init__(self,*args, **kwargs):
user = kwargs.pop('user',None)
super(TrainingTargetForm, self).__init__(*args, **kwargs)
try:
teams = Team.objects.filter(manager=self.instance.manager.user)
except AttributeError:
if user:
teams = Team.objects.filter(manager=user)
else:
teams = []
if not teams:
self.fields.pop('rowers')
else:
self.fields['rowers'].queryset = Rower.objects.filter(
team__in=teams
).distinct().order_by("user__last_name","user__first_name")
class TrainingPlan(models.Model):
statuschoices = (
('active','active'),
('deactivated','inactive'),
)
rowers = models.ManyToManyField(Rower,related_name='planathletes',
verbose_name='Athletes')
manager = models.ForeignKey(Rower,related_name='planmanager',null=True)
name = models.CharField(max_length=150,blank=True)
status = models.BooleanField(default=True,verbose_name='Active')
target = models.ForeignKey(TrainingTarget,blank=True,null=True)
startdate = models.DateField(default=current_day)
enddate = models.DateField(
default=half_year_from_now)
def __unicode__(self):
name = self.name
startdate = self.startdate
enddate = self.enddate
firstname = self.manager.user.first_name
lastname = self.manager.user.last_name
stri = u'Training Plan by {firstname} {lastname} {s} - {e}: {name}'.format(
s = startdate.strftime('%Y-%m-%d'),
e = enddate.strftime('%Y-%m-%d'),
firstname = firstname,
lastname = lastname,
name=name
)
return stri
def save(self, *args, **kwargs):
manager = self.manager
if manager.rowerplan in ['basic','pro']:
raise ValidationError(
"Basic user cannot have a training plan"
)
if self.enddate < self.startdate:
startdate = self.startdate
enddate = self.enddate
self.startdate = enddate
self.enddate = startdate
if not self.enddate <= self.startdate:
super(TrainingPlan,self).save(*args, **kwargs)
if self.status:
otherplans = TrainingPlan.objects.filter(
status=True).exclude(
pk=self.pk).order_by(
"-startdate")
for otherplan in otherplans:
if otherplan.startdate <= self.enddate and otherplan.startdate >= self.startdate:
for rower in self.rowers.all():
if rower in otherplan.rowers.all():
self.status = False
self.save()
if otherplan.enddate >= self.startdate and otherplan.enddate <= self.enddate:
for rower in self.rowers.all():
if rower in otherplan.rowers.all():
self.status = False
self.save()
macrocycles = TrainingMacroCycle.objects.filter(plan = self)
if not macrocycles:
m = TrainingMacroCycle(
plan = self,
name = 'Filler',
startdate = self.startdate,
enddate = self.enddate,
)
m.save()
else:
createmacrofillers(self)
class TrainingPlanForm(ModelForm):
class Meta:
model = TrainingPlan
fields = ['name','target','startdate','enddate','status','rowers']
widgets = {
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget()
}
def __init__(self,*args, **kwargs):
targets = kwargs.pop('targets',None)
user = kwargs.pop('user',None)
super(TrainingPlanForm, self).__init__(*args, **kwargs)
if targets:
targetchoices = [(x.id,x) for x in targets]
targetchoices.append((None,'---'))
self.fields['target'].choices = targetchoices
elif self.instance.pk is not None:
self.fields['target'].queryset = TrainingTarget.objects.filter(
manager=self.instance.manager,
date__gte=current_day()).order_by("date")
else:
self.fields.pop('target')
try:
teams = Team.objects.filter(manager=self.instance.manager.user)
except AttributeError:
if user:
teams = Team.objects.filter(manager=user)
else:
teams = []
if not teams:
self.fields.pop('rowers')
else:
self.fields['rowers'].queryset = Rower.objects.filter(
team__in=teams
).distinct().order_by("user__last_name","user__first_name")
cycletypechoices = (
('filler','System Defined'),
('userdefined','User Defined')
)
def createmacrofillers(plan):
fillers = TrainingMacroCycle.objects.filter(
plan = plan, type = 'filler'
)
for f in fillers:
f.delete()
cycles = TrainingMacroCycle.objects.filter(
plan = plan
).order_by("-startdate")
if not cycles:
macr = TrainingMacroCycle(
plan=plan,
startdate = plan.startdate,
enddate = plan.enddate,
type='filler',
name='Filler'
)
macr.save()
thedate = plan.enddate
while cycles:
if cycles[0].enddate < thedate:
macr = TrainingMacroCycle(
plan=plan,
startdate = cycles[0].enddate+datetime.timedelta(days=1),
enddate = thedate,
type='filler',
name='Filler'
)
macr.save()
thedate = cycles[0].startdate-datetime.timedelta(days=1)
cycles = cycles[1:]
cycles = TrainingMacroCycle.objects.filter(
plan = plan
).order_by("startdate")
if cycles[0].startdate > plan.startdate:
macr = TrainingMacroCycle(
plan=plan,
startdate = plan.startdate,
enddate = cycles[0].startdate-datetime.timedelta(days=1),
type='filler',
name='Filler'
)
macr.save()
def createmesofillers(plan):
fillers = TrainingMesoCycle.objects.filter(
plan = plan, type = 'filler'
)
for f in fillers:
f.delete()
cycles = TrainingMesoCycle.objects.filter(
plan = plan
).order_by("-startdate")
if not cycles:
macr = TrainingMesoCycle(
plan = plan,
startdate = plan.startdate,
enddate = plan.enddate,
type='filler',
name='Filler'
)
macr.save()
thedate = plan.enddate
while cycles:
if cycles[0].enddate < thedate:
macr = TrainingMesoCycle(
plan=plan,
startdate = cycles[0].enddate+datetime.timedelta(days=1),
enddate = thedate,
type='filler',
name='Filler'
)
macr.save()
thedate = cycles[0].startdate-datetime.timedelta(days=1)
cycles = cycles[1:]
cycles = TrainingMesoCycle.objects.filter(
plan = plan
).order_by("startdate")
if cycles[0].startdate > plan.startdate:
macr = TrainingMesoCycle(
plan=plan,
startdate = plan.startdate,
enddate = cycles[0].startdate-datetime.timedelta(days=1),
type='filler',
name='Filler'
)
macr.save()
def createmicrofillers(plan):
fillers = TrainingMicroCycle.objects.filter(
plan = plan, type = 'filler'
)
for f in fillers:
f.delete()
cycles = TrainingMicroCycle.objects.filter(
plan = plan
).order_by("-startdate")
if not cycles:
macr = TrainingMicroCycle(
plan=plan,
startdate = plan.startdate,
enddate = plan.enddate,
type='filler',
name='Filler'
)
macr.save()
thedate = plan.enddate
while cycles:
if cycles[0].enddate < thedate:
macr = TrainingMicroCycle(
plan=plan,
startdate = cycles[0].enddate+datetime.timedelta(days=1),
enddate = thedate,
type='filler',
name='Filler'
)
macr.save()
thedate = cycles[0].startdate-datetime.timedelta(days=1)
cycles = cycles[1:]
cycles = TrainingMicroCycle.objects.filter(
plan = plan
).order_by("startdate")
if cycles and cycles[0].startdate > plan.startdate:
macr = TrainingMicroCycle(
plan=plan,
startdate = plan.startdate,
enddate = cycles[0].startdate-datetime.timedelta(days=1),
type='filler',
name='Filler'
)
macr.save()
def microcyclecheckdates(plan):
cycles = TrainingMicroCycle.objects.filter(
plan=plan
).order_by("-startdate")
thedate = plan.enddate
while cycles:
if cycles[0].enddate < plan.startdate:
cycles[0].delete()
if cycles[0].startdate > plan.enddate:
cycles[0].delete()
if cycles[0].enddate > thedate:
cycles[0].enddate = thedate
cycles[0].save()
thedate = cycles[0].startdate-datetime.timedelta(days=1)
cycles = cycles[1:]
cycles = TrainingMicroCycle.objects.filter(
plan=plan
).order_by("startdate")
thedate = plan.startdate
while cycles:
if cycles[0].startdate < thedate:
cycles[0].startdate = thedate
cycles[0].save()
try:
thedate = cycles[1].startdate-datetime.timedelta(days=1)
except IndexError:
pass
cycles = cycles[1:]
def mesocyclecheckdates(plan):
cycles = TrainingMesoCycle.objects.filter(
plan=plan
).order_by("-startdate")
thedate = plan.enddate
while cycles:
if cycles[0].enddate < plan.startdate:
cycles[0].delete()
if cycles[0].startdate > plan.enddate:
cycles[0].delete()
if cycles[0].enddate > thedate:
cycles[0].enddate = thedate
cycles[0].save()
thedate = cycles[0].startdate-datetime.timedelta(days=1)
cycles = cycles[1:]
cycles = TrainingMesoCycle.objects.filter(
plan=plan
).order_by("startdate")
thedate = plan.startdate
while cycles:
if cycles[0].startdate < thedate:
cycles[0].startdate = thedate
cycles[0].save()
try:
thedate = cycles[1].startdate-datetime.timedelta(days=1)
except IndexError:
pass
cycles = cycles[1:]
def macrocyclecheckdates(plan):
cycles = TrainingMacroCycle.objects.filter(
plan=plan
).order_by("-startdate")
thedate = plan.enddate
while cycles:
if cycles[0].enddate < plan.startdate:
cycles[0].delete()
if cycles[0].startdate > plan.enddate:
cycles[0].delete()
if cycles[0].enddate > thedate:
cycles[0].enddate = thedate
cycles[0].save()
thedate = cycles[0].startdate-datetime.timedelta(days=1)
cycles = cycles[1:]
cycles = TrainingMacroCycle.objects.filter(
plan=plan
).order_by("startdate")
thedate = plan.startdate
while cycles:
if cycles[0].startdate < thedate:
cycles[0].startdate = thedate
cycles[0].save()
try:
thedate = cycles[1].startdate-datetime.timedelta(days=1)
except IndexError:
pass
cycles = cycles[1:]
class TrainingMacroCycle(models.Model):
plan = models.ForeignKey(TrainingPlan)
name = models.CharField(max_length=150,blank=True)
startdate = models.DateField(default=current_day)
enddate = models.DateField(
default=half_year_from_now)
notes = models.TextField(max_length=300,blank=True)
type = models.CharField(default='filler',
choices=cycletypechoices,
max_length=150)
plantime = models.IntegerField(default=0,verbose_name='Planned Duration')
plandistance = models.IntegerField(default=0,verbose_name='Planned Distance')
planrscore = models.IntegerField(default=0,verbose_name='Planned rScore')
plantrimp = models.IntegerField(default=0,verbose_name='Planned TRIMP')
actualtime = models.IntegerField(default=0,verbose_name='Actual Duration')
actualdistance = models.IntegerField(default=0,verbose_name='Actual Distance')
actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore')
actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP')
def __unicode__(self):
stri = 'Macro Cycle - {n} ({sd} - {ed})'.format(
n = self.name,
sd = self.startdate,
ed = self.enddate,
)
return stri
def save(self, *args, **kwargs):
if self.enddate < self.startdate:
startdate = self.startdate
enddate = self.enddate
self.startdate = enddate
self.enddate = startdate
fillers = TrainingMacroCycle.objects.filter(
plan=self.plan,type='filler')
for f in fillers:
f.delete()
if self.enddate > self.plan.enddate:
self.enddate = self.plan.enddate
if self.startdate < self.plan.startdate:
self.startdate = self.plan.startdate
othercycles = TrainingMacroCycle.objects.filter(
plan=self.plan).exclude(pk=self.pk).order_by("-startdate")
for othercycle in othercycles:
if othercycle.startdate <= self.enddate and othercycle.startdate >= self.startdate:
self.enddate = othercycle.startdate-datetime.timedelta(days=1)
if othercycle.enddate >= self.startdate and othercycle.enddate <= self.enddate:
self.startdate = othercycle.enddate+datetime.timedelta(days=1)
if not self.enddate <= self.startdate:
super(TrainingMacroCycle,self).save(*args, **kwargs)
mesocycles = TrainingMesoCycle.objects.filter(plan = self)
if not mesocycles:
meso = TrainingMesoCycle(
plan = self,
name = 'Filler',
startdate = self.startdate,
enddate = self.enddate,
)
meso.save()
else:
createmesofillers(self)
class TrainingMacroCycleForm(ModelForm):
class Meta:
model = TrainingMacroCycle
fields = ['name','startdate','enddate','notes']
widgets = {
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget()
}
class TrainingMesoCycle(models.Model):
plan = models.ForeignKey(TrainingMacroCycle)
name = models.CharField(max_length=150,blank=True)
startdate = models.DateField(default=current_day)
enddate = models.DateField(
default=half_year_from_now)
notes = models.TextField(max_length=300,blank=True)
type = models.CharField(default='filler',
choices=cycletypechoices,
max_length=150)
plantime = models.IntegerField(default=0,verbose_name='Planned Duration')
plandistance = models.IntegerField(default=0,verbose_name='Planned Distance')
planrscore = models.IntegerField(default=0,verbose_name='Planned rScore')
plantrimp = models.IntegerField(default=0,verbose_name='Planned TRIMP')
actualtime = models.IntegerField(default=0,verbose_name='Actual Duration')
actualdistance = models.IntegerField(default=0,verbose_name='Actual Distance')
actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore')
actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP')
def __unicode__(self):
stri = 'Meso Cycle - {n} ({sd} - {ed})'.format(
n = self.name,
sd = self.startdate,
ed = self.enddate,
)
return stri
def save(self, *args, **kwargs):
if self.enddate < self.startdate:
startdate = self.startdate
enddate = self.enddate
self.startdate = enddate
self.enddate = startdate
fillers = TrainingMesoCycle.objects.filter(
plan=self.plan,type='filler')
for f in fillers:
f.delete()
if self.enddate > self.plan.enddate:
self.enddate = self.plan.enddate
if self.startdate < self.plan.startdate:
self.startdate = self.plan.startdate
othercycles = TrainingMesoCycle.objects.filter(
plan=self.plan).exclude(pk=self.pk).order_by("-startdate")
for othercycle in othercycles:
if othercycle.startdate <= self.enddate and othercycle.startdate >= self.startdate:
self.enddate = othercycle.startdate-datetime.timedelta(days=1)
if othercycle.enddate >= self.startdate and othercycle.enddate <= self.enddate:
self.startdate = othercycle.enddate+datetime.timedelta(days=1)
if not self.enddate <= self.startdate:
super(TrainingMesoCycle,self).save(*args, **kwargs)
microcycles = TrainingMicroCycle.objects.filter(plan = self)
if not microcycles:
micro = TrainingMicroCycle(
plan = self,
name = 'Filler',
startdate = self.startdate,
enddate = self.enddate,
)
micro.save()
else:
createmicrofillers(self)
class TrainingMicroCycle(models.Model):
plan = models.ForeignKey(TrainingMesoCycle)
name = models.CharField(max_length=150,blank=True)
startdate = models.DateField(default=current_day)
enddate = models.DateField(
default=half_year_from_now)
notes = models.TextField(max_length=300,blank=True)
type = models.CharField(default='filler',
choices=cycletypechoices,
max_length=150)
plantime = models.IntegerField(default=0,verbose_name='Planned Duration')
plandistance = models.IntegerField(default=0,verbose_name='Planned Distance')
planrscore = models.IntegerField(default=0,verbose_name='Planned rScore')
plantrimp = models.IntegerField(default=0,verbose_name='Planned TRIMP')
actualtime = models.IntegerField(default=0,verbose_name='Actual Duration')
actualdistance = models.IntegerField(default=0,verbose_name='Actual Distance')
actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore')
actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP')
def __unicode__(self):
stri = 'Micro Cycle - {n} ({sd} - {ed})'.format(
n = self.name,
sd = self.startdate,
ed = self.enddate,
)
return stri
def save(self, *args, **kwargs):
if self.enddate < self.startdate:
startdate = self.startdate
enddate = self.enddate
self.startdate = enddate
self.enddate = startdate
fillers = TrainingMicroCycle.objects.filter(
plan=self.plan,type='filler')
for f in fillers:
f.delete()
if self.enddate > self.plan.enddate:
self.enddate = self.plan.enddate
if self.startdate < self.plan.startdate:
self.startdate = self.plan.startdate
othercycles = TrainingMicroCycle.objects.filter(
plan=self.plan).exclude(pk=self.pk).order_by("-startdate")
for othercycle in othercycles:
if othercycle.startdate <= self.enddate and othercycle.startdate >= self.startdate:
self.enddate = othercycle.startdate-datetime.timedelta(days=1)
if othercycle.enddate >= self.startdate and othercycle.enddate <= self.enddate:
self.startdate = othercycle.enddate+datetime.timedelta(days=1)
if not self.enddate <= self.startdate:
super(TrainingMicroCycle,self).save(*args, **kwargs)
class TrainingMesoCycleForm(ModelForm):
class Meta:
model = TrainingMesoCycle
fields = ['name','startdate','enddate','notes']
widgets = {
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget()
}
class TrainingMicroCycleForm(ModelForm):
class Meta:
model = TrainingMicroCycle
fields = ['name','startdate','enddate','notes']
widgets = {
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget()
}
regularsessiontypechoices = (
('session','Training Session'),
('challenge','Challenge'),
('test','Mandatory Test'),
('cycletarget','Total for a time period'),
('coursetest','OTW test over a course'),
)
# model for Planned Session (Workout, Challenge, Test)
class PlannedSession(models.Model):
sessiontypechoices = (
('session','Training Session'),
('challenge','Challenge'),
('test','Mandatory Test'),
('cycletarget','Total for a time period'),
('coursetest','OTW test over a course'),
('race','Virtual Race'),
('indoorrace','Indoor Virtual Race'),
)
regularsessiontypechoices = (
('session','Training Session'),
('challenge','Challenge'),
('test','Mandatory Test'),
('cycletarget','Total for a time period'),
('coursetest','OTW test over a course'),
)
sessionmodechoices = (
('distance','Distance'),
('time','Time'),
('rScore','rScore'),
('TRIMP','TRIMP'),
)
criteriumchoices = (
('none','Approximately'),
('minimum','At Least'),
('exact','Exactly'),
)
verificationchoices = (
('none','None'),
('automatic','Automatic'),
('manual','Manual')
)
sessionunitchoices = (
('min','minutes'),
('m','meters'),
('None',None),
)
manager = models.ForeignKey(User)
course = models.ForeignKey(GeoCourse,blank=True,null=True,
verbose_name='OTW Course')
name = models.CharField(max_length=150,blank=True,
verbose_name='Name')
comment = models.TextField(max_length=500,blank=True,
)
startdate = models.DateField(default=current_day,
verbose_name='On or After')
enddate = models.DateField(default=a_week_from_now,
verbose_name='On or Before')
preferreddate = models.DateField(default=a_week_from_now,
verbose_name='Preferred Date')
sessiontype = models.CharField(default='session',
choices=sessiontypechoices,
max_length=150,
verbose_name='Session Type')
sessionvalue = models.IntegerField(default=60,verbose_name='Value')
max_nr_of_workouts = models.IntegerField(
default=0,verbose_name='Maximum number of workouts'
)
sessionunit = models.CharField(
default='min',choices=sessionunitchoices,
max_length=150,
verbose_name='Unit')
criterium = models.CharField(
default='none',
choices=criteriumchoices,
max_length=150,
verbose_name='Criteria')
verification = models.CharField(
default='none',
max_length=150,
choices=verificationchoices
)
team = models.ManyToManyField(Team,blank=True)
rower = models.ManyToManyField(Rower,blank=True)
sessionmode = models.CharField(default='time',
choices=sessionmodechoices,
max_length=150,
verbose_name='Session Mode')
hasranking = models.BooleanField(default=False)
def __unicode__(self):
name = self.name
startdate = self.startdate
enddate = self.enddate
stri = u'{n} {s} - {e}'.format(
s = startdate.strftime('%Y-%m-%d'),
e = enddate.strftime('%Y-%m-%d'),
n = name,
)
return stri
def save(self, *args, **kwargs):
if self.sessionvalue <= 0:
self.sessionvalue = 1
manager = self.manager
if self.sessiontype not in ['race','indoorrace']:
if manager.rower.rowerplan in ['basic','pro']:
if manager.rower.plantrialexpires < timezone.now().date():
raise ValidationError(
"You must be a Self-Coach user or higher to create a planned session"
)
# sort units
if self.sessionmode == 'distance':
if self.sessionunit not in ['m','km']:
self.sessionunit = 'm'
elif self.sessionmode == 'time':
self.sessionunit = 'min'
else:
self.sessionunit = 'None'
if self.sessiontype == 'test' or self.sessiontype == 'indoorrace':
if self.sessionmode not in ['distance','time']:
if self.sessionvalue < 100:
self.sessionmode = 'time'
self.sessionunit = 'min'
else:
self.sessionmode = 'distance'
self.sessionunit = 'm'
self.criterium = 'exact'
if self.sessiontype == 'coursetest' or self.sessiontype == 'race':
self.sessionmode = 'distance'
self.sessionunit = 'm'
self.criterium = 'none'
if self.course == None:
self.course = GeoCourse.objects.all()[0]
self.sessionvalue = self.course.distance
elif self.sessiontype != 'coursetest' and self.sessiontype != 'race':
self.course = None
if self.enddate < self.startdate:
self.enddate = self.startdate
if self.preferreddate > self.enddate:
self.preferreddate = self.enddate
if self.preferreddate < self.startdate:
self.preferreddate = self.startdate
super(PlannedSession,self).save(*args, **kwargs)
from django.core.validators import RegexValidator,validate_email
registerchoices = (
('windowstart','Start of Race Window'),
('windowend','End of Race Window'),
('deadline','Evaluation Closure Deadline'),
('manual','Manual - select below'),
)
class VirtualRace(PlannedSession):
# has_registration = models.BooleanField(default=False)
registration_form = models.CharField(
max_length=100,
default='windowstart',
choices=registerchoices,
verbose_name='Registration Closure Quick Selector'
)
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
def save(self, *args, **kwargs):
# test race window logic
start_time = self.start_time
start_date = self.startdate
startdatetime = datetime.datetime.combine(start_date,start_time)
startdatetime = pytz.timezone(self.timezone).localize(
startdatetime
)
end_time = self.end_time
end_date = self.enddate
enddatetime = datetime.datetime.combine(end_date,end_time)
enddatetime = pytz.timezone(self.timezone).localize(
enddatetime
)
if startdatetime > enddatetime:
self.start_time = end_time
self.startdate = end_date
self.end_time = start_time
self.enddate = start_date
enddatetime = startdatetime
if self.evaluation_closure < enddatetime:
self.evaluation_closure = enddatetime + timezone.timedelta(days=1)
super(VirtualRace,self).save(*args, **kwargs)
class RaceLogo(models.Model):
filename = models.CharField(default='',max_length=150)
creationdatetime = models.DateTimeField()
user = models.ForeignKey(User)
width = models.IntegerField(default=1200)
height = models.IntegerField(default=600)
race = models.ManyToManyField(VirtualRace,related_name='logos')
def __str__(self):
return self.filename
def delete(self, *args, **kwargs):
os.remove(self.filename)
print 'file deleted'
super(RaceLogo,self).delete(*args, **kwargs)
# Date input utility
class DateInput(forms.DateInput):
input_type = 'date'
class PlannedSessionForm(ModelForm):
class Meta:
model = PlannedSession
fields = ['startdate',
'enddate',
'preferreddate',
'name',
'sessiontype',
'sessionmode',
'criterium',
'sessionvalue',
'sessionunit',
'course',
'comment',
]
dateTimeOptions = {
'format': 'yyyy-mm-dd',
'autoclose': True,
}
widgets = {
'comment': forms.Textarea,
'startdate': AdminDateWidget(),
'enddate': AdminDateWidget(),
'preferreddate': AdminDateWidget(),
}
def __init__(self,*args,**kwargs):
super(PlannedSessionForm, self).__init__(*args, **kwargs)
self.fields['course'].queryset = GeoCourse.objects.all().order_by("country","name")
self.fields['sessiontype'].choices = regularsessiontypechoices
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
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(),
}
labels = {
'sessionunit': 'Meters or minutes',
'sessionvalue': 'How far or how long'
}
def __init__(self,*args,**kwargs):
timezone = kwargs.pop('timezone',None)
super(IndoorVirtualRaceForm, self).__init__(*args, **kwargs)
self.fields['sessionunit'].choices = [('min','minutes'),('m','meters')]
self.fields['sessionvalue'].initial = 2000
self.fields['sessionunit'].initial = 'm'
if timezone:
self.fields['timezone'].initial = timezone
def clean(self):
cd = self.cleaned_data
timezone_str = cd['timezone']
value = cd['sessionvalue']
if value <= 0:
raise forms.ValidationError('The Value must be a positive, non-zero value')
unit = cd['sessionunit']
if unit == 'm' and value < 100:
raise forms.ValidationError('Minimum distance is 100m')
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)
registration_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=False)
evaluation_closure = forms.SplitDateTimeField(widget=AdminSplitDateTime(),required=True)
class Meta:
model = VirtualRace
fields = [
'name',
'startdate',
'start_time',
'enddate',
'end_time',
# 'has_registration',
'registration_form',
'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")
def clean(self):
cd = self.cleaned_data
course = cd['course']
geocourse = GeoCourse.objects.get(id=course.id)
timezone_str = get_course_timezone(geocourse)
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 PlannedSessionFormSmall(ModelForm):
regularsessiontypechoices = (
('session','Training Session'),
('challenge','Challenge'),
('test','Mandatory Test'),
('cycletarget','Total for a time period'),
('coursetest','OTW test over a course'),
)
class Meta:
model = PlannedSession
fields = ['startdate',
'enddate',
'preferreddate',
'name',
'sessiontype',
'sessionmode',
'criterium',
'sessionvalue',
'sessionunit',
'manager',
]
dateTimeOptions = {
'format': 'yyyy-mm-dd',
'autoclose': True,
}
widgets = {
'startdate': DateInput(attrs={'size':10}),
'enddate': DateInput(attrs={'size':10}),
'preferreddate': DateInput(attrs={'size':10}),
'name': forms.TextInput(attrs={'size':10}),
'sessionvalue': forms.TextInput(attrs={'style':'width:5em',
'type':'number'}),
'manager': forms.HiddenInput(),
}
def __init__(self,*args,**kwargs):
super(PlannedSessionFormSmall, self).__init__(*args, **kwargs)
self.fields['sessiontype'].choices = regularsessiontypechoices
boattypes = mytypes.boattypes
# Workout
class Workout(models.Model):
workouttypes = mytypes.workouttypes
workoutsources = mytypes.workoutsources
privacychoices = mytypes.privacychoices
adaptivetypes = mytypes.adaptivetypes
user = models.ForeignKey(Rower)
team = models.ManyToManyField(Team,blank=True)
plannedsession = models.ForeignKey(PlannedSession, blank=True,null=True,
verbose_name='Session')
name = models.CharField(max_length=150,blank=True,null=True)
date = models.DateField()
workouttype = models.CharField(choices=workouttypes,max_length=50,
verbose_name='Exercise/Boat Class')
workoutsource = models.CharField(max_length=100,
default='unknown')
boattype = models.CharField(choices=boattypes,max_length=50,
default='1x',
verbose_name = 'Boat Type')
adaptiveclass = models.CharField(choices=adaptivetypes,max_length=50,
default='None',
verbose_name='Adaptive Classification')
starttime = models.TimeField(blank=True,null=True)
startdatetime = models.DateTimeField(blank=True,null=True)
timezone = models.CharField(default='UTC',
choices=timezones,
max_length=100)
distance = models.IntegerField(default=0,blank=True)
duration = models.TimeField(default=1,blank=True)
trimp = models.IntegerField(default=-1,blank=True)
rscore = models.IntegerField(default=-1,blank=True)
hrtss = models.IntegerField(default=-1,blank=True)
normp = models.IntegerField(default=-1,blank=True)
normv = models.FloatField(default=-1,blank=True)
normw = models.FloatField(default=-1,blank=True)
weightcategory = models.CharField(
default="hwt",
max_length=10,
choices=weightcategories,
verbose_name='Weight Category')
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.BigIntegerField(blank=True,null=True)
uploadedtostrava = models.BigIntegerField(default=0)
uploadedtosporttracks = models.BigIntegerField(default=0)
uploadedtounderarmour = models.BigIntegerField(default=0)
uploadedtotp = models.BigIntegerField(default=0)
uploadedtorunkeeper = models.BigIntegerField(default=0)
forceunit = models.CharField(default='lbs',
choices = (
('lbs','lbs'),
('N','N')
),
max_length=100)
# empower stuff
inboard = models.FloatField(default=0.88)
oarlength = models.FloatField(default=2.89)
notes = models.CharField(blank=True,null=True,max_length=1000)
summary = models.TextField(blank=True)
privacy = models.CharField(default='visible',max_length=30,
choices=privacychoices)
rankingpiece = models.BooleanField(default=False,verbose_name='Ranking Piece')
duplicate = models.BooleanField(default=False,verbose_name='Duplicate Workout')
def __unicode__(self):
date = self.date
name = self.name
distance = str(self.distance)
ownerfirst = self.user.user.first_name
ownerlast = self.user.user.last_name
duration = self.duration
boattype = self.boattype
workouttype = self.workouttype
if workouttype != 'water':
stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {workouttype} {ownerfirst} {ownerlast}'.format(
d = date.strftime('%Y-%m-%d'),
n = name,
dist = distance,
duration = duration,
workouttype = workouttype,
ownerfirst = ownerfirst,
ownerlast = ownerlast,
)
else:
stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {workouttype} {boattype} {ownerfirst} {ownerlast}'.format(
d = date.strftime('%Y-%m-%d'),
n = name,
dist = distance,
duration = duration,
workouttype = workouttype,
boattype=boattype,
ownerfirst = ownerfirst,
ownerlast = ownerlast,
)
return stri
# 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')
@receiver(models.signals.post_delete,sender=Workout)
def update_duplicates_on_delete(sender, instance, **kwargs):
if instance.id:
duplicates = Workout.objects.filter(
user=instance.user,date=instance.date,
duplicate=True)
for d in duplicates:
t = d.duration
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
workoutenddatetime = d.startdatetime+delta
ws = Workout.objects.filter(
user=d.user,date=d.date,
).exclude(
pk__in=[instance.pk,d.pk]
).exclude(
startdatetime__gt=workoutenddatetime
)
ws2 = []
for ww in ws:
t = ww.duration
delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second)
enddatetime = ww.startdatetime+delta
if enddatetime > d.startdatetime:
ws2.append(ww)
if len(ws2) == 0:
d.duplicate=False
d.save()
# 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()
# Virtual Race results (for keeping results when workouts are deleted)
class VirtualRaceResult(models.Model):
boatclasses = (type for type in mytypes.workouttypes if type[0] in mytypes.otwtypes)
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')
adaptiveclass = models.CharField(default="None",max_length=50,
choices=mytypes.adaptivetypes,
verbose_name="Adaptive Class")
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='water',
verbose_name = 'Boat Class')
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)
emailnotifications = models.BooleanField(default=True,
verbose_name = 'Receive race notifications by email')
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}" in {c} {d} with {t} ({s})'.format(
n = name,
r = self.race,
d = self.boattype,
c = self.boatclass,
t = self.teamname,
s = self.sex,
)
else:
return u'Entry for {n} for "{r}" in {c} {d} ({s})'.format(
n = name,
r = self.race,
d = self.boattype,
c = self.boatclass,
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) # ID of rower object
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')
adaptiveclass = models.CharField(default="None",max_length=50,
choices=mytypes.adaptivetypes,
verbose_name="Adaptive Class")
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)
emailnotifications = models.BooleanField(default=True,
verbose_name = 'Receive race notifications by email')
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)
workoutid = models.IntegerField(null=True)
plannedsession = models.ForeignKey(PlannedSession)
duration = models.TimeField(default=datetime.time(1,0))
distance = models.IntegerField(default=0)
coursecompleted = models.BooleanField(default=False)
class IndoorVirtualRaceResultForm(ModelForm):
class Meta:
model = IndoorVirtualRaceResult
fields = ['teamname','weightcategory','boatclass','age','adaptiveclass']
def __init__(self, *args, **kwargs):
super(IndoorVirtualRaceResultForm, self).__init__(*args, **kwargs)
class VirtualRaceResultForm(ModelForm):
class Meta:
model = VirtualRaceResult
fields = ['teamname','weightcategory','boatclass','boattype',
'age','adaptiveclass']
def __init__(self, *args, **kwargs):
boattypes = kwargs.pop('boattypes',None)
super(VirtualRaceResultForm, self).__init__(*args, **kwargs)
if boattypes:
self.fields['boattype'].choices = boattypes
self.fields['mix'] = forms.BooleanField(initial=False,
required=False,
label='Mixed Gender')
from rowers.metrics import rowingmetrics
strokedatafields = {
'workoutid':models.IntegerField(null=True),
'workoutstate':models.IntegerField(null=True,default=1),
'ftime':models.CharField(max_length=30),
'fpace':models.CharField(max_length=30),
'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),
}
for name,d in rowingmetrics:
if d['numtype'] == 'float':
try:
strokedatafields[name] = models.FloatField(
null=d['null'],
default=d['default'],
verbose_name=d['verbose_name'])
except KeyError:
strokedatafields[name] = models.FloatField(
null=d['null'],
verbose_name=d['verbose_name'])
elif d['numtype'] == 'integer':
try:
strokedatafields[name] = models.IntegerField(
null=d['null'],
default=d['default'],
verbose_name=d['verbose_name'])
except KeyError:
strokedatafields[name] = models.IntegerField(
null=d['null'],
verbose_name=d['verbose_name'])
class Meta:
db_table = 'strokedata'
index_together = ['workoutid']
app_label = 'rowers'
attrs = {'__module__': '', 'Meta': Meta}
attrs.update(strokedatafields)
# 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
StrokeData = type(str('StrokeData'), (models.Model,),
attrs
)
# Storing data for the OTW CP chart
class cpdata(models.Model):
delta = models.IntegerField(default=0)
cp = models.FloatField(default=0)
user = models.IntegerField(default=0)
class Meta:
db_table = 'cpdata'
index_together = ['user']
app_label = 'rowers'
# Storing data for the OTW CP chart
class cpergdata(models.Model):
delta = models.IntegerField(default=0)
cp = models.FloatField(default=0)
user = models.IntegerField(default=0)
class Meta:
db_table = 'cpergdata'
index_together = ['user']
app_label = 'rowers'
# Storing data for the OTW CP chart
class ergcpdata(models.Model):
delta = models.IntegerField(default=0)
cp = models.FloatField(default=0)
distance = models.FloatField(default=0)
user = models.IntegerField(default=0)
class Meta:
db_table = 'ergcpdata'
index_together = ['user']
app_label = 'rowers'
# 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)
width = models.IntegerField(default=1200)
height = models.IntegerField(default=600)
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):
others = GraphImage.objects.filter(filename=instance.filename)
if len(others) == 0:
os.remove(instance.filename)
else:
print "couldn't find the file "+instance.filename
# Form to update Workout data
class WorkoutForm(ModelForm):
# duration = forms.TimeInput(format='%H:%M:%S.%f')
class Meta:
model = Workout
fields = ['name',
'date',
'starttime',
'timezone',
'duration',
'distance',
'workouttype',
'boattype',
'weightcategory',
'adaptiveclass',
'notes',
'rankingpiece',
'duplicate',
'plannedsession']
widgets = {
'date': AdminDateWidget(),
'notes': forms.Textarea,
'duration': forms.TimeInput(format='%H:%M:%S.%f'),
}
def __init__(self, *args, **kwargs):
super(WorkoutForm, self).__init__(*args, **kwargs)
self.fields['private'] = forms.BooleanField(initial=False,
required=False,
label='Private')
self.fields['timezone'] = forms.ChoiceField(
choices = [(x,x) for x in pytz.common_timezones]
)
if 'instance' in kwargs:
if kwargs['instance'].privacy == 'visible':
self.fields['private'].initial = False
else:
self.fields['private'].initial = True
workout = self.instance
sps = PlannedSession.objects.filter(
rower__in=[workout.user],
startdate__lte=workout.date,
enddate__gte=workout.date,
).order_by("preferreddate","startdate","enddate").exclude(
sessiontype__in=['race','indoorrace'])
if not sps:
del self.fields['plannedsession']
else:
self.fields['plannedsession'].queryset = sps
else:
del self.fields['plannedsession']
# Used for the rowing physics calculations
class AdvancedWorkoutForm(ModelForm):
quick_calc = forms.BooleanField(initial=True,required=False)
class Meta:
model = Workout
fields = ['boattype','weightvalue','quick_calc']
class RowerExportForm(ModelForm):
class Meta:
model = Rower
fields = [
'stravaexportas',
'polar_auto_import',
'c2_auto_export',
'c2_auto_import',
'mapmyfitness_auto_export',
'runkeeper_auto_export',
'sporttracks_auto_export',
'strava_auto_export',
'strava_auto_import',
'trainingpeaks_auto_export',
]
# Simple form to set rower's Functional Threshold Power
class RowerPowerForm(ModelForm):
class Meta:
model = Rower
fields = ['hrftp','ftp','otwslack']
# 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 KeyError:
raise ValidationError("Value cannot be empty")
except:
pw_ut2 = int(self.data['pw_ut2'])
try:
pw_ut1 = cleaned_data['pw_ut1']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
pw_ut1 = int(self.data['pw_ut1'])
try:
pw_at = cleaned_data['pw_at']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
pw_at = int(self.data['pw_at'])
try:
pw_tr = cleaned_data['pw_tr']
except KeyError:
raise ValidationError("Value cannot be empty")
except:
pw_tr = int(self.data['pw_tr'])
try:
pw_an = cleaned_data['pw_an']
except KeyError:
raise ValidationError("Value cannot be empty")
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'
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 Auto Import and Export settings
class RowerImportExportForm(ModelForm):
class Meta:
model = Rower
fields = [
'polar_auto_import',
'c2_auto_export',
'c2_auto_import',
'mapmyfitness_auto_export',
'runkeeper_auto_export',
'sporttracks_auto_export',
'strava_auto_export',
'strava_auto_import',
'trainingpeaks_auto_export',
]
# Form to collect rower's Billing Info
class RowerBillingAddressForm(ModelForm):
class Meta:
model = Rower
fields = [
'street_address',
'city',
'postal_code',
'country'
]
def __init__(self, *args, **kwargs):
super(RowerBillingAddressForm, self).__init__(*args, **kwargs)
self.fields['country'].required = True
# Form to set rower's Email and Weight category
class AccountRowerForm(ModelForm):
class Meta:
model = Rower
fields = ['sex','birthdate','weightcategory',
'adaptiveclass',
'getemailnotifications',
'getimportantemails',
'defaulttimezone','showfavoritechartnotes',
'defaultlandingpage',
'offercoaching']
widgets = {
'birthdate': SelectDateWidget(
years=range(
timezone.now().year-100,timezone.now().year-10)),
}
def __init__(self, *args, **kwargs):
super(AccountRowerForm, self).__init__(*args, **kwargs)
if self.instance.rowerplan != 'coach':
self.fields.pop('offercoaching')
class UserForm(ModelForm):
class Meta:
model = User
fields = ['first_name','last_name','email']
def clean_first_name(self):
first_name = self.cleaned_data.get('first_name')
if len(first_name):
return first_name
raise forms.ValidationError('Please fill in your first name')
def clean_email(self):
email = self.cleaned_data.get('email')
try:
validate_email(email)
except ValidationError:
raise forms.ValidationError(
'Please enter a valid email address')
try:
match = User.objects.filter(email__iexact=email)
if self.instance in match:
return email
except User.DoesNotExist:
return email
raise forms.ValidationError('This email address is not allowed')
# 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:
try:
rest = int(self.data['rest'])
except ValueError:
rest = 0
try:
ut2 = self.cleaned_data['ut2']
except:
try:
ut2 = self.data['ut2']
except ValueError:
ut2 = 0
try:
ut1 = self.cleaned_data['ut1']
except:
try:
ut1 = self.data['ut1']
except ValueError:
ut1 = 0
try:
at = self.cleaned_data['at']
except:
try:
at = self.data['at']
except ValueError:
at = 0
try:
an = self.cleaned_data['an']
except:
try:
an = self.data['an']
except ValueError:
an = 0
try:
tr = self.cleaned_data['tr']
except:
try:
tr = self.data['tr']
except ValueError:
tr = 0
try:
max = self.cleaned_data['max']
except:
try:
max = self.data['max']
except ValueError:
max = 0
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=current_day)
announcement = models.TextField(max_length=280)
expires = models.DateField(default=current_day)
modified = models.DateField(default=current_day)
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[:270])
except:
pass
return super(SiteAnnouncement,self).save(*args, **kwargs)
# A comment by a user on a training
class WorkoutComment(models.Model):
comment = models.TextField(max_length=300)
created = models.DateTimeField(default=timezone.now)
read = models.BooleanField(default=False)
notification = models.BooleanField(default=True,verbose_name="Subscribe to new comment notifications")
user = models.ForeignKey(User)
workout = models.ForeignKey(Workout)
def __unicode__(self):
return u'Comment to: {w} by {u1} {u2}'.format(
w=self.workout,
u1 = self.user.first_name,
u2 = self.user.last_name,
)
class WorkoutCommentForm(ModelForm):
class Meta:
model = WorkoutComment
fields = ['comment','notification']
widgets = {
'comment': forms.Textarea,
}
# A comment by a user on a training
class PlannedSessionComment(models.Model):
comment = models.TextField(max_length=300)
created = models.DateTimeField(default=timezone.now)
read = models.BooleanField(default=False)
notification = models.BooleanField(default=True,verbose_name="Subscribe to new comment notifications")
user = models.ForeignKey(User)
plannedsession = models.ForeignKey(PlannedSession)
def __unicode__(self):
return u'Comment to: {w} by {u1} {u2}'.format(
w=self.workout,
u1 = self.user.first_name,
u2 = self.user.last_name,
)
class PlannedSessionCommentForm(ModelForm):
class Meta:
model = PlannedSessionComment
fields = ['comment','notification']
widgets = {
'comment': forms.Textarea,
}