From 4fb8694dcb4de12e0837d3b5a640309961c56e9b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 29 Oct 2019 09:42:12 +0100 Subject: [PATCH] Bug fix --- rowers/dataprep.py | 112 +++++++------- rowers/models.py | 372 ++++++++++++++++++++++----------------------- 2 files changed, 242 insertions(+), 242 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 1fb142d0..2c15a5a2 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -49,7 +49,7 @@ from rowingdata import ( SpeedCoach2Parser, FITParser, fitsummarydata, RitmoTimeParser,KinoMapParser, make_cumvalues,cumcpdata,ExcelTemplate, - summarydata, get_file_type, + summarydata, get_file_type, ) from rowingdata.csvparsers import HumonParser @@ -128,12 +128,12 @@ def polarization_index(df,rower): df.dropna(axis=0,inplace=True) df['dt'] = df['dt'].clip(upper=4,lower=0) - + masklow = (df['power']>0) & (df['power']=rower.pw_at) & (df['power']rower.pw_an) - + time_low_pw = df.loc[masklow,'dt'].sum() time_mid_pw = df.loc[maskmid,'dt'].sum() time_high_pw = df.loc[maskhigh,'dt'].sum() @@ -143,7 +143,7 @@ def polarization_index(df,rower): frac_high = time_high_pw/(time_low_pw+time_mid_pw+time_high_pw) index = math.log10(frac_high*100.*frac_low/frac_mid) - + return index @@ -157,7 +157,7 @@ def get_latlon(id): rowdata = rdata(w.csvfilename) if rowdata.df.empty: - return [pd.Series([]), pd.Series([])] + return [pd.Series([]), pd.Series([])] try: try: @@ -216,7 +216,7 @@ def workout_summary_to_df( trimps.append(workout_trimp(w)[0]) rscore = workout_rscore(w) rscores.append(int(rscore[0])) - + df = pd.DataFrame({ 'name':names, 'date':startdatetimes, @@ -285,7 +285,7 @@ def join_workouts(r,ids,title='Joined Workout', else: makeprivate = False - startdatetime = parent.startdatetime + startdatetime = parent.startdatetime else: oarlength = 2.89 inboard = 0.88 @@ -311,7 +311,7 @@ def join_workouts(r,ids,title='Joined Workout', workouttype = parent.workouttype notes = parent.notes summary = parent.summary - + files = [w.csvfilename for w in ws] row = rdata(files[0]) @@ -353,7 +353,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, ignoreadvanced=False): # clean data remove zeros and negative values - + # bring metrics which have negative values to positive domain if len(datadf)==0: return datadf @@ -377,7 +377,7 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, datadf['spm'] = datadf['spm'] + 1.0 except (KeyError,TypeError) as e: pass - + try: datadf = datadf.clip(lower=0) except TypeError: @@ -421,13 +421,13 @@ def clean_df_stats(datadf, workstrokesonly=True, ignorehr=True, datadf.mask(mask,inplace=True) except (KeyError,TypeError): pass - + try: mask = datadf['efficiency'] > 200. datadf.mask(mask,inplace=True) except (KeyError,TypeError): pass - + try: mask = datadf['spm'] < 10 datadf.mask(mask,inplace=True) @@ -648,7 +648,7 @@ def fitnessmetric_to_sql(m,table='powertimefitnessmetric',debug=False): placeholders = ", ".join(["?"] * len(m)) query = "INSERT into %s ( %s ) Values (%s)" % (table, columns, placeholders) - + values = tuple(m[key] for key in m.keys()) with engine.connect() as conn, conn.begin(): result = conn.execute(query,values) @@ -684,8 +684,8 @@ def deletecpdata_sql(rower_id,table='cpdata'): conn.close() engine.dispose() - - + + def updatecpdata_sql(rower_id,delta,cp,table='cpdata',distance=[]): deletecpdata_sql(rower_id) df = pd.DataFrame( @@ -836,7 +836,7 @@ def create_row_df(r,distance,duration,startdatetime,workouttype='rower', spm = 20. else: spm = avgspm - + step = totalseconds/float(nr_strokes) elapsed = np.arange(nr_strokes)*totalseconds/(float(nr_strokes-1)) @@ -867,7 +867,7 @@ def create_row_df(r,distance,duration,startdatetime,workouttype='rower', hr = avghr else: hr = 0 - + df = pd.DataFrame({ 'TimeStamp (sec)': unixtime, ' Horizontal (meters)': d, @@ -902,7 +902,7 @@ def create_row_df(r,distance,duration,startdatetime,workouttype='rower', return (id, message) from rowers.utils import totaltime_sec_to_string - + # Processes painsled CSV file to database def save_workout_database(f2, r, dosmooth=True, workouttype='rower', boattype='1x', @@ -934,7 +934,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', if row.df.empty: return (0, 'Error: CSV data file was empty') - + dtavg = row.df['TimeStamp (sec)'].diff().mean() if dtavg < 1: @@ -1025,7 +1025,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', if dosummary: summary = row.allstats() - + timezone_str = 'UTC' try: @@ -1094,7 +1094,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', ) ws2 = [] - + for ww in ws: t = ww.duration delta = datetime.timedelta(hours=t.hour, minutes=t.minute, seconds=t.second) @@ -1146,7 +1146,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', rscore,normp = workout_rscore(w) trimp,hrtss = workout_trimp(w) - + isbreakthrough = False ishard = False if workouttype == 'water': @@ -1184,7 +1184,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', r.user.first_name, r.user.last_name, btvalues=btvalues.to_json()) - + # submit email task to send email about breakthrough workout if ishard: if r.getemailnotifications and not r.emailbounced: @@ -1194,7 +1194,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', r.user.first_name, r.user.last_name, btvalues=btvalues.to_json()) - + return (w.id, message) @@ -1266,7 +1266,7 @@ def handle_nonpainsled(f2, fileformat, summary=''): if not hasrecognized: return (0,'',0,0,'') - + f_to_be_deleted = f2 # should delete file f2 = f2[:-4] + 'o.csv' @@ -1322,7 +1322,7 @@ def new_workout_from_file(r, f2, f3 = f3[6:] a = MessageAttachment(message=msg,document=f3) a.save() - + return -1, message, f2 # Some people try to upload Concept2 logbook summaries @@ -1340,7 +1340,7 @@ def new_workout_from_file(r, f2, os.remove(f2) message = "KML files are not supported" return (0, message, f2) - + # Some people upload corrupted zip files if fileformat == 'notgzip': os.remove(f2) @@ -1371,7 +1371,7 @@ def new_workout_from_file(r, f2, handle_sendemail_unrecognized, f4, r.user.email) - + return (0, message, f2) if fileformat == 'att': # email attachment which can safely be ignored @@ -1393,7 +1393,7 @@ def new_workout_from_file(r, f2, if workoutsource is None: workoutsource = fileformat - + id, message = save_workout_database( f2, r, notes=notes, @@ -1657,7 +1657,7 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, else: row = Workout.objects.get(id=id) - + if not data.empty and data['efficiency'].mean() == 0 and data['power'].mean() != 0 and checkefficiency == True: data = add_efficiency(id=id) @@ -1671,7 +1671,11 @@ def getrowdata_db(id=0, doclean=False, convertnewtons=True, def getsmallrowdata_db(columns, ids=[], doclean=True,workstrokesonly=True,compute=True): # prepmultipledata(ids) - csvfilenames = ['media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] + if ids: + csvfilenames = ['media/strokedata_{id}.parquet.gz'.format(id=id) for id in ids] + else: + return pd.DataFrame() + data = [] columns = [c for c in columns if c != 'None'] columns = list(set(columns)) @@ -1692,10 +1696,10 @@ def getsmallrowdata_db(columns, ids=[], doclean=True,workstrokesonly=True,comput df = pd.concat(data,axis=0) # df = dd.concat(data,axis=0) - + else: try: - df = pd.read_parquet(csvfilenames[0],columns=columns) + df = pd.read_parquet(csvfilenames[0],columns=columns) except OSError: rowdata,row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): @@ -1707,7 +1711,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True,workstrokesonly=True,comput # df = df.loc[:,~df.columns.duplicated()] - + if compute: data = df.copy() @@ -1717,7 +1721,7 @@ def getsmallrowdata_db(columns, ids=[], doclean=True,workstrokesonly=True,comput data.dropna(axis=1,how='all',inplace=True) data.dropna(axis=0,how='any',inplace=True) return data - + return df def getsmallrowdata_db_dask(columns, ids=[], doclean=True,workstrokesonly=True,compute=True): @@ -1744,10 +1748,10 @@ def getsmallrowdata_db_dask(columns, ids=[], doclean=True,workstrokesonly=True,c df = dd.concat(data,axis=0) # df = dd.concat(data,axis=0) - + else: try: - df = dd.read_parquet(csvfilenames[0],columns=columns) + df = dd.read_parquet(csvfilenames[0],columns=columns) except OSError: rowdata,row = getrowdata(id=ids[0]) if rowdata and len(rowdata.df): @@ -1759,7 +1763,7 @@ def getsmallrowdata_db_dask(columns, ids=[], doclean=True,workstrokesonly=True,c # df = df.loc[:,~df.columns.duplicated()] - + if compute: data = df.compute() @@ -1769,7 +1773,7 @@ def getsmallrowdata_db_dask(columns, ids=[], doclean=True,workstrokesonly=True,c data.dropna(axis=1,how='all',inplace=True) data.dropna(axis=0,how='any',inplace=True) return data - + return df def getsmallrowdata_db_old(columns, ids=[], doclean=True, workstrokesonly=True): @@ -1787,7 +1791,7 @@ def getsmallrowdata_db_old(columns, ids=[], doclean=True, workstrokesonly=True): f = row.df['TimeStamp (sec)'].diff().mean() except (AttributeError,KeyError) as e: f = 0 - + if f != 0 and not np.isnan(f): windowsize = 2 * (int(10. / (f))) + 1 else: @@ -1808,7 +1812,7 @@ def getsmallrowdata_db_old(columns, ids=[], doclean=True, workstrokesonly=True): except (KeyError, AttributeError): data[c] = 0 - + # convert newtons if doclean: @@ -1907,7 +1911,7 @@ def read_cols_df_sql(ids, columns, convertnewtons=True): data.append(df) df = pd.concat(data,axis=0) - + df = df.fillna(value=0) @@ -2215,9 +2219,9 @@ def add_efficiency(id=0): rowdata = remove_invalid_columns(rowdata) rowdata = rowdata.replace([-np.inf, np.inf], np.nan) rowdata = rowdata.fillna(method='ffill') - + delete_strokedata(id) - + if id != 0: rowdata['workoutid'] = id filename = 'media/strokedata_{id}.parquet.gz'.format(id=id) @@ -2250,7 +2254,7 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, velo = rowdatadf.loc[:,' AverageBoatSpeed (m/s)'] except KeyError: velo = 500./p - + hr = rowdatadf.loc[:, ' HRCur (bpm)'] spm = rowdatadf.loc[:, ' Cadence (stokes/min)'] cumdist = rowdatadf.loc[:, 'cum_dist'] @@ -2316,7 +2320,7 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, powerhr = 60.*power/hr powerhr = powerhr.fillna(value=0) - + if driveenergy.mean() == 0 and driveenergy.std() == 0: driveenergy = 0*driveenergy+100 @@ -2521,7 +2525,7 @@ def workout_trimp(w): if w.trimp > 0: return w.trimp,w.hrtss - + r = w.user ftp = float(r.ftp) if w.workouttype in otwtypes: @@ -2557,13 +2561,13 @@ def workout_trimp(w): r.hrftp, r.max, r.rest) - + return 0,0 def workout_rscore(w): if w.rscore > 0: return w.rscore,w.normp - + r = w.user ftp = float(r.ftp) if w.workouttype in otwtypes: @@ -2574,7 +2578,7 @@ def workout_rscore(w): r.hrftp = int(hrftp) r.save() - + job = myqueue( queuehigh, @@ -2586,7 +2590,7 @@ def workout_rscore(w): r.hrftp, r.max, r.rest) - + return 0,0 def workout_normv(w,pp=4.0): @@ -2603,7 +2607,7 @@ def workout_normv(w,pp=4.0): r.hrftp = int(hrftp) r.save() - + job = myqueue( queuehigh, @@ -2615,7 +2619,5 @@ def workout_normv(w,pp=4.0): r.hrftp, r.max, r.rest) - - return 0,0 - + return 0,0 diff --git a/rowers/models.py b/rowers/models.py index defc99f8..5eb8ffa8 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -6,7 +6,6 @@ from __future__ import unicode_literals, absolute_import from django.utils.encoding import python_2_unicode_compatible -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 @@ -16,7 +15,6 @@ from django.dispatch import receiver from django.forms.widgets import SplitDateTimeWidget,SelectDateWidget #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 @@ -103,16 +101,16 @@ class TemplateListField(models.TextField): 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)) @@ -121,7 +119,7 @@ class TemplateListField(models.TextField): 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 @@ -146,7 +144,7 @@ class PowerZonesField(models.TextField): 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): @@ -155,7 +153,7 @@ class PowerZonesField(models.TextField): 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)) @@ -167,7 +165,7 @@ class PowerZonesField(models.TextField): 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'}) @@ -201,7 +199,7 @@ def update_records(url=c2url,verbose=True): weightcategory = row.Weight.lower() except AttributeError: weightcategory = 'hwt' - + sex = row.Gender name = row.Name age = int(row.Age) @@ -232,7 +230,7 @@ def update_records(url=c2url,verbose=True): else: pass - + class CalcAgePerformance(models.Model): weightcategories = ( @@ -266,7 +264,7 @@ class PowerTimeFitnessMetric(models.Model): ('rower','Rower'), ('water','On the water') ) - + date = models.DateField(default=current_day) last_workout = models.IntegerField(default=0) user = models.ForeignKey(User,on_delete=models.CASCADE) @@ -277,7 +275,7 @@ class PowerTimeFitnessMetric(models.Model): max_length=41,) class Meta: db_table = 'powertimefitnessmetric' - + @python_2_unicode_compatible class C2WorldClassAgePerformance(models.Model): weightcategories = ( @@ -308,7 +306,7 @@ class C2WorldClassAgePerformance(models.Model): class Meta: unique_together = ('age','sex','weightcategory','distance') - + def __str__(self): thestring = '{s} {w} {n} age {a} ({season}) {distance}m {duration} seconds'.format( s = self.sex, @@ -327,7 +325,7 @@ def is_not_basic(user): raise ValidationError( "Basic user cannot be team manager" ) - + # For future Team functionality @python_2_unicode_compatible @@ -341,7 +339,7 @@ class Team(models.Model): ('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,on_delete=models.CASCADE) # validators=[is_not_basic]) @@ -368,9 +366,9 @@ class Team(models.Model): raise ValidationError( "Pro and Self-Coach users cannot have more than one team" ) - + super(Team, self).save(*args,**kwargs) - + class TeamForm(ModelForm): class Meta: @@ -394,7 +392,7 @@ class TeamInviteForm(ModelForm): model = TeamInvite fields = ['user','email'] - + class TeamRequest(models.Model): @@ -416,11 +414,11 @@ def polygon_coord_center(polygon): 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 = [] @@ -436,7 +434,7 @@ 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]) @@ -444,7 +442,7 @@ def course_spline(coordinates): 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) @@ -459,7 +457,7 @@ def course_spline(coordinates): }) return newcoordinates - + def course_coord_center(course): polygons = GeoPolygon.objects.filter(course=course).order_by("order_in_course") @@ -475,13 +473,13 @@ def course_coord_center(course): 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): @@ -516,14 +514,14 @@ def get_delta(vector,polygon): 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) @@ -533,7 +531,7 @@ def get_delta(vector,polygon): 'lon':lon, 'dist':dist, }) - + df['inpolygon'] = df.apply(f,axis=1) @@ -545,8 +543,8 @@ def get_delta(vector,polygon): 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]) @@ -568,7 +566,7 @@ def course_length(course): 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]) @@ -584,7 +582,7 @@ def course_length(course): polygons = polygons.reverse() vector = get_dir_vector(polygons[0],polygons[1]) deltafinish = get_delta(vector,polygons[0]) - + return int(totaldist-deltastart-deltafinish) sexcategories = ( @@ -657,7 +655,7 @@ class CoachingGroup(models.Model): def __len__(self): rs = Rower.objects.filter(coachinggroups__in=[self]) return len(rs) - + # Extension of User with rowing specific data @python_2_unicode_compatible class Rower(models.Model): @@ -704,7 +702,7 @@ class Rower(models.Model): 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( @@ -718,14 +716,14 @@ class Rower(models.Model): default='braintree') paidplan = models.ForeignKey(PaidPlan,null=True,default=None,on_delete=models.SET_NULL) - + 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) @@ -765,15 +763,15 @@ class Rower(models.Model): 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") @@ -781,14 +779,14 @@ class Rower(models.Model): 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) @@ -855,7 +853,7 @@ class Rower(models.Model): 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,on_delete=models.SET_NULL) @@ -873,10 +871,10 @@ class Rower(models.Model): # 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 clean_email(self): return self.user.email.lower() @@ -891,9 +889,9 @@ class Rower(models.Model): pass except ValueError: pass - + super(Rower, self).save(*args, **kwargs) - + class DeactivateUserForm(forms.ModelForm): class Meta: model = User @@ -902,7 +900,7 @@ class DeactivateUserForm(forms.ModelForm): class DeleteUserForm(forms.ModelForm): delete_user = forms.BooleanField(initial=False, label='Remove my account and all data') - + class Meta: model = User fields = [] @@ -922,7 +920,7 @@ class CoachOffer(models.Model): 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) @@ -937,8 +935,8 @@ def check_teams_on_change(sender, **kwargs): ) 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': @@ -954,7 +952,7 @@ 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 = [ @@ -965,12 +963,12 @@ class FavoriteChart(models.Model): 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') @@ -985,7 +983,7 @@ class FavoriteChart(models.Model): notes = models.CharField(max_length=300,verbose_name='Chart Notes', default='Flex Chart Notes',blank=True) user = models.ForeignKey(Rower,on_delete=models.CASCADE) - + class FavoriteForm(ModelForm): class Meta: @@ -1010,7 +1008,7 @@ class BaseFavoriteFormSet(BaseFormSet): 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.', @@ -1027,7 +1025,7 @@ class BaseFavoriteFormSet(BaseFormSet): yparam2 = 'None' - + class Condition(models.Model): conditionchoices = ( ('<','<'), @@ -1065,15 +1063,15 @@ class BaseConditionFormSet(BaseFormSet): value1 = form.cleaned_data['value1'] value2 = form.cleaned_data['value2'] - - + + rowchoices = [] for key,value in mytypes.workouttypes: if key in mytypes.rowtypes: rowchoices.append((key,value)) - + class Alert(models.Model): name = models.CharField(max_length=150,verbose_name='Alert Name',null=True,blank=True) manager = models.ForeignKey(User, on_delete=models.CASCADE) @@ -1089,7 +1087,7 @@ class Alert(models.Model): verbose_name='Exercise/Boat Class',default='water') boattype = models.CharField(choices=mytypes.boattypes,max_length=50, verbose_name='Boat Type',default='1x') - + def __str__(self): metricdict = {key:value for (key,value) in parchoicesy1} @@ -1110,7 +1108,7 @@ class Alert(models.Model): def description(self): metricdict = {key:value for (key,value) in parchoicesy1} - + if self.measured.condition == 'between': description = 'This alert measures strokes where {metric} is between {value1} and {value2}.'.format( metric = metricdict[self.measured.metric], @@ -1129,10 +1127,10 @@ class Alert(models.Model): ) return description - + def shortdescription(self): metricdict = {key:value for (key,value) in parchoicesy1} - + if self.measured.condition == 'between': description = '{value1} < {metric} < {value2}'.format( metric = self.measured.metric, @@ -1147,7 +1145,7 @@ class Alert(models.Model): ) return description - + class AlertEditForm(ModelForm): class Meta: @@ -1156,13 +1154,13 @@ class AlertEditForm(ModelForm): widgets = { 'reststrokes':forms.CheckboxInput() } - + 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: @@ -1214,8 +1212,8 @@ def checkviewworkouts(user,rower): if rower in Rower.objects.filter(coachinggroups__in=[user.rower.mycoachgroup]): return True - - + + except Rower.DoesNotExist: return False @@ -1234,7 +1232,7 @@ def checkaccessplanuser(user,rower): return False except Rower.DoesNotExist: return False - + # Check if user is coach or rower def checkaccessuser(user,rower): try: @@ -1252,7 +1250,7 @@ def checkaccessuser(user,rower): return False except Rower.DoesNotExist: return False - + timezones = ( (x,x) for x in pytz.common_timezones ) @@ -1274,7 +1272,7 @@ class GeoCourse(models.Model): self.distance = course_length(self) self.save() d = self.distance - + return u'{country} - {name} - {d}m'.format( name=name, country=country, @@ -1289,7 +1287,7 @@ class GeoCourseEditForm(ModelForm): widgets = { 'notes': forms.Textarea, } - + @python_2_unicode_compatible class GeoPolygon(models.Model): name = models.CharField(max_length=150,blank=True) @@ -1299,16 +1297,16 @@ class GeoPolygon(models.Model): def __str__(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) @@ -1372,7 +1370,7 @@ class TrainingTargetForm(ModelForm): teams = Team.objects.filter(manager=user) else: teams = [] - + if not teams: self.fields.pop('rowers') else: @@ -1420,13 +1418,13 @@ class TrainingPlan(models.Model): def save(self, *args, **kwargs): manager = self.manager - + if manager.rowerplan in ['basic','pro']: if manager.plantrialexpires < timezone.now().date(): raise ValidationError( "Basic user cannot have a training plan" ) - + if self.enddate < self.startdate: startdate = self.startdate enddate = self.enddate @@ -1457,7 +1455,7 @@ class TrainingPlan(models.Model): - + macrocycles = TrainingMacroCycle.objects.filter(plan = self) if not macrocycles: m = TrainingMacroCycle( @@ -1513,7 +1511,7 @@ class TrainingPlanForm(ModelForm): 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') @@ -1540,7 +1538,7 @@ def createmacrofillers(plan): name='Filler' ) macr.save() - + thedate = plan.enddate while cycles: if cycles[0].enddate < thedate: @@ -1554,7 +1552,7 @@ def createmacrofillers(plan): macr.save() thedate = cycles[0].startdate-datetime.timedelta(days=1) cycles = cycles[1:] - + cycles = TrainingMacroCycle.objects.filter( plan = plan ).order_by("startdate") @@ -1568,7 +1566,7 @@ def createmacrofillers(plan): name='Filler' ) macr.save() - + def createmesofillers(plan): fillers = TrainingMesoCycle.objects.filter( plan = plan, type = 'filler' @@ -1590,7 +1588,7 @@ def createmesofillers(plan): name='Filler' ) macr.save() - + thedate = plan.enddate while cycles: if cycles[0].enddate < thedate: @@ -1618,7 +1616,7 @@ def createmesofillers(plan): name='Filler' ) macr.save() - + def createmicrofillers(plan): fillers = TrainingMicroCycle.objects.filter( @@ -1641,7 +1639,7 @@ def createmicrofillers(plan): name='Filler' ) macr.save() - + thedate = plan.enddate while cycles: if cycles[0].enddate < thedate: @@ -1660,7 +1658,7 @@ def createmicrofillers(plan): plan = plan ).order_by("startdate") - + if cycles and cycles[0].startdate > plan.startdate: macr = TrainingMicroCycle( plan=plan, @@ -1810,27 +1808,27 @@ class TrainingMacroCycle(models.Model): 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( @@ -1845,7 +1843,7 @@ class TrainingMacroCycle(models.Model): createmesofillers(self) - + class TrainingMacroCycleForm(ModelForm): class Meta: model = TrainingMacroCycle @@ -1905,14 +1903,14 @@ class TrainingMesoCycle(models.Model): 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) @@ -1955,9 +1953,9 @@ class TrainingMicroCycle(models.Model): actualrscore = models.IntegerField(default=0,verbose_name='Actual rScore') actualtrimp = models.IntegerField(default=0,verbose_name='Actual TRIMP') - - - + + + def __str__(self): stri = 'Micro Cycle - {n} ({sd} - {ed})'.format( n = self.name, @@ -1978,28 +1976,28 @@ class TrainingMicroCycle(models.Model): 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( + + 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 @@ -2027,7 +2025,7 @@ regularsessiontypechoices = ( ('cycletarget','Total for a time period'), ('coursetest','OTW test over a course'), ) - + # model for Planned Session (Workout, Challenge, Test) @python_2_unicode_compatible class PlannedSession(models.Model): @@ -2049,7 +2047,7 @@ class PlannedSession(models.Model): ('cycletarget','Total for a time period'), ('coursetest','OTW test over a course'), ) - + sessionmodechoices = ( ('distance','Distance'), ('time','Time'), @@ -2074,7 +2072,7 @@ class PlannedSession(models.Model): ('m','meters'), ('None',None), ) - + manager = models.ForeignKey(User,on_delete=models.PROTECT) course = models.ForeignKey(GeoCourse,blank=True,null=True, verbose_name='OTW Course',on_delete=models.SET_NULL) @@ -2093,7 +2091,7 @@ class PlannedSession(models.Model): preferreddate = models.DateField(default=a_week_from_now, verbose_name='Preferred Date') - + sessiontype = models.CharField(default='session', choices=sessiontypechoices, max_length=150, @@ -2145,7 +2143,7 @@ class PlannedSession(models.Model): e = enddate.strftime('%Y-%m-%d'), n = name, ) - + return stri def save(self, *args, **kwargs): @@ -2159,8 +2157,8 @@ class PlannedSession(models.Model): 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']: @@ -2191,12 +2189,12 @@ class PlannedSession(models.Model): 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 @@ -2234,9 +2232,9 @@ class VirtualRace(PlannedSession): 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_phone = models.CharField(validators=[phone_regex], max_length=17, blank=True) + contact_email = models.EmailField(max_length=254, validators=[validate_email],blank=True) @@ -2249,7 +2247,7 @@ class VirtualRace(PlannedSession): stri = u'Virtual Race {n}'.format( n = name, ) - + return stri @@ -2277,12 +2275,12 @@ class VirtualRace(PlannedSession): 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() @@ -2298,13 +2296,13 @@ class RaceLogo(models.Model): 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', @@ -2324,14 +2322,14 @@ class PlannedSessionForm(ModelForm): '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") @@ -2363,7 +2361,7 @@ class IndoorVirtualRaceForm(ModelForm): timezone = forms.ChoiceField(initial='UTC', choices=[(x,x) for x in pytz.common_timezones], label='Time Zone') - + class Meta: model = VirtualRace fields = [ @@ -2424,14 +2422,14 @@ class IndoorVirtualRaceForm(ModelForm): 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( @@ -2444,7 +2442,7 @@ class IndoorVirtualRaceForm(ModelForm): '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( @@ -2491,17 +2489,17 @@ class IndoorVirtualRaceForm(ModelForm): 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 = [ @@ -2524,7 +2522,7 @@ class VirtualRaceForm(ModelForm): 'format': 'yyyy-mm-dd', 'autoclose': True, } - + widgets = { 'comment': forms.Textarea, 'startdate': AdminDateWidget(), @@ -2534,11 +2532,11 @@ class VirtualRaceForm(ModelForm): '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 @@ -2552,7 +2550,7 @@ class VirtualRaceForm(ModelForm): '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( @@ -2565,7 +2563,7 @@ class VirtualRaceForm(ModelForm): '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( @@ -2612,7 +2610,7 @@ class VirtualRaceForm(ModelForm): if cd['evaluation_closure'] <= timezone.now(): raise forms.ValidationError("Evaluation closure cannot be in the past") - + return cd @@ -2624,7 +2622,7 @@ class PlannedSessionFormSmall(ModelForm): ('cycletarget','Total for a time period'), ('coursetest','OTW test over a course'), ) - + class Meta: model = PlannedSession fields = ['startdate', @@ -2645,7 +2643,7 @@ class PlannedSessionFormSmall(ModelForm): } input_formats=('%Y-%m-%d') - + widgets = { 'startdate': DateInput(attrs={'size':10, 'class':'datepicker'}, format='%Y-%m-%d'), 'enddate': DateInput(attrs={'size':10, 'class':'datepicker'}, format='%Y-%m-%d'), @@ -2661,14 +2659,14 @@ class PlannedSessionFormSmall(ModelForm): 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,on_delete=models.CASCADE) team = models.ManyToManyField(Team,blank=True) plannedsession = models.ForeignKey(PlannedSession, blank=True,null=True, @@ -2720,12 +2718,12 @@ class Workout(models.Model): ('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, @@ -2739,7 +2737,7 @@ class Workout(models.Model): raise forms.ValidationError("Free Coach User cannot have any workouts") super(Workout, self).save(*args, **kwargs) - + def __str__(self): date = self.date @@ -2772,9 +2770,9 @@ class Workout(models.Model): ownerfirst = ownerfirst, ownerlast = ownerlast, ) - + return stri - + class TombStone(models.Model): user = models.ForeignKey(Rower,on_delete=models.CASCADE) uploadedtoc2 = models.IntegerField(default=0) @@ -2795,7 +2793,7 @@ def create_tombstone_on_delete(sender, instance, **kwargs): uploadedtorunkeeper = instance.uploadedtorunkeeper, ) t.save() - + # delete files belonging to workout instance # related GraphImage objects should be deleted automatically @receiver(models.signals.post_delete,sender=Workout) @@ -2847,8 +2845,8 @@ def update_duplicates_on_delete(sender, instance, **kwargs): 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): @@ -2987,7 +2985,7 @@ class CourseTestResult(models.Model): 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 @@ -2997,7 +2995,7 @@ class IndoorVirtualRaceResultForm(ModelForm): def __init__(self, *args, **kwargs): super(IndoorVirtualRaceResultForm, self).__init__(*args, **kwargs) - + class VirtualRaceResultForm(ModelForm): class Meta: model = VirtualRaceResult @@ -3015,7 +3013,7 @@ class VirtualRaceResultForm(ModelForm): self.fields['mix'] = forms.BooleanField(initial=False, required=False, label='Mixed Gender') - + from rowers.metrics import rowingmetrics strokedatafields = { @@ -3067,7 +3065,7 @@ class Meta: attrs = {'__module__': 'rowers.models', '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. @@ -3101,7 +3099,7 @@ class cpergdata(models.Model): app_label = 'rowers' - + # Storing data for the OTW CP chart class ergcpdata(models.Model): delta = models.IntegerField(default=0) @@ -3126,7 +3124,7 @@ class GraphImage(models.Model): 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): @@ -3141,7 +3139,7 @@ def auto_delete_image_on_delete(sender,instance, **kwargs): - + # Form to update Workout data class WorkoutForm(ModelForm): # duration = forms.TimeInput(format='%H:%M:%S.%f') @@ -3167,17 +3165,17 @@ class WorkoutForm(ModelForm): 'notes': forms.Textarea, 'duration': forms.TimeInput(format='%H:%M:%S.%f'), } - - def __init__(self, *args, **kwargs): + + 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 @@ -3191,18 +3189,18 @@ class WorkoutForm(ModelForm): 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'] @@ -3222,7 +3220,7 @@ class RowerExportForm(ModelForm): 'strava_auto_import', 'trainingpeaks_auto_export', ] - + # Simple form to set rower's Functional Threshold Power class RowerPowerForm(ModelForm): class Meta: @@ -3232,7 +3230,7 @@ class RowerPowerForm(ModelForm): # 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]) @@ -3240,10 +3238,10 @@ class RowerPowerZonesForm(ModelForm): 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: @@ -3416,7 +3414,7 @@ class RowerBillingAddressForm(ModelForm): 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: @@ -3440,8 +3438,8 @@ class AccountRowerForm(ModelForm): if 'coach' not in self.instance.rowerplan: self.fields.pop('offercoaching') - - + + class UserForm(ModelForm): class Meta: @@ -3455,7 +3453,7 @@ class UserForm(ModelForm): return first_name raise forms.ValidationError('Please fill in your first name') - + def clean_email(self): email = self.cleaned_data.get('email') @@ -3464,7 +3462,7 @@ class UserForm(ModelForm): except ValidationError: raise forms.ValidationError( 'Please enter a valid email address') - + try: match = User.objects.filter(email__iexact=email) if self.instance in match: @@ -3474,7 +3472,7 @@ class UserForm(ModelForm): 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): @@ -3489,7 +3487,7 @@ class RowerForm(ModelForm): 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 @@ -3567,7 +3565,7 @@ class RowerForm(ModelForm): def clean(self): - + try: rest = self.cleaned_data['rest'] except: @@ -3623,7 +3621,7 @@ class RowerForm(ModelForm): 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: @@ -3639,7 +3637,7 @@ class RowerForm(ModelForm): 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): @@ -3680,8 +3678,8 @@ class WorkoutComment(models.Model): u1 = self.user.first_name, u2 = self.user.last_name, ) - - + + class WorkoutCommentForm(ModelForm): class Meta: model = WorkoutComment @@ -3706,8 +3704,8 @@ class PlannedSessionComment(models.Model): u1 = self.user.first_name, u2 = self.user.last_name, ) - - + + class PlannedSessionCommentForm(ModelForm): class Meta: model = PlannedSessionComment @@ -3716,7 +3714,7 @@ class PlannedSessionCommentForm(ModelForm): 'comment': forms.Textarea, } - + class BlogPost(models.Model): title = models.TextField(max_length=300) link = models.TextField(max_length=300)