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 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 django.core.exceptions import ValidationError 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 ) 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): 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: print record record.save() except IntegrityError: print(record,'*') 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=timezone.now) 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 # 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) 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 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=timezone.now) 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) 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=timezone.now) 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 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'), ) # Extension of User with rowing specific data class Rower(models.Model): 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) 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) 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) # Plan plans = ( ('basic','basic'), ('pro','pro'), ('plan','plan'), ('coach','coach') ) 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') rowerplan = models.CharField(default='basic',max_length=30, choices=plans) paymenttype = models.CharField( default='single',max_length=30, verbose_name='Payment Type', choices=( ('single','single'), ('recurring','recurring') ) ) planexpires = models.DateField(default=timezone.now) teamplanexpires = models.DateField(default=timezone.now) clubsize = models.IntegerField(default=0) protrialexpires = models.DateField(blank=True,null=True) plantrialexpires = models.DateField(blank=True,null=True) # Friends/Team friends = models.ManyToManyField("self",blank=True) 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 = [] @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) teams = workout.team.all() if workout.user == r: return True elif teams: for team in teams: if user == team.manager and workout.privacy == 'visible': return True else: 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) teams = Team.objects.filter(manager=user) if rower == r: return True elif teams: for team in teams: if team in rower.team.all(): 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 def half_year_from_now(): return timezone.now()+timezone.timedelta(days=182) def a_week_from_now(): return timezone.now()+timezone.timedelta(days=7) # 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") # SportTracks has a TrainingGoal like this #class TrainingGoal(models.Model): # rower = models.ForeignKey(Rower) # name = models.CharField(max_length=150,blank=True) # startdate = models.DateField(default=timezone.now) # enddate = models.DateField( # default=timezone.now()+datetime.timedelta(days=28)) # goalmetric = models.CharField(max_length=150,default='rower', # choices = modechoices) # value = models.IntegerValue(default=1) # I think we can use PlannedSession for that (in challenge mode) # although such a TrainingGoal could have automatically calculated # values without needing the user to assign 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=timezone.now) 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): 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=datetime.date.today()).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=timezone.now) 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=timezone.now) 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=timezone.now) 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() } # 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'), ) 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=timezone.now, 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 # 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") 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): 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(), } boattypes = mytypes.boattypes # Workout class Workout(models.Model): workouttypes = mytypes.workouttypes workoutsources = mytypes.workoutsources privacychoices = mytypes.privacychoices 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') 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.IntegerField(blank=True,null=True) uploadedtostrava = models.IntegerField(default=0) uploadedtosporttracks = models.IntegerField(default=0) uploadedtounderarmour = models.IntegerField(default=0) uploadedtotp = models.IntegerField(default=0) uploadedtorunkeeper = models.IntegerField(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') 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') 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'] def __init__(self, *args, **kwargs): super(IndoorVirtualRaceResultForm, self).__init__(*args, **kwargs) class VirtualRaceResultForm(ModelForm): class Meta: model = VirtualRaceResult fields = ['teamname','weightcategory','boatclass','boattype','age'] 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','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 set rower's Email and Weight category class AccountRowerForm(ModelForm): class Meta: model = Rower fields = ['sex','birthdate','weightcategory', 'getemailnotifications', 'getimportantemails', 'defaulttimezone','showfavoritechartnotes', 'defaultlandingpage'] widgets = { 'birthdate': SelectDateWidget( years=range( timezone.now().year-100,timezone.now().year-10)), } 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.get(email__iexact=email) if self.instance.user == match: return email except User.DoesNotExist: return email raise forms.ValidationError('This email address is not allowed') 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=timezone.now) announcement = models.TextField(max_length=280) 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[: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, }