diff --git a/rowers/admin.py b/rowers/admin.py index 4f1f9aee..2b156476 100644 --- a/rowers/admin.py +++ b/rowers/admin.py @@ -4,8 +4,9 @@ from django.contrib.auth.models import User from .models import Rower, Workout,GraphImage,FavoriteChart,SiteAnnouncement -# Register your models here. +# Register your models here so you can use them in the Admin module +# Rower details directly under the User class RowerInline(admin.StackedInline): model = Rower can_delete = False diff --git a/rowers/apps.py b/rowers/apps.py index 86eb5861..c41466d3 100644 --- a/rowers/apps.py +++ b/rowers/apps.py @@ -2,6 +2,6 @@ from __future__ import unicode_literals from django.apps import AppConfig - +# Store metadata for the app class RowersConfig(AppConfig): name = 'rowers' diff --git a/rowers/celery.py b/rowers/celery.py index 9f1f306f..7eb1d00e 100644 --- a/rowers/celery.py +++ b/rowers/celery.py @@ -4,6 +4,9 @@ import os from celery import Celery +# Only used for testing with Celery on localhost. RQ is not available +# on Windows, so I use Celery on my notebook. + # set the default Django settings module for the 'celery' program. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rowsandall_app.settings') diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py index 48e86794..c42855e8 100644 --- a/rowers/dataprepnodjango.py +++ b/rowers/dataprepnodjango.py @@ -1,3 +1,5 @@ +# This is Data prep used for testing purposes (no Django environment) +# Uses the debug SQLite database for stroke data from rowingdata import rowingdata as rrdata from rowingdata import rower as rrower diff --git a/rowers/mailprocessing.py b/rowers/mailprocessing.py index d53c1eee..403b5a2d 100644 --- a/rowers/mailprocessing.py +++ b/rowers/mailprocessing.py @@ -1,3 +1,4 @@ +# Processes emails sent to workouts@rowsandall.com import time from django.conf import settings from rowers.tasks import handle_sendemail_unrecognized @@ -21,7 +22,9 @@ from scipy.signal import savgol_filter import zipfile import os +import rowers.dataprep as dataprep +# Sends a confirmation with a link to the workout def send_confirm(u,name,link): fullemail = u.email subject = 'Workout added: '+name @@ -38,6 +41,7 @@ def send_confirm(u,name,link): return 1 +# Reads a "rowingdata" object, plus some error protections def rdata(file,rower=rrower()): try: res = rrdata(file,rower=rower) @@ -49,13 +53,18 @@ def rdata(file,rower=rrower()): return res +# 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: @@ -73,7 +82,6 @@ def processattachments(): try: wid = [make_new_workout_from_email(rr,a.document,name)] res += wid - print wid link = 'https://rowsandall.com/rowers/workout/'+str(wid[0])+'/edit' dd = send_confirm(u,name,link) except: @@ -92,6 +100,7 @@ def processattachments(): # 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: @@ -99,6 +108,7 @@ def processattachments(): return res +# As above, but with some print commands for debugging purposes def processattachments_debug(): res = [] attachments = MessageAttachment.objects.all() @@ -144,7 +154,9 @@ def processattachments_debug(): 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' f2 = f2.name @@ -305,6 +317,16 @@ def make_new_workout_from_email(rr,f2,name,cntr=0): startdatetime=row.rowdatetime) w.save() + + print w.id + print row.df.info() + + # put stroke data in database + res = dataprep.dataprep(row.df,id=w.id, + bands=True,barchart=True, + otwpower=True,empower=True) + + return w.id diff --git a/rowers/ownapistuff.py b/rowers/ownapistuff.py index 3ec7088e..8f5ec305 100644 --- a/rowers/ownapistuff.py +++ b/rowers/ownapistuff.py @@ -1,3 +1,5 @@ +# Interactions with Rowsandall.com API. Not fully complete. + # Python import oauth2 as oauth import cgi diff --git a/rowers/permissions.py b/rowers/permissions.py index 67afe16c..d4d6d820 100644 --- a/rowers/permissions.py +++ b/rowers/permissions.py @@ -1,3 +1,5 @@ +# Defines permissions for objects (API related) + from rest_framework import permissions from rowers.models import Rower diff --git a/rowers/plots.py b/rowers/plots.py index 0ea1b6be..8193a088 100644 --- a/rowers/plots.py +++ b/rowers/plots.py @@ -3,6 +3,7 @@ import matplotlib.pyplot as plt import numpy as np +# Make pace ticks for pace axis '2:00', '1:50', etc def format_pace_tick(x,pos=None): min=int(x/60) sec=int(x-min*60.) @@ -10,6 +11,7 @@ def format_pace_tick(x,pos=None): template='%d:%s' return template % (min,sec_str) +# Returns a pace string from a pace in seconds/500m, '1:45.4' def format_pace(x,pos=None): if isinf(x) or isnan(x): x=0 @@ -24,6 +26,7 @@ def format_pace(x,pos=None): return str1 +# Returns a time string from a time in seconds def format_time(x,pos=None): @@ -37,11 +40,13 @@ def format_time(x,pos=None): return str1 +# Formatting the distance tick marks def format_dist_tick(x,pos=None): km = x/1000. template='%6.3f' return template % (km) +# Formatting the time tick marks (h:mm) def format_time_tick(x,pos=None): hour=int(x/3600) min=int((x-hour*3600.)/60) @@ -49,6 +54,11 @@ def format_time_tick(x,pos=None): template='%d:%s' return template % (hour,min_str) +# Utility to select reasonable y axis range +# Basically the data range plus some padding, but with ultimate +# you can set the slowest paces to fall off the axis. +# Useful for OTW rowing where you sometimes stops and pace runs out of +# the boundaries def y_axis_range(ydata,miny=0,padding=.1,ultimate=[-1e9,1e9]): # ydata must by a numpy array @@ -86,6 +96,7 @@ def y_axis_range(ydata,miny=0,padding=.1,ultimate=[-1e9,1e9]): return [yrangemin,yrangemax] +# Make a plot (this one is only used for testing) def mkplot(row,title): df = row.df diff --git a/rowers/serializers.py b/rowers/serializers.py index 8842d18d..c552e2ed 100644 --- a/rowers/serializers.py +++ b/rowers/serializers.py @@ -1,3 +1,6 @@ +# Serializers. Defines which fields from an object get to the JSON object +# Also optionally define POST, PATCH methods (create, update) + from rest_framework import serializers from rowers.models import Workout,Rower,StrokeData,FavoriteChart @@ -104,6 +107,7 @@ class WorkoutSerializer(serializers.ModelSerializer): instance.save() return instance +# This is just a fake one for URL registration purposes class StrokeDataSerializer(serializers.Serializer): workoutid = serializers.IntegerField strokedata = serializers.JSONField diff --git a/rowers/tasks.py b/rowers/tasks.py index 8a7b2019..3fc8685a 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -21,12 +21,12 @@ from rowers.dataprepnodjango import update_strokedata from django.core.mail import send_mail, BadHeaderError,EmailMessage - +# testing task @app.task def add(x, y): return x + y - +# send email to me when an unrecognized file is uploaded @app.task def handle_sendemail_unrecognized(unrecognizedfile,useremail): @@ -51,7 +51,7 @@ def handle_sendemail_unrecognized(unrecognizedfile,useremail): os.remove(unrecognizedfile) return 1 - +# Send email with TCX attachment @app.task def handle_sendemailtcx(first_name,last_name,email,tcxfile): @@ -75,6 +75,7 @@ def handle_sendemailtcx(first_name,last_name,email,tcxfile): os.remove(tcxfile) return 1 +# Send email with CSV attachment @app.task def handle_sendemailcsv(first_name,last_name,email,csvfile): @@ -104,6 +105,7 @@ def handle_sendemailcsv(first_name,last_name,email,csvfile): return 1 +# Calculate wind and stream corrections for OTW rowing @app.task def handle_otwsetpower(f1,boattype,weightvalue, first_name,last_name,email,workoutid, @@ -129,7 +131,7 @@ def handle_otwsetpower(f1,boattype,weightvalue, except KeyError: rg = rowingdata.getrigging('static/rigging/1x.txt') - # do calculation + # do calculation, but do not overwrite NK Empower Power data powermeasured = False try: w = rowdata.df['wash'] @@ -166,6 +168,7 @@ def handle_otwsetpower(f1,boattype,weightvalue, return 1 +# This function generates all the static (PNG image) plots @app.task def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename): hrmax = hrdata['hrmax'] @@ -222,5 +225,6 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename): return imagename +# Another simple task for debugging purposes def add2(x,y): return x+y diff --git a/rowers/weather.py b/rowers/weather.py index 272dd7ce..74e2bdd6 100644 --- a/rowers/weather.py +++ b/rowers/weather.py @@ -8,9 +8,8 @@ from rowers.models import Rower, Workout from rowsandall_app.settings import FORECAST_IO_KEY - +# Get weather data from the DarkSky API def get_weather_data(long,lat,unixtime): -# url = "https://api.forecast.io/forecast/"+FORECAST_IO_KEY+"/" url = "https://api.darksky.net/forecast/"+FORECAST_IO_KEY+"/" url += str(long)+","+str(lat)+","+str(unixtime) @@ -21,6 +20,7 @@ def get_weather_data(long,lat,unixtime): else: return 0 +# Get wind data (and translate from knots to m/s) def get_wind_data(lat,long,unixtime): data = get_weather_data(lat,long,unixtime) if data: @@ -39,6 +39,7 @@ def get_wind_data(lat,long,unixtime): try: temperature = data['currently']['temperature'] + # Temp is given in Fahrenheit, so convert to Celsius for Europeans temperaturec = (temperature-32.)*(5./9.) temperaturec = int(10*temperaturec)/10. except KeyError: @@ -54,7 +55,8 @@ def get_wind_data(lat,long,unixtime): windbearing = 0 message = 'Not able to get weather data' - # apply Hellman's coefficient for neutral air above human inhabitated areas + # apply Hellman's coefficient for neutral air above human + # inhabitated areas windspeed = windspeed*(0.1)**0.34 windspeed = 0.01*int(100*windspeed)