Private
Public Access
1
0

Merge branch 'release/v4.5'

This commit is contained in:
Sander Roosendaal
2017-10-24 15:05:57 +02:00
36 changed files with 1443 additions and 1280 deletions
+70 -69
View File
@@ -61,7 +61,6 @@ queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('default') queuehigh = django_rq.get_queue('default')
user = settings.DATABASES['default']['USER'] user = settings.DATABASES['default']['USER']
password = settings.DATABASES['default']['PASSWORD'] password = settings.DATABASES['default']['PASSWORD']
database_name = settings.DATABASES['default']['NAME'] database_name = settings.DATABASES['default']['NAME']
@@ -106,6 +105,7 @@ from scipy.signal import savgol_filter
import datetime import datetime
def get_latlon(id): def get_latlon(id):
try: try:
w = Workout.objects.get(id=id) w = Workout.objects.get(id=id)
@@ -126,6 +126,7 @@ def get_latlon(id):
return [pd.Series([]), pd.Series([])] return [pd.Series([]), pd.Series([])]
def get_workouts(ids, userid): def get_workouts(ids, userid):
goodids = [] goodids = []
for id in ids: for id in ids:
@@ -135,6 +136,7 @@ def get_workouts(ids,userid):
return [Workout.objects.get(id=id) for id in goodids] return [Workout.objects.get(id=id) for id in goodids]
def filter_df(datadf, fieldname, value, largerthan=True): def filter_df(datadf, fieldname, value, largerthan=True):
try: try:
@@ -142,7 +144,6 @@ def filter_df(datadf,fieldname,value,largerthan=True):
except KeyError: except KeyError:
return datadf return datadf
if largerthan: if largerthan:
mask = datadf[fieldname] < value mask = datadf[fieldname] < value
else: else:
@@ -150,7 +151,6 @@ def filter_df(datadf,fieldname,value,largerthan=True):
datadf.loc[mask, fieldname] = np.nan datadf.loc[mask, fieldname] = np.nan
return datadf return datadf
@@ -182,6 +182,7 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True,
except KeyError: except KeyError:
pass pass
try: try:
datadf = datadf.clip(lower=0) datadf = datadf.clip(lower=0)
except TypeError: except TypeError:
@@ -220,7 +221,6 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True,
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['pace'] / 1000. > 300. mask = datadf['pace'] / 1000. > 300.
datadf.loc[mask, 'pace'] = np.nan datadf.loc[mask, 'pace'] = np.nan
@@ -245,14 +245,12 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True,
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['wash'] < 1 mask = datadf['wash'] < 1
datadf.loc[mask, 'wash'] = np.nan datadf.loc[mask, 'wash'] = np.nan
except KeyError: except KeyError:
pass pass
if not ignoreadvanced: if not ignoreadvanced:
try: try:
mask = datadf['rhythm'] < 5 mask = datadf['rhythm'] < 5
@@ -260,79 +258,66 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True,
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['rhythm'] > 70 mask = datadf['rhythm'] > 70
datadf.loc[mask, 'rhythm'] = np.nan datadf.loc[mask, 'rhythm'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['power'] < 20 mask = datadf['power'] < 20
datadf.loc[mask, 'power'] = np.nan datadf.loc[mask, 'power'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['drivelength'] < 0.5 mask = datadf['drivelength'] < 0.5
datadf.loc[mask, 'drivelength'] = np.nan datadf.loc[mask, 'drivelength'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['forceratio'] < 0.2 mask = datadf['forceratio'] < 0.2
datadf.loc[mask, 'forceratio'] = np.nan datadf.loc[mask, 'forceratio'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['forceratio'] > 1.0 mask = datadf['forceratio'] > 1.0
datadf.loc[mask, 'forceratio'] = np.nan datadf.loc[mask, 'forceratio'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['drivespeed'] < 0.5 mask = datadf['drivespeed'] < 0.5
datadf.loc[mask, 'drivespeed'] = np.nan datadf.loc[mask, 'drivespeed'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['drivespeed'] > 4 mask = datadf['drivespeed'] > 4
datadf.loc[mask, 'drivespeed'] = np.nan datadf.loc[mask, 'drivespeed'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['driveenergy'] > 2000 mask = datadf['driveenergy'] > 2000
datadf.loc[mask, 'driveenergy'] = np.nan datadf.loc[mask, 'driveenergy'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['driveenergy'] < 100 mask = datadf['driveenergy'] < 100
datadf.loc[mask, 'driveenergy'] = np.nan datadf.loc[mask, 'driveenergy'] = np.nan
except KeyError: except KeyError:
pass pass
try: try:
mask = datadf['catch'] > -30. mask = datadf['catch'] > -30.
datadf.loc[mask, 'catch'] = np.nan datadf.loc[mask, 'catch'] = np.nan
except KeyError: except KeyError:
pass pass
workoutstateswork = [1, 4, 5, 8, 9, 6, 7] workoutstateswork = [1, 4, 5, 8, 9, 6, 7]
workoutstatesrest = [3] workoutstatesrest = [3]
workoutstatetransition = [0, 2, 10, 11, 12, 13] workoutstatetransition = [0, 2, 10, 11, 12, 13]
@@ -345,6 +330,7 @@ def clean_df_stats(datadf,workstrokesonly=True,ignorehr=True,
return datadf return datadf
def getstatsfields(): def getstatsfields():
# Get field names and remove those that are not useful in stats # Get field names and remove those that are not useful in stats
fields = StrokeData._meta.get_fields() fields = StrokeData._meta.get_fields()
@@ -389,6 +375,8 @@ def niceformat(values):
return out return out
# A nice printable format for time delta values # A nice printable format for time delta values
def strfdelta(tdelta): def strfdelta(tdelta):
try: try:
minutes, seconds = divmod(tdelta.seconds, 60) minutes, seconds = divmod(tdelta.seconds, 60)
@@ -406,25 +394,28 @@ def strfdelta(tdelta):
return res return res
# A nice printable format for pace values # A nice printable format for pace values
def nicepaceformat(values): def nicepaceformat(values):
out = [] out = []
for v in values: for v in values:
formattedv = strfdelta(v) formattedv = strfdelta(v)
out.append(formattedv) out.append(formattedv)
return out return out
# Convert seconds to a Time Delta value, replacing NaN with a 5:50 pace # Convert seconds to a Time Delta value, replacing NaN with a 5:50 pace
def timedeltaconv(x): def timedeltaconv(x):
if np.isfinite(x) and x != 0 and x > 0 and x < 175000: if np.isfinite(x) and x != 0 and x > 0 and x < 175000:
dt = datetime.timedelta(seconds=x) dt = datetime.timedelta(seconds=x)
else: else:
dt = datetime.timedelta(seconds=350.) dt = datetime.timedelta(seconds=350.)
return dt return dt
def paceformatsecs(values): def paceformatsecs(values):
out = [] out = []
for v in values: for v in values:
@@ -435,6 +426,8 @@ def paceformatsecs(values):
return out return out
# Processes painsled CSV file to database # Processes painsled CSV file to database
def save_workout_database(f2, r, dosmooth=True, workouttype='rower', def save_workout_database(f2, r, dosmooth=True, workouttype='rower',
dosummary=True, title='Workout', dosummary=True, title='Workout',
workoutsource='unknown', workoutsource='unknown',
@@ -474,7 +467,8 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
if not value: if not value:
allchecks = 0 allchecks = 0
if consistencychecks: if consistencychecks:
a_messages.error(r.user,'Failed consistency check: '+key+', autocorrected') a_messages.error(
r.user, 'Failed consistency check: ' + key + ', autocorrected')
else: else:
pass pass
# a_messages.error(r.user,'Failed consistency check: '+key+', not corrected') # a_messages.error(r.user,'Failed consistency check: '+key+', not corrected')
@@ -485,7 +479,6 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
# row.repair() # row.repair()
pass pass
if row == 0: if row == 0:
return (0, 'Error: CSV data file not found') return (0, 'Error: CSV data file not found')
@@ -537,7 +530,8 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
if totaldist == 0: if totaldist == 0:
totaldist = row.df['cum_dist'].max() totaldist = row.df['cum_dist'].max()
if totaltime == 0: if totaltime == 0:
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min() totaltime = row.df['TimeStamp (sec)'].max(
) - row.df['TimeStamp (sec)'].min()
try: try:
totaltime = totaltime + row.df.ix[0, ' ElapsedTime (sec)'] totaltime = totaltime + row.df.ix[0, ' ElapsedTime (sec)']
except KeyError: except KeyError:
@@ -577,7 +571,6 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
#summary += '\n' #summary += '\n'
#summary += row.intervalstats() #summary += row.intervalstats()
#workoutstartdatetime = row.rowdatetime #workoutstartdatetime = row.rowdatetime
timezone_str = 'UTC' timezone_str = 'UTC'
try: try:
@@ -585,8 +578,6 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
except ValueError: except ValueError:
workoutstartdatetime = row.rowdatetime workoutstartdatetime = row.rowdatetime
try: try:
latavg = row.df[' latitude'].mean() latavg = row.df[' latitude'].mean()
lonavg = row.df[' longitude'].mean() lonavg = row.df[' longitude'].mean()
@@ -612,8 +603,6 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
except KeyError: except KeyError:
timezone_str = r.defaulttimezone timezone_str = r.defaulttimezone
workoutdate = workoutstartdatetime.astimezone( workoutdate = workoutstartdatetime.astimezone(
pytz.timezone(timezone_str) pytz.timezone(timezone_str)
).strftime('%Y-%m-%d') ).strftime('%Y-%m-%d')
@@ -640,9 +629,6 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
message = "Warning: This workout probably already exists in the database" message = "Warning: This workout probably already exists in the database"
privacy = 'hidden' privacy = 'hidden'
w = Workout(user=r, name=title, date=workoutdate, w = Workout(user=r, name=title, date=workoutdate,
workouttype=workouttype, workouttype=workouttype,
duration=duration, distance=totaldist, duration=duration, distance=totaldist,
@@ -657,7 +643,6 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
timezone=timezone_str, timezone=timezone_str,
privacy=privacy) privacy=privacy)
w.save() w.save()
isbreakthrough = False isbreakthrough = False
@@ -672,7 +657,8 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
dfgrouped = df.groupby(['workoutid']) dfgrouped = df.groupby(['workoutid'])
delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr)
res,btvalues,res2 = utils.isbreakthrough(delta,cpvalues,r.p0,r.p1,r.p2,r.p3,r.cpratio) res, btvalues, res2 = utils.isbreakthrough(
delta, cpvalues, r.p0, r.p1, r.p2, r.p3, r.cpratio)
else: else:
res = 0 res = 0
res2 = 0 res2 = 0
@@ -684,7 +670,8 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
# submit email task to send email about breakthrough workout # submit email task to send email about breakthrough workout
if isbreakthrough: if isbreakthrough:
a_messages.info(r.user,'It looks like you have a new breakthrough workout') a_messages.info(
r.user, 'It looks like you have a new breakthrough workout')
if settings.DEBUG and r.getemailnotifications: if settings.DEBUG and r.getemailnotifications:
res = handle_sendemail_breakthrough.delay(w.id, r.user.email, res = handle_sendemail_breakthrough.delay(w.id, r.user.email,
r.user.first_name, r.user.first_name,
@@ -734,6 +721,7 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower',
return (w.id, message) return (w.id, message)
def handle_nonpainsled(f2, fileformat, summary=''): def handle_nonpainsled(f2, fileformat, summary=''):
oarlength = 2.89 oarlength = 2.89
inboard = 0.88 inboard = 0.88
@@ -756,7 +744,6 @@ def handle_nonpainsled(f2,fileformat,summary=''):
if (fileformat == 'ergdata'): if (fileformat == 'ergdata'):
row = ErgDataParser(f2) row = ErgDataParser(f2)
# handle CoxMate # handle CoxMate
if (fileformat == 'coxmate'): if (fileformat == 'coxmate'):
row = CoxMateParser(f2) row = CoxMateParser(f2)
@@ -790,7 +777,6 @@ def handle_nonpainsled(f2,fileformat,summary=''):
except: except:
pass pass
# handle ErgStick # handle ErgStick
if (fileformat == 'ergstick'): if (fileformat == 'ergstick'):
row = ErgStickParser(f2) row = ErgStickParser(f2)
@@ -805,7 +791,6 @@ def handle_nonpainsled(f2,fileformat,summary=''):
except: except:
pass pass
f_to_be_deleted = f2 f_to_be_deleted = f2
# should delete file # should delete file
f2 = f2[:-4] + 'o.csv' f2 = f2[:-4] + 'o.csv'
@@ -822,6 +807,8 @@ def handle_nonpainsled(f2,fileformat,summary=''):
# Create new workout from file and store it in the database # Create new workout from file and store it in the database
# This routine should be used everywhere in views.py and mailprocessing.py # This routine should be used everywhere in views.py and mailprocessing.py
# Currently there is code duplication # Currently there is code duplication
def new_workout_from_file(r, f2, def new_workout_from_file(r, f2,
workouttype='rower', workouttype='rower',
title='Workout', title='Workout',
@@ -873,7 +860,6 @@ def new_workout_from_file(r,f2,
message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect." message = "Rowsandall could not process this file. The extension is supported but the file seems corrupt. Contact info@rowsandall.com if you think this is incorrect."
return (0, message, f2) return (0, message, f2)
# Some people try to upload RowPro summary logs # Some people try to upload RowPro summary logs
if fileformat == 'rowprolog': if fileformat == 'rowprolog':
os.remove(f2) os.remove(f2)
@@ -906,8 +892,6 @@ def new_workout_from_file(r,f2,
message = 'Something went wrong: ' + errorstring message = 'Something went wrong: ' + errorstring
return (0, message, '') return (0, message, '')
dosummary = (fileformat != 'fit') dosummary = (fileformat != 'fit')
id, message = save_workout_database(f2, r, id, message = save_workout_database(f2, r,
workouttype=workouttype, workouttype=workouttype,
@@ -920,6 +904,7 @@ def new_workout_from_file(r,f2,
return (id, message, f2) return (id, message, f2)
def split_workout(r, parent, splitsecond, splitmode): def split_workout(r, parent, splitsecond, splitmode):
data, row = getrowdata_db(id=parent.id) data, row = getrowdata_db(id=parent.id)
latitude, longitude = get_latlon(parent.id) latitude, longitude = get_latlon(parent.id)
@@ -942,7 +927,6 @@ def split_workout(r,parent,splitsecond,splitmode):
data1['time'] = data1.index data1['time'] = data1.index
data1.reset_index(drop=True, inplace=True) data1.reset_index(drop=True, inplace=True)
data2 = data2.sort_values(['time']) data2 = data2.sort_values(['time'])
data2 = data2.interpolate(method='linear', axis=0, limit_direction='both', data2 = data2.interpolate(method='linear', axis=0, limit_direction='both',
limit=10) limit=10)
@@ -956,7 +940,6 @@ def split_workout(r,parent,splitsecond,splitmode):
data1['pace'] = data1['pace'] / 1000. data1['pace'] = data1['pace'] / 1000.
data2['pace'] = data2['pace'] / 1000. data2['pace'] = data2['pace'] / 1000.
data1.drop_duplicates(subset='time', inplace=True) data1.drop_duplicates(subset='time', inplace=True)
data2.drop_duplicates(subset='time', inplace=True) data2.drop_duplicates(subset='time', inplace=True)
@@ -995,7 +978,6 @@ def split_workout(r,parent,splitsecond,splitmode):
messages.append(message) messages.append(message)
ids.append(id) ids.append(id)
if not 'keep original' in splitmode: if not 'keep original' in splitmode:
if 'keep second' in splitmode or 'keep first' in splitmode: if 'keep second' in splitmode or 'keep first' in splitmode:
parent.delete() parent.delete()
@@ -1012,6 +994,8 @@ def split_workout(r,parent,splitsecond,splitmode):
# Create new workout from data frame and store it in the database # Create new workout from data frame and store it in the database
# This routine should be used everywhere in views.py and mailprocessing.py # This routine should be used everywhere in views.py and mailprocessing.py
# Currently there is code duplication # Currently there is code duplication
def new_workout_from_df(r, df, def new_workout_from_df(r, df,
title='New Workout', title='New Workout',
parent=None, parent=None,
@@ -1046,7 +1030,6 @@ def new_workout_from_df(r,df,
if setprivate: if setprivate:
makeprivate = True makeprivate = True
timestr = strftime("%Y%m%d-%H%M%S") timestr = strftime("%Y%m%d-%H%M%S")
csvfilename = 'media/df_' + timestr + '.csv' csvfilename = 'media/df_' + timestr + '.csv'
@@ -1079,11 +1062,9 @@ def new_workout_from_df(r,df,
dosmooth=False, dosmooth=False,
consistencychecks=False) consistencychecks=False)
return (id, message) return (id, message)
# Compare the data from the CSV file and the database # Compare the data from the CSV file and the database
# Currently only calculates number of strokes. To be expanded with # Currently only calculates number of strokes. To be expanded with
# more elaborate testing if needed # more elaborate testing if needed
@@ -1115,6 +1096,8 @@ def compare_data(id):
# Repair data for workouts where the CSV file is lost (or the DB entries # Repair data for workouts where the CSV file is lost (or the DB entries
# don't exist) # don't exist)
def repair_data(verbose=False): def repair_data(verbose=False):
ws = Workout.objects.all() ws = Workout.objects.all()
for w in ws: for w in ws:
@@ -1157,6 +1140,8 @@ def repair_data(verbose=False):
pass pass
# A wrapper around the rowingdata class, with some error catching # A wrapper around the rowingdata class, with some error catching
def rdata(file, rower=rrower()): def rdata(file, rower=rrower()):
try: try:
res = rrdata(csvfile=file, rower=rower) res = rrdata(csvfile=file, rower=rower)
@@ -1171,6 +1156,8 @@ def rdata(file,rower=rrower()):
return res return res
# Remove all stroke data for workout ID from database # Remove all stroke data for workout ID from database
def delete_strokedata(id): def delete_strokedata(id):
engine = create_engine(database_url, echo=False) engine = create_engine(database_url, echo=False)
query = sa.text('DELETE FROM strokedata WHERE workoutid={id};'.format( query = sa.text('DELETE FROM strokedata WHERE workoutid={id};'.format(
@@ -1185,11 +1172,15 @@ def delete_strokedata(id):
engine.dispose() engine.dispose()
# Replace stroke data in DB with data from CSV file # Replace stroke data in DB with data from CSV file
def update_strokedata(id, df): def update_strokedata(id, df):
delete_strokedata(id) delete_strokedata(id)
rowdata = dataprep(df, id=id, bands=True, barchart=True, otwpower=True) rowdata = dataprep(df, id=id, bands=True, barchart=True, otwpower=True)
# Test that all data are of a numerical time # Test that all data are of a numerical time
def testdata(time, distance, pace, spm): def testdata(time, distance, pace, spm):
t1 = np.issubdtype(time, np.number) t1 = np.issubdtype(time, np.number)
t2 = np.issubdtype(distance, np.number) t2 = np.issubdtype(distance, np.number)
@@ -1200,6 +1191,8 @@ def testdata(time,distance,pace,spm):
# Get data from DB for one workout (fetches all data). If data # Get data from DB for one workout (fetches all data). If data
# is not in DB, read from CSV file (and create DB entry) # is not in DB, read from CSV file (and create DB entry)
def getrowdata_db(id=0, doclean=False, convertnewtons=True): def getrowdata_db(id=0, doclean=False, convertnewtons=True):
data = read_df_sql(id) data = read_df_sql(id)
data['x_right'] = data['x_right'] / 1.0e6 data['x_right'] = data['x_right'] / 1.0e6
@@ -1207,7 +1200,8 @@ def getrowdata_db(id=0,doclean=False,convertnewtons=True):
if data.empty: if data.empty:
rowdata, row = getrowdata(id=id) rowdata, row = getrowdata(id=id)
if rowdata: if rowdata:
data = dataprep(rowdata.df,id=id,bands=True,barchart=True,otwpower=True) data = dataprep(rowdata.df, id=id, bands=True,
barchart=True, otwpower=True)
else: else:
data = pd.DataFrame() # returning empty dataframe data = pd.DataFrame() # returning empty dataframe
else: else:
@@ -1219,27 +1213,26 @@ def getrowdata_db(id=0,doclean=False,convertnewtons=True):
if doclean: if doclean:
data = clean_df_stats(data, ignorehr=True) data = clean_df_stats(data, ignorehr=True)
return data, row return data, row
# Fetch a subset of the data from the DB # Fetch a subset of the data from the DB
def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True): def getsmallrowdata_db(columns, ids=[], doclean=True, workstrokesonly=True):
prepmultipledata(ids) prepmultipledata(ids)
data = read_cols_df_sql(ids, columns) data = read_cols_df_sql(ids, columns)
# convert newtons # convert newtons
if doclean: if doclean:
data = clean_df_stats(data, ignorehr=True, data = clean_df_stats(data, ignorehr=True,
workstrokesonly=workstrokesonly) workstrokesonly=workstrokesonly)
return data return data
# Fetch both the workout and the workout stroke data (from CSV file) # Fetch both the workout and the workout stroke data (from CSV file)
def getrowdata(id=0): def getrowdata(id=0):
# check if valid ID exists (workout exists) # check if valid ID exists (workout exists)
@@ -1266,6 +1259,8 @@ def getrowdata(id=0):
# In theory, this should never yield any work, but it's a good # In theory, this should never yield any work, but it's a good
# safety net for programming errors elsewhere in the app # safety net for programming errors elsewhere in the app
# Also used heavily when I moved from CSV file only to CSV+Stroke data # Also used heavily when I moved from CSV file only to CSV+Stroke data
def prepmultipledata(ids, verbose=False): def prepmultipledata(ids, verbose=False):
query = sa.text('SELECT DISTINCT workoutid FROM strokedata') query = sa.text('SELECT DISTINCT workoutid FROM strokedata')
engine = create_engine(database_url, echo=False) engine = create_engine(database_url, echo=False)
@@ -1287,11 +1282,14 @@ def prepmultipledata(ids,verbose=False):
if verbose: if verbose:
print id print id
if rowdata and len(rowdata.df): if rowdata and len(rowdata.df):
data = dataprep(rowdata.df,id=id,bands=True,barchart=True,otwpower=True) data = dataprep(rowdata.df, id=id, bands=True,
barchart=True, otwpower=True)
return res return res
# Read a set of columns for a set of workout ids, returns data as a # Read a set of columns for a set of workout ids, returns data as a
# pandas dataframe # pandas dataframe
def read_cols_df_sql(ids, columns, convertnewtons=True): def read_cols_df_sql(ids, columns, convertnewtons=True):
# drop columns that are not in offical list # drop columns that are not in offical list
# axx = [ax[0] for ax in axes] # axx = [ax[0] for ax in axes]
@@ -1325,29 +1323,33 @@ def read_cols_df_sql(ids,columns,convertnewtons=True):
ids=tuple(ids), ids=tuple(ids),
)) ))
connection = engine.raw_connection() connection = engine.raw_connection()
df = pd.read_sql_query(query, engine) df = pd.read_sql_query(query, engine)
df = df.fillna(value=0) df = df.fillna(value=0)
if 'peakforce' in columns: if 'peakforce' in columns:
funits = ((w.id,w.forceunit) for w in Workout.objects.filter(id__in=ids)) funits = ((w.id, w.forceunit)
for w in Workout.objects.filter(id__in=ids))
for id, u in funits: for id, u in funits:
if u == 'lbs': if u == 'lbs':
mask = df['workoutid'] == id mask = df['workoutid'] == id
df.loc[mask, 'peakforce'] = df.loc[mask, 'peakforce'] * lbstoN df.loc[mask, 'peakforce'] = df.loc[mask, 'peakforce'] * lbstoN
if 'averageforce' in columns: if 'averageforce' in columns:
funits = ((w.id,w.forceunit) for w in Workout.objects.filter(id__in=ids)) funits = ((w.id, w.forceunit)
for w in Workout.objects.filter(id__in=ids))
for id, u in funits: for id, u in funits:
if u == 'lbs': if u == 'lbs':
mask = df['workoutid'] == id mask = df['workoutid'] == id
df.loc[mask,'averageforce'] = df.loc[mask,'averageforce']*lbstoN df.loc[mask, 'averageforce'] = df.loc[mask,
'averageforce'] * lbstoN
engine.dispose() engine.dispose()
return df return df
# Read stroke data from the DB for a Workout ID. Returns a pandas dataframe # Read stroke data from the DB for a Workout ID. Returns a pandas dataframe
def read_df_sql(id): def read_df_sql(id):
engine = create_engine(database_url, echo=False) engine = create_engine(database_url, echo=False)
@@ -1374,6 +1376,8 @@ def read_df_sql(id):
# Get the necessary data from the strokedata table in the DB. # Get the necessary data from the strokedata table in the DB.
# For the flex plot # For the flex plot
def smalldataprep(therows, xparam, yparam1, yparam2): def smalldataprep(therows, xparam, yparam1, yparam2):
df = pd.DataFrame() df = pd.DataFrame()
if yparam2 == 'None': if yparam2 == 'None':
@@ -1432,10 +1436,11 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
except IOError: except IOError:
pass pass
return df return df
# data fusion # data fusion
def datafusion(id1, id2, columns, offset): def datafusion(id1, id2, columns, offset):
workout1 = Workout.objects.get(id=id1) workout1 = Workout.objects.get(id=id1)
workout2 = Workout.objects.get(id=id2) workout2 = Workout.objects.get(id=id2)
@@ -1460,7 +1465,6 @@ def datafusion(id1,id2,columns,offset):
df1[' latitude'] = latitude df1[' latitude'] = latitude
df1[' longitude'] = longitude df1[' longitude'] = longitude
df2 = getsmallrowdata_db(['time'] + columns, ids=[id2], doclean=False) df2 = getsmallrowdata_db(['time'] + columns, ids=[id2], doclean=False)
forceunit = 'N' forceunit = 'N'
@@ -1469,13 +1473,11 @@ def datafusion(id1,id2,columns,offset):
offsetmillisecs += offset.days * (3600 * 24 * 1000) offsetmillisecs += offset.days * (3600 * 24 * 1000)
df2['time'] = df2['time'] + offsetmillisecs df2['time'] = df2['time'] + offsetmillisecs
keep1 = {c: c for c in set(df1.columns)} keep1 = {c: c for c in set(df1.columns)}
for c in columns: for c in columns:
keep1.pop(c) keep1.pop(c)
for c in df1.columns: for c in df1.columns:
if not c in keep1: if not c in keep1:
df1 = df1.drop(c, 1, errors='ignore') df1 = df1.drop(c, 1, errors='ignore')
@@ -1497,6 +1499,7 @@ def datafusion(id1,id2,columns,offset):
return df, forceunit return df, forceunit
def fix_newtons(id=0, limit=3000): def fix_newtons(id=0, limit=3000):
# rowdata,row = getrowdata_db(id=id,doclean=False,convertnewtons=False) # rowdata,row = getrowdata_db(id=id,doclean=False,convertnewtons=False)
rowdata = getsmallrowdata_db(['peakforce'], ids=[id], doclean=False) rowdata = getsmallrowdata_db(['peakforce'], ids=[id], doclean=False)
@@ -1512,6 +1515,7 @@ def fix_newtons(id=0,limit=3000):
except KeyError: except KeyError:
pass pass
def add_efficiency(id=0): def add_efficiency(id=0):
rowdata, row = getrowdata_db(id=id, doclean=False, convertnewtons=False) rowdata, row = getrowdata_db(id=id, doclean=False, convertnewtons=False)
power = rowdata['power'] power = rowdata['power']
@@ -1528,7 +1532,8 @@ def add_efficiency(id=0):
rowdata['workoutid'] = id rowdata['workoutid'] = id
engine = create_engine(database_url, echo=False) engine = create_engine(database_url, echo=False)
with engine.connect() as conn, conn.begin(): with engine.connect() as conn, conn.begin():
rowdata.to_sql('strokedata',engine,if_exists='append',index=False) rowdata.to_sql('strokedata', engine,
if_exists='append', index=False)
conn.close() conn.close()
engine.dispose() engine.dispose()
return rowdata return rowdata
@@ -1537,6 +1542,8 @@ def add_efficiency(id=0):
# it reindexes, sorts, filters, and smooths the data, then # it reindexes, sorts, filters, and smooths the data, then
# saves it to the stroke_data table in the database # saves it to the stroke_data table in the database
# Takes a rowingdata object's DataFrame as input # Takes a rowingdata object's DataFrame as input
def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True, def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
empower=True, inboard=0.88, forceunit='lbs'): empower=True, inboard=0.88, forceunit='lbs'):
if rowdatadf.empty: if rowdatadf.empty:
@@ -1593,7 +1600,6 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
except TypeError: except TypeError:
t2 = 0 * t t2 = 0 * t
p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x)) p2 = p.fillna(method='ffill').apply(lambda x: timedeltaconv(x))
try: try:
@@ -1601,7 +1607,6 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
except TypeError: except TypeError:
drivespeed = 0.0 * rowdatadf['TimeStamp (sec)'] drivespeed = 0.0 * rowdatadf['TimeStamp (sec)']
drivespeed = drivespeed.fillna(value=0) drivespeed = drivespeed.fillna(value=0)
try: try:
@@ -1617,7 +1622,6 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
distanceperstroke = 60. * velo / spm distanceperstroke = 60. * velo / spm
data = DataFrame( data = DataFrame(
dict( dict(
time=t * 1e3, time=t * 1e3,
@@ -1686,7 +1690,6 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
except KeyError: except KeyError:
peakforceangle = 0 * power peakforceangle = 0 * power
if data['driveenergy'].mean() == 0: if data['driveenergy'].mean() == 0:
try: try:
driveenergy = rowdatadf.ix[:, 'driveenergy'] driveenergy = rowdatadf.ix[:, 'driveenergy']
@@ -1695,7 +1698,6 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
else: else:
driveenergy = data['driveenergy'] driveenergy = data['driveenergy']
arclength = (inboard - 0.05) * (np.radians(finish) - np.radians(catch)) arclength = (inboard - 0.05) * (np.radians(finish) - np.radians(catch))
if arclength.mean() > 0: if arclength.mean() > 0:
drivelength = arclength drivelength = arclength
@@ -1754,7 +1756,6 @@ def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
velo = 500. / p velo = 500. / p
ergpw = 2.8 * velo**3 ergpw = 2.8 * velo**3
efficiency = 100. * ergpw / power efficiency = 100. * ergpw / power
+5
View File
@@ -12,6 +12,7 @@ import dataprep
import types import types
import datetime import datetime
from django.forms import formset_factory from django.forms import formset_factory
from utils import landingpages
# login form # login form
class LoginForm(forms.Form): class LoginForm(forms.Form):
@@ -180,6 +181,10 @@ class UploadOptionsForm(forms.Form):
makeprivate = forms.BooleanField(initial=False,required=False, makeprivate = forms.BooleanField(initial=False,required=False,
label='Make Workout Private') label='Make Workout Private')
landingpage = forms.ChoiceField(choices=landingpages,
initial='workout_edit_view',
label='Landing Page')
class Meta: class Meta:
fields = ['make_plot','plottype','upload_toc2','makeprivate'] fields = ['make_plot','plottype','upload_toc2','makeprivate']
+9 -1
View File
@@ -2046,7 +2046,6 @@ def interactive_flex_chart2(id=0,promember=0,
plottype='line', plottype='line',
workstrokesonly=False): workstrokesonly=False):
#rowdata,row = dataprep.getrowdata_db(id=id) #rowdata,row = dataprep.getrowdata_db(id=id)
columns = [xparam,yparam1,yparam2, columns = [xparam,yparam1,yparam2,
'ftime','distance','fpace', 'ftime','distance','fpace',
@@ -2510,7 +2509,16 @@ def thumbnails_set(r,id,favorites):
columns += [f.yparam2 for f in favorites] columns += [f.yparam2 for f in favorites]
columns += ['time'] columns += ['time']
try:
rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True) rowdata = dataprep.getsmallrowdata_db(columns,ids=[id],doclean=True)
except:
return [
{'script':"",
'div':"",
'notes':""
}]
rowdata.dropna(axis=1,how='all',inplace=True) rowdata.dropna(axis=1,how='all',inplace=True)
if rowdata.empty: if rowdata.empty:
+65 -161
View File
@@ -1,24 +1,19 @@
# Processes emails sent to workouts@rowsandall.com # Processes emails sent to workouts@rowsandall.com
""" Processes emails sent to workouts@rowsandall.com """
import shutil
import time import time
from django.conf import settings from django.conf import settings
from rowers.tasks import handle_sendemail_unrecognized from rowers.tasks import handle_sendemail_unrecognized
from django_mailbox.models import Mailbox,Message,MessageAttachment from django_mailbox.models import Message, MessageAttachment
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage,AdvancedWorkoutForm from rowers.models import User, Rower, RowerForm
from django.core.files.base import ContentFile
from django.core.mail import send_mail, BadHeaderError,EmailMessage from django.core.mail import EmailMessage
from rowsandall_app.settings import BASE_DIR
from rowingdata import rower as rrower from rowingdata import rower as rrower
from rowingdata import main as rmain
from rowingdata import rowingdata as rrdata
from rowingdata import TCXParser,RowProParser,ErgDataParser
from rowingdata import MysteryParser,BoatCoachParser
from rowingdata import painsledDesktopParser,speedcoachParser,ErgStickParser
from rowingdata import SpeedCoach2Parser,FITParser,fitsummarydata
from rowingdata import make_cumvalues
from rowingdata import summarydata,get_file_type
from scipy.signal import savgol_filter from rowingdata import rowingdata as rrdata
from rowingdata import get_file_type
import zipfile import zipfile
import os import os
@@ -30,10 +25,13 @@ queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('default') queuehigh = django_rq.get_queue('default')
# Sends a confirmation with a link to the workout # Sends a confirmation with a link to the workout
def send_confirm(u,name,link,options):
fullemail = u.email
def send_confirm(user, name, link, options):
""" Send confirmation email to user when email has been processed """
fullemail = user.email
subject = 'Workout added: ' + name subject = 'Workout added: ' + name
message = 'Dear '+u.first_name+',\n\n' message = 'Dear ' + user.first_name + ',\n\n'
message += "Your workout has been added to Rowsandall.com.\n" message += "Your workout has been added to Rowsandall.com.\n"
message += "Link to workout: " + link + "\n\n" message += "Link to workout: " + link + "\n\n"
message += "Best Regards, the Rowsandall Team" message += "Best Regards, the Rowsandall Team"
@@ -41,7 +39,6 @@ def send_confirm(u,name,link,options):
if options: if options:
message += "\n\n" + str(options) message += "\n\n" + str(options)
email = EmailMessage(subject, message, email = EmailMessage(subject, message,
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
@@ -51,176 +48,86 @@ def send_confirm(u,name,link,options):
return 1 return 1
# Reads a "rowingdata" object, plus some error protections # Reads a "rowingdata" object, plus some error protections
def rdata(file, rower=rrower()): def rdata(file, rower=rrower()):
""" Reads rowingdata data or returns 0 on Error """
try: try:
res = rrdata(file,rower=rower) result = rrdata(file, rower=rower)
except IOError: except IOError:
try: try:
res = rrdata(file+'.gz',rower=rower) result = rrdata(file + '.gz', rower=rower)
except IOError: except IOError:
res = 0 result = 0
return res return result
# Some error protection around process attachments
def safeprocessattachments():
try:
return processattachments()
except:
return [0]
# This is duplicated in management/commands/processemail
# Need to double check the code there, update here, and only
# use the code here.
def processattachments():
# in res, we store the ids of the new workouts
res = []
attachments = MessageAttachment.objects.all()
for a in attachments:
donotdelete = 0
m = Message.objects.get(id=a.message_id)
from_address = m.from_address[0]
name = m.subject
# get a list of users
theusers = User.objects.filter(email=from_address)
for u in theusers:
try:
rr = Rower.objects.get(user=u.id)
# move attachment and make workout
try:
wid = [make_new_workout_from_email(rr,a.document,name)]
res += wid
link = 'https://rowsandall.com/rowers/workout/'+str(wid[0])+'/edit'
if wid != 1:
dd = send_confirm(u,name,link)
except:
# replace with code to process error
res += ['fail: '+name]
donotdelete = 1
except Rower.DoesNotExist:
pass
# remove attachment
if donotdelete == 0:
a.delete()
if m.attachments.exists()==False:
# no attachments, so can be deleted
m.delete()
# Delete remaining messages (which should not have attachments)
mm = Message.objects.all()
for m in mm:
if m.attachments.exists()==False:
m.delete()
return res
# As above, but with some print commands for debugging purposes
def processattachments_debug():
res = []
attachments = MessageAttachment.objects.all()
for a in attachments:
donotdelete = 1
m = Message.objects.get(id=a.message_id)
from_address = m.from_address[0]
name = m.subject
# get a list of users
theusers = User.objects.filter(email=from_address)
print theusers
for u in theusers:
try:
rr = Rower.objects.get(user=u.id)
doorgaan = 1
except:
doorgaan = 0
if doorgaan:
# move attachment and make workout
print a.document
print name
wid = [make_new_workout_from_email(rr,a.document,name)]
res += wid
link = 'https://rowsandall.com/rowers/workout/'+str(wid[0])+'/edit'
if wid != 1:
dd = send_confirm(u,name,link)
# remove attachment def make_new_workout_from_email(rower, datafile, name, cntr=0):
if donotdelete == 0: """ This one is used in processemail """
a.delete()
if m.attachments.exists()==False:
# no attachments, so can be deleted
m.delete()
mm = Message.objects.all()
for m in mm:
if m.attachments.exists()==False:
m.delete()
return res
# Process the attachment file, create new workout
# The code here is duplication of the code in views.py (workout_upload_view)
# Need to move the code to a subroutine used both in views.py and here
def make_new_workout_from_email(rr,f2,name,cntr=0):
workouttype = 'rower' workouttype = 'rower'
try: try:
f2 = f2.name datafilename = datafile.name
fileformat = get_file_type('media/'+f2) fileformat = get_file_type('media/' + datafilename)
except IOError: except IOError:
f2 = f2.name+'.gz' datafilename = datafile.name + '.gz'
fileformat = get_file_type('media/'+f2) fileformat = get_file_type('media/' + datafilename)
except AttributeError: except AttributeError:
fileformat = get_file_type('media/'+f2) datafilename = datafile
fileformat = get_file_type('media/' + datafile)
if len(fileformat) == 3 and fileformat[0] == 'zip': if len(fileformat) == 3 and fileformat[0] == 'zip':
f_to_be_deleted = f2 with zipfile.ZipFile('media/' + datafilename) as zip_file:
with zipfile.ZipFile('media/'+f2) as z: datafilename = zip_file.extract(
f2 = z.extract(z.namelist()[0],path='media/')[6:] zip_file.namelist()[0],
path='media/')[6:]
fileformat = fileformat[2] fileformat = fileformat[2]
if fileformat == 'unknown': if fileformat == 'unknown':
# extension = datafilename[-4:].lower()
# fcopy = "media/"+datafilename[:-4]+"_copy"+extension
# with open('media/'+datafilename, 'r') as f_in, open(fcopy, 'w') as f_out:
# shutil.copyfileobj(f_in,f_out)
fcopy = "media/"+datafilename
if settings.DEBUG: if settings.DEBUG:
res = handle_sendemail_unrecognized.delay(f2, res = handle_sendemail_unrecognized.delay(
"roosendaalsander@gmail.com") fcopy,
rower.user.email
)
res = handle_sendemail_unrecognizedowner.delay(
rower.user.email,
rower.user.first_name
)
else: else:
res = queuehigh.enqueue(handle_sendemail_unrecognized, res = queuehigh.enqueue(handle_sendemail_unrecognized,
f2,"roosendaalsander@gmail.com") fcopy,
rower.user.email)
return 1 res = queuehigh.enqueue(handle_sendemail_unrecognizedowner,
rower.user.email,
rower.user.first_name
)
return 0
summary = '' summary = ''
# handle non-Painsled # handle non-Painsled
if fileformat != 'csv': if fileformat != 'csv':
f3,summary,oarlength,inboard = dataprep.handle_nonpainsled('media/'+f2,fileformat,summary) filename_mediadir, summary, oarlength, inboard = dataprep.handle_nonpainsled(
'media/' + datafilename, fileformat, summary)
else: else:
f3 = 'media/'+f2 filename_mediadir = 'media/' + datafilename
inboard = 0.88 inboard = 0.88
oarlength = 2.89 oarlength = 2.89
row = rdata(filename_mediadir)
# make workout and put in database
#r = rrower(hrmax=rr.max,hrut2=rr.ut2,
# hrut1=rr.ut1,hrat=rr.at,
# hrtr=rr.tr,hran=rr.an,ftp=r.ftp)
row = rdata(f3) #,rower=r)
if row == 0: if row == 0:
return 0 return 0
# change filename # change filename
if f2[:5] != 'media': if datafilename[:5] != 'media':
timestr = time.strftime("%Y%m%d-%H%M%S") timestr = time.strftime("%Y%m%d-%H%M%S")
f2 = 'media/'+timestr+str(cntr)+'o.csv' datafilename = 'media/' + timestr + str(cntr) + 'o.csv'
try: try:
avglat = row.df[' latitude'].mean() avglat = row.df[' latitude'].mean()
@@ -230,24 +137,21 @@ def make_new_workout_from_email(rr,f2,name,cntr=0):
except KeyError: except KeyError:
pass pass
row.write_csv(f2,gzip=True) row.write_csv(datafilename, gzip=True)
dosummary = (fileformat != 'fit') dosummary = (fileformat != 'fit')
if name == '': if name == '':
name = 'imported through email' name = 'imported through email'
id,message = dataprep.save_workout_database(f2,rr, id, message = dataprep.save_workout_database(
datafilename, rower,
workouttype=workouttype, workouttype=workouttype,
dosummary=dosummary, dosummary=dosummary,
inboard=inboard, inboard=inboard,
oarlength=oarlength, oarlength=oarlength,
title=name, title=name,
workoutsource=fileformat, workoutsource=fileformat,
notes='imported through email') notes='imported through email'
)
return id return id
+112 -126
View File
@@ -1,162 +1,148 @@
#!/srv/venv/bin/python #!/srv/venv/bin/python
""" Process emails """
import sys import sys
import os import os
import zipfile
import time
from time import strftime
from django.core.management.base import BaseCommand
from django_mailbox.models import Message, MessageAttachment
from django.core.urlresolvers import reverse
from django.conf import settings
from rowers.models import Workout, Rower
from rowingdata import rower as rrower
from rowingdata import rowingdata as rrdata
import rowers.uploads as uploads
from rowers.mailprocessing import make_new_workout_from_email, send_confirm
# If you find a solution that does not need the two paths, please comment! # If you find a solution that does not need the two paths, please comment!
sys.path.append('$path_to_root_of_project$') sys.path.append('$path_to_root_of_project$')
sys.path.append('$path_to_root_of_project$/$project_name$') sys.path.append('$path_to_root_of_project$/$project_name$')
os.environ['DJANGO_SETTINGS_MODULE'] = '$project_name$.settings' os.environ['DJANGO_SETTINGS_MODULE'] = '$project_name$.settings'
import zipfile if not getattr(__builtins__, "WindowsError", None):
class WindowsError(OSError): pass
from django.core.management.base import BaseCommand, CommandError def rdata(file_obj, rower=rrower()):
from django.conf import settings """ Read rowing data file and return 0 if file doesn't exist"""
#from rowers.mailprocessing import processattachments
import time
from time import strftime
from django.conf import settings
from rowers.tasks import handle_sendemail_unrecognized
from django_mailbox.models import Mailbox,Message,MessageAttachment
from rowers.models import Workout, User, Rower, WorkoutForm,RowerForm,GraphImage,AdvancedWorkoutForm
from django.core.files.base import ContentFile
from rowsandall_app.settings import BASE_DIR
from rowingdata import rower as rrower
from rowingdata import main as rmain
from rowingdata import rowingdata as rrdata
from rowingdata import make_cumvalues
from rowingdata import summarydata,get_file_type
from scipy.signal import savgol_filter
from rowers.mailprocessing import make_new_workout_from_email,send_confirm
import rowers.uploads as uploads
def rdata(file,rower=rrower()):
try: try:
res = rrdata(file,rower=rower) result = rrdata(file_obj, rower=rower)
except IOError: except IOError:
res = 0 result = 0
return res return result
def processattachment(rower, fileobj, title, uploadoptions):
try:
filename = fileobj.name
except AttributeError:
filename = fileobj[6:]
workoutid = [
make_new_workout_from_email(rower, filename, title)
]
if workoutid[0]:
link = settings.SITE_URL+reverse(
rower.defaultlandingpage,
kwargs = {
'id':workoutid[0],
}
)
if uploadoptions and not 'error' in uploadoptions:
workout = Workout.objects.get(id=workoutid[0])
uploads.do_sync(workout, uploadoptions)
uploads.make_private(workout, uploadoptions)
if 'make_plot' in uploadoptions:
plottype = uploadoptions['plottype']
workoutcsvfilename = workout.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = workoutcsvfilename + timestr + '.png'
result = uploads.make_plot(
workout.user, workout, workoutcsvfilename,
workout.csvfilename,
plottype, title,
imagename=imagename
)
try:
if workoutid:
email_sent = send_confirm(
rower.user, title, link,
uploadoptions
)
time.sleep(10)
except:
try:
time.sleep(10)
if workoutid:
email_sent = send_confirm(
rower.user, title, link,
uploadoptions
)
except:
pass
return workoutid
class Command(BaseCommand): class Command(BaseCommand):
"""Run the Email processing command """
def handle(self, *args, **options): def handle(self, *args, **options):
res = []
attachments = MessageAttachment.objects.all() attachments = MessageAttachment.objects.all()
cntr = 0 cntr = 0
for a in attachments: for attachment in attachments:
extension = a.document.name[-3:].lower() extension = attachment.document.name[-3:].lower()
donotdelete = 0 message = Message.objects.get(id=attachment.message_id)
m = Message.objects.get(id=a.message_id) body = "\n".join(message.text.splitlines())
body = "\n".join(m.text.splitlines())
uploadoptions = uploads.upload_options(body) uploadoptions = uploads.upload_options(body)
from_address = m.from_address[0].lower() from_address = message.from_address[0].lower()
name = m.subject name = message.subject
cntr += 1
# get a list of users # get a list of users
# theusers = User.objects.filter(email=from_address) # theusers = User.objects.filter(email=from_address)
ther = [ rowers = [
r for r in Rower.objects.all() if r.user.email.lower() == from_address r for r in Rower.objects.all() if r.user.email.lower() == from_address
] ]
for rr in ther: for rower in rowers:
if extension == 'zip': if extension == 'zip':
z = zipfile.ZipFile(a.document) zip_file = zipfile.ZipFile(attachment.document)
for f in z.namelist(): for filename in zip_file.namelist():
f2 = z.extract(f,path='media/') datafile = zip_file.extract(filename, path='media/')
title = os.path.basename(f2) title = os.path.basename(datafile)
wid = [ workoutid = processattachment(
make_new_workout_from_email(rr,f2[6:],title) rower, datafile, title, uploadoptions
] )
res += wid
link = 'http://rowsandall.com/rowers/workout/'+str(wid[0])+'/edit'
if uploadoptions and not 'error' in uploadoptions:
w = Workout.objects.get(id=wid[0])
r = w.user
uploads.do_sync(w,uploadoptions)
uploads.make_private(w,uploadoptions)
if 'make_plot' in uploadoptions:
plottype = uploadoptions['plottype']
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
resu = uploads.make_plot(r,w,f1,
w.csvfilename,
plottype,name,
imagename=imagename)
try:
if wid != 1:
dd = send_confirm(rr.user,title,link,
uploadoptions)
time.sleep(10)
except:
try:
time.sleep(10)
if wid != 1:
dd = send_confirm(rr.user,title,link,
uploadoptions)
except:
pass
else: else:
# move attachment and make workout # move attachment and make workout
try: workoutid = processattachment(
wid = [ rower, attachment.document, name, uploadoptions
make_new_workout_from_email(rr, )
a.document,
name)
]
res += wid
link = 'http://rowsandall.com/rowers/workout/'+str(wid[0])+'/edit'
if uploadoptions:
w = Workout.objects.get(id=wid[0])
r = w.user
uploads.do_sync(w,uploadoptions)
uploads.make_private(w,uploadoptions)
if 'make_plot' in uploadoptions:
plottype = uploadoptions['plottype']
f1 = w.csvfilename[6:-4]
timestr = strftime("%Y%m%d-%H%M%S")
imagename = f1+timestr+'.png'
resu = uploads.make_plot(r,w,f1,
w.csvfilename,
plottype,name,
imagename=imagename)
except: # We're done with the attachment. It can be deleted
# replace with code to process error
res += ['fail: '+name]
donotdelete = 1
wid = 1
try: try:
if wid != 1: attachment.delete()
dd = send_confirm(rr.user,name,link,
uploadoptions)
time.sleep(10)
except:
pass
try:
a.delete()
except IOError: except IOError:
pass pass
# remove attachment except WindowsError:
#if donotdelete == 0: time.sleep(2)
try:
attachment.delete()
except WindowsError:
pass
if m.attachments.exists()==False: if message.attachments.exists() is False:
# no attachments, so can be deleted # no attachments, so can be deleted
m.delete() message.delete()
mm = Message.objects.all()
for m in mm:
if m.attachments.exists()==False:
m.delete()
self.stdout.write(self.style.SUCCESS('Successfully processed email attachments'))
messages = Message.objects.all()
for message in messages:
if message.attachments.exists() is False:
message.delete()
self.stdout.write(self.style.SUCCESS(
'Successfully processed email attachments'))
+2 -2
View File
@@ -269,7 +269,7 @@ in case you recorded your heart rate during your workout""",
heart rate versus time. """, heart rate versus time. """,
}, },
{ {
'yparam1':'strokeenergy', 'yparam1':'driveenergy',
'yparam2':'hr', 'yparam2':'hr',
'xparam':'time', 'xparam':'time',
'plottype':'line', 'plottype':'line',
@@ -291,7 +291,7 @@ as you increase stroke rate. Typical values are > 10m for steady state
dropping to 8m for race pace in the single.""", dropping to 8m for race pace in the single.""",
}, },
{ {
'yparam1':'strokeenergy', 'yparam1':'driveenergy',
'yparam2':'None', 'yparam2':'None',
'xparam':'spm', 'xparam':'spm',
'plottype':'line', 'plottype':'line',
+7 -3
View File
@@ -198,7 +198,7 @@ class TeamRequest(models.Model):
from utils import ( from utils import (
workflowleftpanel,workflowmiddlepanel, workflowleftpanel,workflowmiddlepanel,
defaultleft,defaultmiddle defaultleft,defaultmiddle,landingpages
) )
# Extension of User with rowing specific data # Extension of User with rowing specific data
@@ -281,8 +281,11 @@ class Rower(models.Model):
# Site Settings # Site Settings
workflowleftpanel = TemplateListField(default=defaultleft) workflowleftpanel = TemplateListField(default=defaultleft)
workflowmiddlepanel = TemplateListField(default=defaultmiddle) workflowmiddlepanel = TemplateListField(default=defaultmiddle)
defaultlandingpage = models.CharField(default='workout_edit_view',
max_length=200,
choices=landingpages,
verbose_name="Default Landing Page")
# Access tokens # Access tokens
c2token = models.CharField(default='',max_length=200,blank=True,null=True) c2token = models.CharField(default='',max_length=200,blank=True,null=True)
@@ -889,7 +892,8 @@ class AccountRowerForm(ModelForm):
class Meta: class Meta:
model = Rower model = Rower
fields = ['weightcategory','getemailnotifications', fields = ['weightcategory','getemailnotifications',
'defaulttimezone','showfavoritechartnotes'] 'defaulttimezone','showfavoritechartnotes',
'defaultlandingpage']
class UserForm(ModelForm): class UserForm(ModelForm):
class Meta: class Meta:
+78 -33
View File
@@ -1,4 +1,4 @@
from celery import Celery,app """ Background tasks done by Celery (develop) or QR (production) """
import os import os
import time import time
import gc import gc
@@ -7,40 +7,44 @@ import shutil
import numpy as np import numpy as np
import rowingdata import rowingdata
from rowingdata import main as rmain
from rowingdata import rowingdata as rdata from rowingdata import rowingdata as rdata
import rowingdata
from async_messages import message_user,messages from celery import app
from matplotlib.backends.backend_agg import FigureCanvas from matplotlib.backends.backend_agg import FigureCanvas
#from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas #from matplotlib.backends.backend_cairo import FigureCanvasCairo as FigureCanvas
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib import figure
import stravalib
import pandas as pd import pandas as pd
from django_rq import job from django_rq import job
from utils import serialize_list,deserialize_list from utils import deserialize_list
from rowers.dataprepnodjango import ( from rowers.dataprepnodjango import (
update_strokedata, new_workout_from_file, update_strokedata, new_workout_from_file,
getsmallrowdata_db,read_df_sql,columndict, getsmallrowdata_db,
) )
from django.core.mail import send_mail, BadHeaderError,EmailMessage from django.core.mail import send_mail, EmailMessage
import datautils import datautils
import utils import utils
# testing task # testing task
@app.task @app.task
def add(x, y): def add(x, y):
return x + y return x + y
# create workout # create workout
@app.task @app.task
def handle_new_workout_from_file(r, f2, def handle_new_workout_from_file(r, f2,
workouttype='rower', workouttype='rower',
@@ -51,6 +55,8 @@ def handle_new_workout_from_file(r,f2,
title, makeprivate, notes) title, makeprivate, notes)
# process and update workouts # process and update workouts
@app.task @app.task
def handle_updatedps(useremail, workoutids, debug=False): def handle_updatedps(useremail, workoutids, debug=False):
for wid, f1 in workoutids: for wid, f1 in workoutids:
@@ -81,6 +87,8 @@ def handle_updatedps(useremail,workoutids,debug=False):
return 1 return 1
# send email when a breakthrough workout is uploaded # send email when a breakthrough workout is uploaded
@app.task @app.task
def handle_sendemail_breakthrough(workoutid, useremail, def handle_sendemail_breakthrough(workoutid, useremail,
userfirstname, userlastname, userfirstname, userlastname,
@@ -124,7 +132,6 @@ def handle_sendemail_breakthrough(workoutid,useremail,
pwr=pwr pwr=pwr
) )
message += "To opt out of these email notifications, deselect the checkbox on your Profile page under Account Information.\n\n" message += "To opt out of these email notifications, deselect the checkbox on your Profile page under Account Information.\n\n"
message += "Best Regards, the Rowsandall Team" message += "Best Regards, the Rowsandall Team"
@@ -133,13 +140,14 @@ def handle_sendemail_breakthrough(workoutid,useremail,
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[useremail]) [useremail])
res = email.send() res = email.send()
# remove tcx file # remove tcx file
return 1 return 1
# send email when a breakthrough workout is uploaded # send email when a breakthrough workout is uploaded
@app.task @app.task
def handle_sendemail_hard(workoutid, useremail, def handle_sendemail_hard(workoutid, useremail,
userfirstname, userlastname, userfirstname, userlastname,
@@ -167,7 +175,6 @@ def handle_sendemail_hard(workoutid,useremail,
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[useremail]) [useremail])
res = email.send() res = email.send()
# remove tcx file # remove tcx file
@@ -206,6 +213,37 @@ def handle_sendemail_unrecognized(unrecognizedfile,useremail):
return 1 return 1
# send email to owner when an unrecognized file is uploaded
@app.task
def handle_sendemail_unrecognizedowner(useremail, userfirstname):
# send email with attachment
fullemail = useremail
subject = "Unrecognized file from Rowsandall.com"
message = "Dear " + userfirstname + ",\n\n"
message += """
The file you tried to send to rowsandall.com was not recognized by
our email processing system. You may have sent a file in a format
that is not supported. Sometimes, rowing apps make file format changes.
When that happens, it takes some time for rowsandall.comm to make
the necessary changes on our side and support the app again.
The file has been sent to the developer at rowsandall.com for evaluation.
"""
message += "Best Regards, the Rowsandall Team"
email = EmailMessage(subject, message,
'Rowsandall <info@rowsandall.com>',
[fullemail])
res = email.send()
return 1
# Send email with TCX attachment # Send email with TCX attachment
@app.task @app.task
def handle_sendemailtcx(first_name, last_name, email, tcxfile): def handle_sendemailtcx(first_name, last_name, email, tcxfile):
@@ -221,7 +259,6 @@ def handle_sendemailtcx(first_name,last_name,email,tcxfile):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
email.attach_file(tcxfile) email.attach_file(tcxfile)
res = email.send() res = email.send()
@@ -230,6 +267,7 @@ def handle_sendemailtcx(first_name,last_name,email,tcxfile):
os.remove(tcxfile) os.remove(tcxfile)
return 1 return 1
@app.task @app.task
def handle_zip_file(emailfrom, subject, file): def handle_zip_file(emailfrom, subject, file):
message = "... zip processing ... " message = "... zip processing ... "
@@ -242,6 +280,8 @@ def handle_zip_file(emailfrom,subject,file):
return 1 return 1
# Send email with CSV attachment # Send email with CSV attachment
@app.task @app.task
def handle_sendemailcsv(first_name, last_name, email, csvfile): def handle_sendemailcsv(first_name, last_name, email, csvfile):
@@ -256,7 +296,6 @@ def handle_sendemailcsv(first_name,last_name,email,csvfile):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
if os.path.isfile(csvfile): if os.path.isfile(csvfile):
email.attach_file(csvfile) email.attach_file(csvfile)
else: else:
@@ -272,9 +311,12 @@ def handle_sendemailcsv(first_name,last_name,email,csvfile):
return 1 return 1
# Calculate wind and stream corrections for OTW rowing # Calculate wind and stream corrections for OTW rowing
@app.task @app.task
def handle_otwsetpower(f1, boattype, weightvalue, def handle_otwsetpower(f1, boattype, weightvalue,
first_name,last_name,email,workoutid,ps=[1,1,1,1], first_name, last_name, email, workoutid, ps=[
1, 1, 1, 1],
ratio=1.0, ratio=1.0,
debug=False): debug=False):
try: try:
@@ -317,7 +359,8 @@ def handle_otwsetpower(f1,boattype,weightvalue,
rowdata.write_csv(f1, gzip=True) rowdata.write_csv(f1, gzip=True)
update_strokedata(workoutid, rowdata.df, debug=debug) update_strokedata(workoutid, rowdata.df, debug=debug)
totaltime = rowdata.df['TimeStamp (sec)'].max()-rowdata.df['TimeStamp (sec)'].min() totaltime = rowdata.df['TimeStamp (sec)'].max(
) - rowdata.df['TimeStamp (sec)'].min()
try: try:
totaltime = totaltime + rowdata.df.ix[0, ' ElapsedTime (sec)'] totaltime = totaltime + rowdata.df.ix[0, ' ElapsedTime (sec)']
except KeyError: except KeyError:
@@ -329,9 +372,9 @@ def handle_otwsetpower(f1,boattype,weightvalue,
dfgrouped = df.groupby(['workoutid']) dfgrouped = df.groupby(['workoutid'])
delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr) delta, cpvalues, avgpower = datautils.getcp(dfgrouped, logarr)
#delta,cpvalues,avgpower = datautils.getsinglecp(rowdata.df) #delta,cpvalues,avgpower = datautils.getsinglecp(rowdata.df)
res,btvalues,res2 = utils.isbreakthrough(delta,cpvalues,ps[0],ps[1],ps[2],ps[3],ratio) res, btvalues, res2 = utils.isbreakthrough(
delta, cpvalues, ps[0], ps[1], ps[2], ps[3], ratio)
if res: if res:
handle_sendemail_breakthrough(workoutid, email, handle_sendemail_breakthrough(workoutid, email,
first_name, first_name,
@@ -358,6 +401,8 @@ def handle_otwsetpower(f1,boattype,weightvalue,
return 1 return 1
# This function generates all the static (PNG image) plots # This function generates all the static (PNG image) plots
@app.task @app.task
def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename): def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename):
@@ -381,7 +426,6 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
except IOError: except IOError:
row = rdata(f2 + '.gz', rower=rr) row = rdata(f2 + '.gz', rower=rr)
haspower = row.df[' Power (watts)'].mean() > 50 haspower = row.df[' Power (watts)'].mean() > 50
nr_rows = len(row.df) nr_rows = len(row.df)
@@ -430,6 +474,7 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
# Team related remote tasks # Team related remote tasks
@app.task @app.task
def handle_sendemail_invite(email, name, code, teamname, manager): def handle_sendemail_invite(email, name, code, teamname, manager):
fullemail = name + ' <' + email + '>' fullemail = name + ' <' + email + '>'
@@ -461,11 +506,11 @@ def handle_sendemail_invite(email,name,code,teamname,manager):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemailnewresponse(first_name, last_name, def handle_sendemailnewresponse(first_name, last_name,
email, email,
@@ -482,21 +527,23 @@ def handle_sendemailnewresponse(first_name,last_name,
message += comment message += comment
message += '\n\n' message += '\n\n'
message += 'You can read the comment here:\n' message += 'You can read the comment here:\n'
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment' message += 'https://rowsandall.com/rowers/workout/' + \
str(workoutid) + '/comment'
message += '\n\n' message += '\n\n'
message += 'You are receiving this email because you are subscribed ' message += 'You are receiving this email because you are subscribed '
message += 'to comments on this workout. To unsubscribe, follow this link:\n' message += 'to comments on this workout. To unsubscribe, follow this link:\n'
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/unsubscribe' message += 'https://rowsandall.com/rowers/workout/' + \
str(workoutid) + '/unsubscribe'
email = EmailMessage(subject, message, email = EmailMessage(subject, message,
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemailnewcomment(first_name, def handle_sendemailnewcomment(first_name,
last_name, last_name,
@@ -514,13 +561,13 @@ def handle_sendemailnewcomment(first_name,
message += comment message += comment
message += '\n\n' message += '\n\n'
message += 'You can read the comment here:\n' message += 'You can read the comment here:\n'
message += 'https://rowsandall.com/rowers/workout/'+str(workoutid)+'/comment' message += 'https://rowsandall.com/rowers/workout/' + \
str(workoutid) + '/comment'
email = EmailMessage(subject, message, email = EmailMessage(subject, message,
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@@ -545,11 +592,11 @@ def handle_sendemail_request(email,name,code,teamname,requestor,id):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemail_request_accept(email, name, teamname, managername): def handle_sendemail_request_accept(email, name, teamname, managername):
fullemail = name + ' <' + email + '>' fullemail = name + ' <' + email + '>'
@@ -565,11 +612,11 @@ def handle_sendemail_request_accept(email,name,teamname,managername):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemail_request_reject(email, name, teamname, managername): def handle_sendemail_request_reject(email, name, teamname, managername):
fullemail = name + ' <' + email + '>' fullemail = name + ' <' + email + '>'
@@ -586,11 +633,11 @@ def handle_sendemail_request_reject(email,name,teamname,managername):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemail_member_dropped(email, name, teamname, managername): def handle_sendemail_member_dropped(email, name, teamname, managername):
fullemail = name + ' <' + email + '>' fullemail = name + ' <' + email + '>'
@@ -607,11 +654,11 @@ def handle_sendemail_member_dropped(email,name,teamname,managername):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemail_team_removed(email, name, teamname, managername): def handle_sendemail_team_removed(email, name, teamname, managername):
fullemail = name + ' <' + email + '>' fullemail = name + ' <' + email + '>'
@@ -629,11 +676,11 @@ def handle_sendemail_team_removed(email,name,teamname,managername):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemail_invite_reject(email, name, teamname, managername): def handle_sendemail_invite_reject(email, name, teamname, managername):
fullemail = managername + ' <' + email + '>' fullemail = managername + ' <' + email + '>'
@@ -650,11 +697,11 @@ def handle_sendemail_invite_reject(email,name,teamname,managername):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
@app.task @app.task
def handle_sendemail_invite_accept(email, name, teamname, managername): def handle_sendemail_invite_accept(email, name, teamname, managername):
fullemail = managername + ' <' + email + '>' fullemail = managername + ' <' + email + '>'
@@ -667,13 +714,11 @@ def handle_sendemail_invite_accept(email,name,teamname,managername):
'Rowsandall <info@rowsandall.com>', 'Rowsandall <info@rowsandall.com>',
[fullemail]) [fullemail])
res = email.send() res = email.send()
return 1 return 1
# Another simple task for debugging purposes # Another simple task for debugging purposes
def add2(x, y): def add2(x, y):
return x + y return x + y
+6 -1
View File
@@ -24,7 +24,12 @@
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a>
</p> </p>
</div> </div>
<div class="grid_2 suffix_2 omega"> <div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2 omega">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a>
</p> </p>
+6 -1
View File
@@ -23,7 +23,12 @@
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a>
</p> </p>
</div> </div>
<div class="grid_2 suffix_2 omega"> <div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2 omega">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a>
</p> </p>
+2 -15
View File
@@ -38,23 +38,10 @@
You can select one static plot to be generated immediately for You can select one static plot to be generated immediately for
this workout. You can select to export to major fitness this workout. You can select to export to major fitness
platforms automatically. platforms automatically.
If you check "make private", this workout will not be visible to your followers and will not show up in your teams' workouts list. If you check "make private", this workout will not be visible to your followers and will not show up in your teams' workouts list. With the Landing Page option, you can select to which (workout related) page you will be
taken after a successfull upload.
</p> </p>
<p>
Valid file types are:
<ul>
<li>Painsled iOS Stroke Export (CSV)</li>
<li>Painsled desktop version Stroke Export (CSV)</li>
<li>A TCX file with location data (lat,long) - with or without Heart Rate value, for example from RiM or CrewNerd</li>
<li>RowPro CSV export</li>
<li>SpeedCoach GPS and SpeedCoach GPS 2 CSV export</li>
<li>ErgData CSV export</li>
<li>ErgStick CSV export</li>
<li>BoatCoach CSV export</li>
<li>A FIT file with location data (experimental)</li>
</ul>
</p>
</div> </div>
+6 -1
View File
@@ -15,7 +15,12 @@
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a>
</p> </p>
</div> </div>
<div class="grid_2 suffix_2 omega"> <div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2 omega">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
</p> </p>
+6 -1
View File
@@ -32,7 +32,12 @@
<a class="button gray small" href="/rowers/workout/{{ id }}/edit">Edit Workout</a> <a class="button gray small" href="/rowers/workout/{{ id }}/edit">Edit Workout</a>
</p> </p>
</div> </div>
<div class="grid_2 suffix_8 omega"> <div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ id }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2 suffix_6 omega">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ id }}/advanced">Advanced Edit</a> <a class="button gray small" href="/rowers/workout/{{ id }}/advanced">Advanced Edit</a>
</p> </p>
+1
View File
@@ -1,5 +1,6 @@
{% if charts %} {% if charts %}
<h2>Flex Charts</h2> <h2>Flex Charts</h2>
<p>Click on the thumbnails to view the full chart.</p>
{% for chart in charts %} {% for chart in charts %}
<div class="grid_3 alpha"> <div class="grid_3 alpha">
<big>{{ forloop.counter }}</big> <big>{{ forloop.counter }}</big>
+2 -2
View File
@@ -84,9 +84,9 @@
[RANKING PIECE] [RANKING PIECE]
{% endif %} {% endif %}
{% if workout.name != '' %} {% if workout.name != '' %}
<a href="/rowers/workout/{{ workout.id }}/edit">{{ workout.name }}</a> </td> <a href={% url rower.defaultlandingpage id=workout.id %}>{{ workout.name }}</a> </td>
{% else %} {% else %}
<a href="/rowers/workout/{{ workout.id }}/edit">No Name</a> </td> <a href={% url rower.defaultlandingpage id=workout.id %}>No Name</a> </td>
{% endif %} {% endif %}
{% else %} {% else %}
{% if workout.name != '' %} {% if workout.name != '' %}
+7 -1
View File
@@ -43,7 +43,13 @@
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a>
</p> </p>
</div> </div>
<div class="grid_2 suffix_2 omega"> <div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2 omega">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
</p> </p>
+7 -1
View File
@@ -14,7 +14,13 @@
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/edit">Edit Workout</a>
</p> </p>
</div> </div>
<div class="grid_2 suffix_2 omega"> <div class="grid_2">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2 omega">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/advanced">Advanced Edit</a>
</p> </p>
+31
View File
@@ -0,0 +1,31 @@
{% load rowerfilters %}
{% load tz %}
<div class="grid_6 suffix_3 alpha">
<table width=100%>
<tr>
{% localtime on %}
<th>Date/Time:</th><td>{{ workout.startdatetime|localtime}}</td>
{% endlocaltime %}
</tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr><tr>
<th>Public link to this workout</th>
<td>
<a href="/rowers/workout/{{ workout.id }}">https://rowsandall.com/rowers/workout/{{ workout.id }}</a>
</td>
</tr><tr>
<th>Comments</th>
<td>
<a href="/rowers/workout/{{ workout.id }}/comment">Comment ({{ aantalcomments }})</a>
</td>
</tr><tr>
<th>Public link to interactive chart</th>
<td>
<a href="/rowers/workout/{{ workout.id }}/interactiveplot">https://rowsandall.com/rowers/workout/{{ workout.id }}/interactiveplot</a>
<td>
</tr>
</table>
</div>
+5
View File
@@ -0,0 +1,5 @@
<div class="grid_2 alpha">
<p>
<a class="button red small" href="/rowers/workout/{{ workout.id }}/deleteconfirm">Delete</a>
</p>
</div>
+5
View File
@@ -0,0 +1,5 @@
<div class="grid_2 alpha">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export Workout</a>
</p>
</div>
+9
View File
@@ -0,0 +1,9 @@
<div style="height:100%;" id="theplot" class="grid_9 alpha flexplot">
{{ mapdiv|safe }}
{{ mapscript|safe }}
</div>
+5
View File
@@ -0,0 +1,5 @@
<div class="grid_2 alpha">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/map">Map View</a>
</p>
</div>
+14
View File
@@ -0,0 +1,14 @@
<div class="grid_9">
<div class="grid_1 alpha">
<div class="fb-share-button" data-href="https://rowsandall.com/rowers/workout/{{ workout.id }}" data-layout="button" data-size="small" data-mobile-iframe="false">
<a class="fb-xfbml-parse-ignore" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://rowsandall.com/rowers/workout/{{ workout.id }}">Share</a>
</div>
</div>
<div class="grid_1 suffix_7 omega">
<a class="twitter-share-button"
href="https://twitter.com/intent/tweet"
data-url="https://rowsandall.com/rowers/workout/{{ workout.id }}"
data-text="@rowsandall #rowingdata">Tweet</a>
</div>
+14
View File
@@ -0,0 +1,14 @@
<div class="grid_2 alpha">
<div class="fb-share-button" data-href="https://rowsandall.com/rowers/workout/{{ workout.id }}" data-layout="button" data-size="small" data-mobile-iframe="false">
<a class="fb-xfbml-parse-ignore" target="_blank" href="https://www.facebook.com/sharer/sharer.php?u=https://rowsandall.com/rowers/workout/{{ workout.id }}">Share</a>
</div>
</div>
<div class="grid_2 alpha">
<p>&nbsp;</p>
<a class="twitter-share-button"
href="https://twitter.com/intent/tweet"
data-url="https://rowsandall.com/rowers/workout/{{ workout.id }}"
data-text="@rowsandall #rowingdata">Tweet</a>
</div>
+4 -1
View File
@@ -29,7 +29,10 @@
</div> </div>
</div> </div>
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<div class="grid_2 prefix_4 alpha"> <div class="grid_2 prefix_2 alpha">
<p><a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a></p>
</div>
<div class="grid_2 omega">
<p><a class="button blue small" href="/rowers/workout/{{ workout.id }}/wind">Wind Edit</a></p> <p><a class="button blue small" href="/rowers/workout/{{ workout.id }}/wind">Wind Edit</a></p>
</div> </div>
</div> </div>
+1 -1
View File
@@ -23,7 +23,7 @@
</div> </div>
<div class="grid_2"> <div class="grid_2">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p> </p>
</div> </div>
+4 -1
View File
@@ -28,7 +28,10 @@
</div> </div>
</div> </div>
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<div class="grid_2 prefix_4 alpha"> <div class="grid_2 prefix_2 alpha">
<p><a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a></p>
</div>
<div class="grid_2 omega">
<p><a class="button blue small" href="/rowers/workout/{{ workout.id }}/stream">Stream Edit</a></p> <p><a class="button blue small" href="/rowers/workout/{{ workout.id }}/stream">Stream Edit</a></p>
</div> </div>
</div> </div>
-4
View File
@@ -3,7 +3,6 @@
{% load rowerfilters %} {% load rowerfilters %}
{% load tz %} {% load tz %}
{% get_current_timezone as TIME_ZONE %} {% get_current_timezone as TIME_ZONE %}
{% block title %}{{ workout.name }}{% endblock %} {% block title %}{{ workout.name }}{% endblock %}
@@ -40,9 +39,6 @@
{% include templateName %} {% include templateName %}
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}
<div class="grid_2 alpha">
<p>Click on the thumbnails to view the full chart</p>
</div>
</div> </div>
<div id="middlepanel" class="grid_9"> <div id="middlepanel" class="grid_9">
{% block middle_panel %} {% block middle_panel %}
+6 -1
View File
@@ -44,7 +44,12 @@
</div> </div>
</div> </div>
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<div class="grid_2 prefix_2 alpha"> <div class="grid_2 alpha">
<p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p>
</div>
<div class="grid_2">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/map">Map View</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/map">Map View</a>
</p> </p>
+1 -1
View File
@@ -18,7 +18,7 @@
</div> </div>
<div class="grid_2"> <div class="grid_2">
<p> <p>
<a class="button gray small" href="/rowers/workout/{{ workout.id }}/export">Export</a> <a class="button gray small" href="/rowers/workout/{{ workout.id }}/workflow">Workflow View</a>
</p> </p>
</div> </div>
-1
View File
@@ -118,7 +118,6 @@ def user_teams(user):
return teams return teams
@register.filter @register.filter
def has_teams(user): def has_teams(user):
try: try:
+4 -2
View File
@@ -191,7 +191,8 @@ urlpatterns = [
url(r'^workout/compare/(?P<id>\d+)/$',views.workout_comparison_list), url(r'^workout/compare/(?P<id>\d+)/$',views.workout_comparison_list),
url(r'^workout/compare2/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>\w+.*)/$',views.workout_comparison_view), url(r'^workout/compare2/(?P<id1>\d+)/(?P<id2>\d+)/(?P<xparam>\w+.*)/(?P<yparam>\w+.*)/$',views.workout_comparison_view),
url(r'^workout/compare/(?P<id>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\w+.*)$',views.workout_comparison_list), url(r'^workout/compare/(?P<id>\d+)/(?P<startdatestring>\d+-\d+-\d+)/(?P<enddatestring>\w+.*)$',views.workout_comparison_list),
url(r'^workout/(?P<id>\d+)/edit$',views.workout_edit_view), url(r'^workout/(?P<id>\d+)/edit$',views.workout_edit_view,
name='workout_edit_view'),
url(r'^workout/(?P<id>\d+)/navionics$',views.workout_edit_view_navionics), url(r'^workout/(?P<id>\d+)/navionics$',views.workout_edit_view_navionics),
url(r'^workout/(?P<id>\d+)/map$',views.workout_map_view), url(r'^workout/(?P<id>\d+)/map$',views.workout_map_view),
url(r'^workout/(?P<id>\d+)/setprivate$',views.workout_setprivate_view), url(r'^workout/(?P<id>\d+)/setprivate$',views.workout_setprivate_view),
@@ -329,7 +330,8 @@ urlpatterns = [
url(r'^legal', TemplateView.as_view(template_name='legal.html'),name='legal'), url(r'^legal', TemplateView.as_view(template_name='legal.html'),name='legal'),
url(r'^register$',views.rower_register_view), url(r'^register$',views.rower_register_view),
url(r'^register/thankyou/$', TemplateView.as_view(template_name='registerthankyou.html'), name='registerthankyou'), url(r'^register/thankyou/$', TemplateView.as_view(template_name='registerthankyou.html'), name='registerthankyou'),
url(r'^workout/(?P<id>\d+)/workflow$',views.workout_workflow_view), url(r'^workout/(?P<id>\d+)/workflow$',views.workout_workflow_view,
name='workout_workflow_view'),
url(r'^workout/(?P<id>\d+)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<plottype>\w+)/$',views.workout_flexchart3_view), url(r'^workout/(?P<id>\d+)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<plottype>\w+)/$',views.workout_flexchart3_view),
url(r'^workout/(?P<id>\d+)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<plottype>\w+.*)$',views.workout_flexchart3_view), url(r'^workout/(?P<id>\d+)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)/(?P<plottype>\w+.*)$',views.workout_flexchart3_view),
url(r'^workout/(?P<id>\d+)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)$',views.workout_flexchart3_view), url(r'^workout/(?P<id>\d+)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>\w+.*)/(?P<yparam2>\w+.*)$',views.workout_flexchart3_view),
+18 -3
View File
@@ -5,19 +5,33 @@ import colorsys
lbstoN = 4.44822 lbstoN = 4.44822
landingpages = (
('workout_edit_view','Edit View'),
('workout_workflow_view','Workflow View'),
)
workflowmiddlepanel = ( workflowmiddlepanel = (
('panel_statcharts.html','Static Charts'), ('panel_statcharts.html','Static Charts'),
('flexthumbnails.html','Flex Charts'), ('flexthumbnails.html','Flex Charts'),
('panel_summary.html','Summary'), ('panel_summary.html','Summary'),
('panel_map.html','Map'),
('panel_comments.html','Basic Info and Links'),
('panel_middlesocial.html','Social Media Share Buttons'),
) )
defaultmiddle = ['panel_statcharts.html', defaultmiddle = ['panel_middlesocial.html',
'panel_statcharts.html',
'flexthumbnails.html', 'flexthumbnails.html',
'panel_summary.html'] 'panel_summary.html',
'panel_map.html']
workflowleftpanel = ( workflowleftpanel = (
('panel_navigationheader.html','Navigation Header'), ('panel_navigationheader.html','Navigation Header'),
('panel_editbuttons.html','Edit Workout Button'), ('panel_editbuttons.html','Edit Workout Button'),
('panel_delete.html','Delete Workout Button'),
('panel_export.html','Export Workout Button'),
('panel_social.html','Social Media Share Buttons'),
('panel_advancededit.html','Advanced Workout Edit Button'), ('panel_advancededit.html','Advanced Workout Edit Button'),
('panel_editintervals.html','Edit Intervals Button'), ('panel_editintervals.html','Edit Intervals Button'),
('panel_stats.html','Workout Statistics Button'), ('panel_stats.html','Workout Statistics Button'),
@@ -25,7 +39,8 @@ workflowleftpanel = (
('panel_geekyheader.html','Geeky Header'), ('panel_geekyheader.html','Geeky Header'),
('panel_editwind.html','Edit Wind Data'), ('panel_editwind.html','Edit Wind Data'),
('panel_editstream.html','Edit Stream Data'), ('panel_editstream.html','Edit Stream Data'),
('panel_otwpower.html','Run OTW Power Calculations') ('panel_otwpower.html','Run OTW Power Calculations'),
('panel_mapview.html','Map'),
) )
defaultleft = [ defaultleft = [
+99 -20
View File
@@ -12,6 +12,7 @@ from PIL import Image
from numbers import Number from numbers import Number
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.db.models import Q from django.db.models import Q
from django import template
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.shortcuts import render from django.shortcuts import render
from django.http import ( from django.http import (
@@ -157,6 +158,27 @@ from interactiveplots import *
# Define the API documentation # Define the API documentation
schema_view = get_swagger_view(title='Rowsandall API') schema_view = get_swagger_view(title='Rowsandall API')
# Test if row data include candidates
def rowhascoordinates(row):
# create interactive plot
f1 = row.csvfilename
u = row.user.user
r = getrower(u)
rowdata = rdata(f1)
hascoordinates = 1
if rowdata != 0:
try:
latitude = rowdata.df[' latitude']
if not latitude.std():
hascoordinates = 0
except KeyError,AttributeError:
hascoordinates = 0
else:
hascoordinates = 0
return hascoordinates
# Custom error pages with Rowsandall headers # Custom error pages with Rowsandall headers
def error500_view(request): def error500_view(request):
response = render_to_response('500.html', {}, response = render_to_response('500.html', {},
@@ -4432,6 +4454,7 @@ def workouts_view(request,message='',successmessage='',
return render(request, 'list_workouts.html', return render(request, 'list_workouts.html',
{'workouts': workouts, {'workouts': workouts,
'rower':r,
'dateform':dateform, 'dateform':dateform,
'startdate':startdate, 'startdate':startdate,
'enddate':enddate, 'enddate':enddate,
@@ -5231,7 +5254,11 @@ def workout_otwsetpower_view(request,id=0,message="",successmessage=""):
kwargs = { kwargs = {
'id':int(id)} 'id':int(id)}
try:
url = request.session['referer']
except KeyError:
url = reverse(workout_advanced_view,kwargs=kwargs) url = reverse(workout_advanced_view,kwargs=kwargs)
response = HttpResponseRedirect(url) response = HttpResponseRedirect(url)
return response return response
@@ -6006,15 +6033,41 @@ def workout_workflow_view(request,id):
charts = [] charts = []
if favorites: if favorites and 'flexthumbnails.html' in r.workflowmiddlepanel:
charts = thumbnails_set(r,id,favorites) charts = thumbnails_set(r,id,favorites)
if charts[0]['script'] == '':
charts = []
if 'panel_map.html' in r.workflowmiddlepanel and rowhascoordinates(row):
rowdata = rdata(row.csvfilename)
mapscript,mapdiv = leaflet_chart2(rowdata.df[' latitude'],
rowdata.df[' longitude'],
row.name)
else:
mapscript = ''
mapdiv = ''
statcharts = GraphImage.objects.filter(workout=row) statcharts = GraphImage.objects.filter(workout=row)
# This will be user configurable in the future
middleTemplates = r.workflowmiddlepanel
leftTemplates = r.workflowleftpanel middleTemplates = []
for t in r.workflowmiddlepanel:
try:
template.loader.get_template(t)
middleTemplates.append(t)
except template.TemplateDoesNotExist:
pass
leftTemplates = []
for t in r.workflowleftpanel:
try:
template.loader.get_template(t)
leftTemplates.append(t)
except template.TemplateDoesNotExist:
pass
return render(request, return render(request,
'workflow.html', 'workflow.html',
@@ -6023,6 +6076,8 @@ def workout_workflow_view(request,id):
'leftTemplates':leftTemplates, 'leftTemplates':leftTemplates,
'charts':charts, 'charts':charts,
'workout':row, 'workout':row,
'mapscript':mapscript,
'mapdiv':mapdiv,
'statcharts':statcharts, 'statcharts':statcharts,
}) })
@@ -6142,6 +6197,19 @@ def workout_flexchart3_view(request,*args,**kwargs):
else: else:
workstrokesonly = False workstrokesonly = False
if not promember:
for name,d in rowingmetrics:
if d['type'] != 'basic':
if xparam == name:
xparam = 'time'
messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member')
if yparam1 == name:
yparam1 = 'pace'
messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member')
if yparam2 == name:
yparam2 = 'spm'
messages.info(request,'To use '+d['verbose_name']+', you have to be Pro member')
# create interactive plot # create interactive plot
try: try:
script,div,js_resources,css_resources,workstrokesonly = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1, script,div,js_resources,css_resources,workstrokesonly = interactive_flex_chart2(id,xparam=xparam,yparam1=yparam1,
@@ -7533,13 +7601,17 @@ def workout_upload_view(request,
'make_plot':False, 'make_plot':False,
'upload_to_C2':False, 'upload_to_C2':False,
'plottype':'timeplot', 'plottype':'timeplot',
'landingpage':'workout_edit_view',
}, },
docformoptions={ docformoptions={
'workouttype':'rower', 'workouttype':'rower',
}): }):
r = getrower(request.user)
if 'uploadoptions' in request.session: if 'uploadoptions' in request.session:
uploadoptions = request.session['uploadoptions'] uploadoptions = request.session['uploadoptions']
uploadoptions['landingpage'] = r.defaultlandingpage
else: else:
request.session['uploadoptions'] = uploadoptions request.session['uploadoptions'] = uploadoptions
@@ -7568,36 +7640,40 @@ def workout_upload_view(request,
plottype = 'timeplot' plottype = 'timeplot'
try: try:
upload_toc2 = uploadoptions['upload_to_C2'] landingpage = uploadoptions['landingpage']
except KeyError: except KeyError:
upload_toc2 = False landingpage = r.defaultlandingpage
try: try:
upload_tostrava = uploadoptions['upload_to_Strava'] upload_to_c2 = uploadoptions['upload_to_C2']
except KeyError: except KeyError:
upload_tostrava = False upload_to_c2 = False
try: try:
upload_tost = uploadoptions['upload_to_SportTracks'] upload_to_strava = uploadoptions['upload_to_Strava']
except KeyError: except KeyError:
upload_tost = False upload_to_strava = False
try: try:
upload_tork = uploadoptions['upload_to_RunKeeper'] upload_to_st = uploadoptions['upload_to_SportTracks']
except KeyError: except KeyError:
upload_tork = False upload_to_st = False
try: try:
upload_toua = uploadoptions['upload_to_MapMyFitness'] upload_to_rk = uploadoptions['upload_to_RunKeeper']
except KeyError: except KeyError:
upload_toua = False upload_to_rk = False
try: try:
upload_totp = uploadoptions['upload_to_TrainingPeaks'] upload_to_ua = uploadoptions['upload_to_MapMyFitness']
except KeyError: except KeyError:
upload_totp = False upload_to_ua = False
try:
upload_to_tp = uploadoptions['upload_to_TrainingPeaks']
except KeyError:
upload_to_tp = False
r = getrower(request.user)
if request.method == 'POST': if request.method == 'POST':
form = DocumentsForm(request.POST,request.FILES) form = DocumentsForm(request.POST,request.FILES)
optionsform = UploadOptionsForm(request.POST) optionsform = UploadOptionsForm(request.POST)
@@ -7622,6 +7698,7 @@ def workout_upload_view(request,
upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness'] upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness']
upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks'] upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks']
makeprivate = optionsform.cleaned_data['makeprivate'] makeprivate = optionsform.cleaned_data['makeprivate']
landingpage = optionsform.cleaned_data['landingpage']
uploadoptions = { uploadoptions = {
'makeprivate':makeprivate, 'makeprivate':makeprivate,
@@ -7633,6 +7710,7 @@ def workout_upload_view(request,
'upload_to_RunKeeper':upload_to_rk, 'upload_to_RunKeeper':upload_to_rk,
'upload_to_MapMyFitness':upload_to_ua, 'upload_to_MapMyFitness':upload_to_ua,
'upload_to_TrainingPeaks':upload_to_tp, 'upload_to_TrainingPeaks':upload_to_tp,
'landingpage':r.defaultlandingpage,
} }
@@ -7756,7 +7834,7 @@ def workout_upload_view(request,
else: else:
messages.error(request,message) messages.error(request,message)
url = reverse(workout_edit_view, url = reverse(landingpage,
kwargs = { kwargs = {
'id':w.id, 'id':w.id,
}) })
@@ -8474,10 +8552,10 @@ def rower_favoritecharts_view(request):
if request.method == 'POST': if request.method == 'POST':
favorites_formset = FavoriteChartFormSet(request.POST) favorites_formset = FavoriteChartFormSet(request.POST)
if favorites_formset.is_valid(): if favorites_formset.is_valid():
new_instances = [] new_instances = []
for favorites_form in favorites_formset: for favorites_form in favorites_formset:
print 'mies'
yparam1 = favorites_form.cleaned_data.get('yparam1') yparam1 = favorites_form.cleaned_data.get('yparam1')
yparam2 = favorites_form.cleaned_data.get('yparam2') yparam2 = favorites_form.cleaned_data.get('yparam2')
xparam = favorites_form.cleaned_data.get('xparam') xparam = favorites_form.cleaned_data.get('xparam')
@@ -8503,7 +8581,6 @@ def rower_favoritecharts_view(request):
except IntegrityError: except IntegrityError:
message = "something went wrong" message = "something went wrong"
messages.error(request,message) messages.error(request,message)
else: else:
favorites_formset = FavoriteChartFormSet(initial=favorites_data) favorites_formset = FavoriteChartFormSet(initial=favorites_data)
@@ -8724,6 +8801,7 @@ def rower_edit_view(request,message=""):
first_name = ucd['first_name'] first_name = ucd['first_name']
last_name = ucd['last_name'] last_name = ucd['last_name']
email = ucd['email'] email = ucd['email']
defaultlandingpage = cd['defaultlandingpage']
weightcategory = cd['weightcategory'] weightcategory = cd['weightcategory']
getemailnotifications = cd['getemailnotifications'] getemailnotifications = cd['getemailnotifications']
defaulttimezone=cd['defaulttimezone'] defaulttimezone=cd['defaulttimezone']
@@ -8740,6 +8818,7 @@ def rower_edit_view(request,message=""):
r.defaulttimezone=defaulttimezone r.defaulttimezone=defaulttimezone
r.weightcategory = weightcategory r.weightcategory = weightcategory
r.getemailnotifications = getemailnotifications r.getemailnotifications = getemailnotifications
r.defaultlandingpage = defaultlandingpage
r.save() r.save()
form = RowerForm(instance=r) form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r) powerform = RowerPowerForm(instance=r)
+3
View File
@@ -261,6 +261,9 @@ TP_CLIENT_SECRET = CFG["tp_client_secret"]
TP_REDIRECT_URI = CFG["tp_redirect_uri"] TP_REDIRECT_URI = CFG["tp_redirect_uri"]
TP_CLIENT_KEY = TP_CLIENT_ID TP_CLIENT_KEY = TP_CLIENT_ID
# Full Site URL
SITE_URL = "https://rowsandall.com"
# RQ stuff # RQ stuff
RQ_QUEUES = { RQ_QUEUES = {
+2
View File
@@ -76,6 +76,8 @@ LOGIN_REDIRECT_URL = '/rowers/list-workouts/'
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies"
SITE_URL = "http://localhost:8000"
#EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' #EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
#EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' #EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'