Merge branch 'feature/cleanup' into develop
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,110 +0,0 @@
|
||||
TimeStamp (sec), activityIdx, lapIdx, pointIdx, ElapsedTime (sec), Horizontal (meters), Stroke500mPace (sec/500m), Cadence (stokes/min), HRCur (bpm), Power (watts), Calories (kCal), Speed (m/sec), StrokeCount, StrokeDistance (meters), DriveLength (meters), DriveTime (ms), StrokeRecoveryTime (ms), WorkPerStroke (joules), AverageDriveForce (lbs), PeakDriveForce (lbs), DragFactor
|
||||
1461004356.11047, 0, 0, 0, 1.34, 5.8, 124.45, 0, 112, 184, 0, 4.033, 1, 2.67, 1.18, 660, 430, 0, 148.4, 228.3, 0
|
||||
1461004357.37363, 0, 0, 1, 2.64, 11.9, 107.86, 52, 114, 283, 0, 4.657, 2, 5.4, 1.18, 530, 570, 0, 134.4, 209.7, 110
|
||||
1461004358.86966, 0, 0, 2, 4.11, 19.5, 102.66, 46, 118, 328, 1, 4.894, 3, 6.36, 1.33, 520, 730, 0, 137.8, 226.2, 110
|
||||
1461004360.40359, 0, 0, 3, 5.69, 28, 95.64, 40, 120, 406, 1, 5.254, 4, 7.8, 1.45, 540, 830, 0, 139, 219.9, 111
|
||||
1461004361.99023, 0, 0, 4, 7.26, 36.7, 92.38, 38, 125, 451, 2, 5.441, 5, 8.59, 1.48, 530, 820, 0, 141, 223.7, 110
|
||||
1461004363.64047, 0, 0, 5, 8.89, 45.7, 90.84, 38, 132, 475, 3, 5.534, 6, 8.66, 1.48, 540, 890, 0, 132.9, 245.3, 110
|
||||
1461004365.29068, 0, 0, 6, 10.55, 54.7, 91.31, 37, 135, 467, 4, 5.505, 7, 8.86, 1.42, 510, 930, 0, 138.6, 222.8, 110
|
||||
1461004367.00093, 0, 0, 7, 12.24, 64, 92.1, 36, 141, 455, 5, 5.458, 8, 9.21, 1.48, 540, 930, 0, 134.8, 226.9, 110
|
||||
1461004368.65075, 0, 0, 8, 13.93, 73.1, 92.54, 36, 144, 455, 5, 5.432, 9, 9.14, 1.48, 550, 930, 0, 131.2, 234.4, 110
|
||||
1461004370.51044, 0, 0, 9, 15.79, 82.9, 93.32, 36, 152, 438, 6, 5.386, 10, 9.07, 1.45, 550, 1110, 0, 124.9, 211.2, 110
|
||||
1461004372.4003, 0, 0, 10, 17.68, 92.6, 95.87, 32, 156, 403, 7, 5.242, 11, 9.79, 1.42, 550, 1150, 0, 123.9, 209.1, 110
|
||||
1461004374.35136, 0, 0, 11, 19.63, 102.5, 98.2, 31, 160, 375, 8, 5.117, 12, 9.86, 1.45, 570, 1160, 0, 121.1, 210.9, 110
|
||||
1461004376.27099, 0, 0, 12, 21.55, 112.1, 99.61, 31, 164, 359, 9, 5.044, 13, 9.79, 1.45, 590, 1150, 0, 118.8, 219.1, 110
|
||||
1461004378.07035, 0, 0, 13, 23.34, 121.2, 100.17, 31, 166, 353, 10, 5.016, 14, 9.79, 1.48, 590, 1150, 0, 122.6, 208.4, 110
|
||||
1461004380.05067, 0, 0, 14, 25.32, 131.2, 99.45, 32, 168, 361, 10, 5.052, 15, 9.51, 1.45, 580, 1090, 0, 126.1, 218, 110
|
||||
1461004382.00372, 0, 0, 15, 27.24, 140.9, 98.98, 32, 169, 366, 11, 5.076, 16, 9.51, 1.45, 580, 1130, 0, 119.5, 204.9, 110
|
||||
1461004383.89027, 0, 0, 16, 29.16, 150.5, 99.84, 31, 171, 357, 12, 5.032, 17, 9.58, 1.42, 570, 1140, 0, 120.1, 213.3, 110
|
||||
1461004385.68965, 0, 0, 17, 30.96, 159.6, 100.5, 31, 172, 350, 13, 4.999, 18, 9.79, 1.51, 610, 1140, 0, 117.3, 210.8, 110
|
||||
1461004387.79028, 0, 0, 18, 33.05, 169.9, 100.34, 32, 173, 352, 14, 5.007, 19, 9.44, 1.45, 580, 1190, 0, 119.7, 198.6, 110
|
||||
1461004389.74033, 0, 0, 19, 35.02, 179.8, 100.57, 30, 174, 349, 15, 4.996, 20, 9.87, 1.45, 580, 1180, 0, 126.1, 212.6, 110
|
||||
1461004391.6934, 0, 0, 20, 36.97, 189.6, 100.2, 30, 174, 353, 15, 5.014, 21, 9.86, 1.45, 580, 1160, 0, 124.7, 204.5, 110
|
||||
1461004393.70408, 0, 0, 21, 38.96, 199.5, 100.1, 31, 175, 354, 16, 5.019, 22, 9.78, 1.45, 580, 1190, 0, 120.2, 202.3, 110
|
||||
1461004395.67953, 0, 0, 22, 40.9, 209.2, 100.99, 30, 175, 345, 17, 4.975, 23, 9.85, 1.45, 590, 1160, 0, 119.7, 197.6, 110
|
||||
1461004397.41974, 0, 1, 0, 42.69, 218.2, 101.05, 31, 176, 344, 18, 4.972, 24, 9.71, 1.45, 600, 1160, 0, 121.1, 202.9, 110
|
||||
1461004399.46009, 0, 1, 1, 44.72, 228.3, 100.13, 32, 176, 354, 19, 5.018, 25, 9.51, 1.45, 580, 1130, 0, 121.8, 207.3, 110
|
||||
1461004401.3506, 0, 1, 2, 46.62, 237.8, 100.06, 31, 176, 355, 19, 5.021, 26, 9.64, 1.45, 580, 1110, 0, 116.5, 207.4, 110
|
||||
1461004403.29953, 0, 1, 3, 48.42, 246.9, 100.65, 31, 176, 348, 20, 4.992, 27, 9.57, 1.48, 600, 1110, 0, 123, 211.5, 110
|
||||
1461004405.22022, 0, 1, 4, 50.47, 257.2, 99.69, 32, 177, 359, 21, 5.04, 28, 9.58, 1.45, 580, 1130, 0, 124.3, 208.8, 110
|
||||
1461004407.19972, 0, 1, 5, 52.39, 266.9, 99.47, 31, 177, 361, 22, 5.051, 29, 9.65, 1.45, 580, 1150, 0, 120.4, 207.4, 110
|
||||
1461004408.9101, 0, 1, 6, 54.17, 276, 99.87, 31, 177, 357, 23, 5.031, 30, 9.72, 1.45, 580, 1150, 0, 126.3, 210.5, 110
|
||||
1461004411.00986, 0, 1, 7, 56.23, 286.3, 99.22, 32, 177, 364, 23, 5.064, 31, 9.51, 1.42, 560, 1170, 0, 125.6, 215.2, 110
|
||||
1461004412.77946, 0, 1, 8, 58.04, 295.5, 99.56, 31, 177, 360, 24, 5.047, 32, 9.86, 1.45, 580, 1170, 0, 121.8, 214.4, 110
|
||||
1461004414.82028, 0, 1, 9, 60.1, 305.8, 99.94, 31, 178, 356, 25, 5.027, 33, 9.71, 1.45, 600, 1160, 0, 120.6, 210.2, 110
|
||||
1461004416.8312, 0, 1, 10, 62.04, 315.5, 99.94, 31, 178, 356, 26, 5.028, 34, 9.78, 1.45, 580, 1160, 0, 124, 222.3, 110
|
||||
1461004418.72014, 0, 1, 11, 63.99, 325.3, 99.87, 31, 178, 357, 27, 5.031, 35, 9.78, 1.45, 580, 1140, 0, 123.7, 208.6, 110
|
||||
1461004420.67038, 0, 1, 12, 65.93, 335.1, 99.71, 31, 178, 358, 28, 5.039, 36, 9.79, 1.48, 590, 1140, 0, 121.8, 218.6, 109
|
||||
1461004422.49949, 0, 1, 13, 67.76, 344.4, 99.81, 31, 179, 357, 28, 5.034, 37, 9.71, 1.45, 590, 1140, 0, 121.9, 212.9, 110
|
||||
1461004424.56946, 0, 1, 14, 69.81, 354.6, 99.91, 31, 179, 356, 29, 5.029, 38, 9.79, 1.42, 570, 1170, 0, 122.3, 208.7, 110
|
||||
1461004426.45946, 0, 1, 15, 71.72, 364.2, 100.48, 31, 179, 350, 30, 5, 39, 9.78, 1.45, 580, 1120, 0, 122.7, 209, 110
|
||||
1461004428.22943, 0, 1, 16, 73.51, 373.3, 99.94, 32, 179, 356, 31, 5.028, 40, 9.57, 1.45, 580, 1120, 0, 123.5, 229.9, 110
|
||||
1461004430.30156, 0, 1, 17, 75.51, 383.3, 99.42, 32, 179, 362, 32, 5.054, 41, 9.5, 1.42, 560, 1120, 0, 124.8, 208.4, 109
|
||||
1461004432.16011, 0, 1, 18, 77.41, 393, 99.26, 32, 179, 363, 32, 5.062, 42, 9.63, 1.45, 580, 1130, 0, 122.9, 226.6, 110
|
||||
1461004434.10936, 0, 1, 19, 79.34, 402.7, 99.5, 32, 179, 361, 33, 5.05, 43, 9.56, 1.42, 560, 1150, 0, 124.9, 218.3, 110
|
||||
1461004436.0894, 0, 2, 0, 81.3, 412.5, 99.64, 31, 179, 359, 34, 5.043, 44, 9.71, 1.42, 570, 1180, 0, 122.6, 212.9, 109
|
||||
1461004437.86025, 0, 2, 1, 83.1, 421.6, 100.31, 31, 180, 352, 35, 5.009, 45, 9.84, 1.45, 600, 1180, 0, 120.4, 215.3, 110
|
||||
1461004439.89998, 0, 2, 2, 85.14, 431.8, 100.11, 31, 180, 354, 36, 5.019, 46, 9.78, 1.48, 590, 1110, 0, 123.2, 213.3, 110
|
||||
1461004441.81943, 0, 2, 3, 87.07, 441.6, 99.56, 32, 180, 360, 37, 5.047, 47, 9.51, 1.42, 570, 1160, 0, 123.4, 209.6, 110
|
||||
1461004443.77011, 0, 2, 4, 89.02, 451.3, 99.72, 31, 180, 358, 37, 5.038, 48, 9.79, 1.45, 590, 1160, 0, 121.4, 212.2, 110
|
||||
1461004445.59931, 0, 2, 5, 90.83, 460.6, 100.05, 31, 180, 355, 38, 5.022, 49, 9.71, 1.42, 570, 1160, 0, 126.1, 214.7, 110
|
||||
1461004447.6099, 0, 2, 6, 92.85, 470.7, 99.57, 31, 180, 360, 39, 5.046, 50, 9.78, 1.45, 580, 1120, 0, 126, 216.2, 110
|
||||
1461004449.55928, 0, 2, 7, 94.78, 480.5, 99.2, 32, 181, 364, 40, 5.065, 51, 9.63, 1.45, 590, 1150, 0, 122, 229.7, 109
|
||||
1461004451.4801, 0, 2, 8, 96.72, 490.3, 99.47, 31, 181, 361, 41, 5.051, 52, 9.7, 1.42, 560, 1170, 0, 124.9, 221.2, 110
|
||||
1461004453.33954, 0, 2, 9, 98.57, 499.5, 100.01, 31, 181, 355, 42, 5.024, 53, 9.85, 1.45, 580, 1170, 0, 119.2, 209, 109
|
||||
1461004455.4099, 0, 2, 10, 100.66, 509.9, 100.76, 31, 181, 347, 42, 4.986, 54, 9.77, 1.45, 590, 1180, 0, 121.6, 224.3, 110
|
||||
1461004457.36002, 0, 2, 11, 102.6, 519.6, 100.59, 31, 181, 349, 43, 4.995, 55, 9.78, 1.42, 580, 1160, 0, 123.1, 212.1, 109
|
||||
1461004459.31064, 0, 2, 12, 104.58, 529.5, 100.3, 31, 181, 352, 44, 5.009, 56, 9.77, 1.45, 580, 1190, 0, 122.6, 216.3, 109
|
||||
1461004461.16922, 0, 2, 13, 106.42, 538.7, 100.5, 30, 181, 350, 45, 4.999, 57, 9.92, 1.45, 580, 1190, 0, 120.4, 213.9, 110
|
||||
1461004463.26918, 0, 2, 14, 108.49, 549, 100.79, 31, 181, 347, 46, 4.985, 58, 9.71, 1.45, 580, 1150, 0, 121.7, 216.6, 110
|
||||
1461004465.10101, 0, 2, 15, 110.36, 558.4, 100.66, 31, 181, 348, 46, 4.991, 59, 9.63, 1.42, 570, 1110, 0, 120.4, 218.1, 109
|
||||
1461004467.01999, 0, 2, 16, 112.26, 567.8, 100.59, 32, 181, 349, 47, 4.995, 60, 9.41, 1.42, 570, 1120, 0, 121.8, 227.8, 109
|
||||
1461004468.90954, 0, 2, 17, 114.14, 577.3, 100.48, 31, 181, 350, 48, 5, 61, 9.55, 1.45, 600, 1090, 0, 117.8, 218, 109
|
||||
1461004470.80092, 0, 2, 18, 116.06, 586.9, 100.11, 32, 181, 354, 49, 5.019, 62, 9.49, 1.48, 610, 1110, 0, 115, 209.3, 110
|
||||
1461004472.72009, 0, 2, 19, 117.97, 596.5, 100.44, 32, 181, 351, 50, 5.002, 63, 9.49, 1.45, 580, 1130, 0, 120.6, 209.1, 110
|
||||
1461004474.60979, 0, 2, 20, 119.87, 606, 100.28, 31, 182, 352, 50, 5.01, 64, 9.57, 1.42, 570, 1120, 0, 120, 217.9, 109
|
||||
1461004476.4385, 0, 3, 0, 121.65, 614.9, 100.89, 32, 181, 346, 51, 4.98, 65, 9.41, 1.39, 570, 1120, 0, 118.6, 211.9, 110
|
||||
1461004478.47986, 0, 3, 1, 123.73, 625.1, 101.01, 31, 182, 345, 52, 4.974, 66, 9.71, 1.48, 600, 1130, 0, 115.3, 219.1, 110
|
||||
1461004480.27929, 0, 3, 2, 125.52, 634.1, 101.24, 31, 182, 342, 53, 4.962, 67, 9.57, 1.45, 590, 1130, 0, 121.4, 219, 110
|
||||
1461004482.37966, 0, 3, 3, 127.6, 644.4, 100.67, 32, 181, 348, 54, 4.991, 68, 9.42, 1.42, 570, 1190, 0, 118.7, 218.1, 109
|
||||
1461004484.29991, 0, 3, 4, 129.56, 654.1, 101.34, 30, 181, 341, 54, 4.958, 69, 9.85, 1.45, 590, 1140, 0, 121.6, 220.8, 110
|
||||
1461004486.16014, 0, 3, 5, 131.41, 663.4, 100.79, 31, 181, 347, 55, 4.985, 70, 9.57, 1.42, 570, 1090, 0, 122.9, 210.4, 110
|
||||
1461004488.07993, 0, 3, 6, 133.33, 673, 100.32, 32, 181, 352, 56, 5.008, 71, 9.35, 1.42, 570, 1140, 0, 118.5, 206.8, 109
|
||||
1461004490.03001, 0, 3, 7, 135.27, 682.6, 100.98, 31, 181, 345, 57, 4.975, 72, 9.56, 1.42, 580, 1150, 0, 116.9, 218.1, 109
|
||||
1461004491.97913, 0, 3, 8, 137.22, 692.3, 101.5, 31, 181, 340, 58, 4.95, 73, 9.63, 1.45, 590, 1150, 0, 118.7, 221.9, 110
|
||||
1461004493.77931, 0, 3, 9, 138.99, 701.1, 101.41, 31, 181, 341, 58, 4.954, 74, 9.57, 1.42, 580, 1150, 0, 119, 205.9, 110
|
||||
1461004495.78987, 0, 3, 10, 141.04, 711.2, 101.29, 31, 181, 342, 59, 4.96, 75, 9.64, 1.48, 600, 1100, 0, 116.2, 222.9, 110
|
||||
1461004497.64987, 0, 3, 11, 142.92, 720.6, 101.08, 32, 181, 344, 60, 4.971, 76, 9.36, 1.42, 590, 1090, 0, 120.4, 210.2, 110
|
||||
1461004499.56987, 0, 3, 12, 144.81, 730, 100.32, 32, 182, 352, 61, 5.008, 77, 9.35, 1.42, 570, 1130, 0, 119.2, 218.4, 109
|
||||
1461004501.45977, 0, 3, 13, 146.71, 739.5, 100.77, 32, 181, 347, 61, 4.986, 78, 9.49, 1.42, 570, 1110, 0, 119.5, 210.1, 109
|
||||
1461004503.31974, 0, 3, 14, 148.59, 748.9, 100.91, 32, 182, 346, 62, 4.979, 79, 9.41, 1.42, 590, 1100, 0, 117.6, 217.4, 109
|
||||
1461004505.20973, 0, 3, 15, 150.44, 758.1, 100.85, 32, 181, 346, 63, 4.982, 80, 9.34, 1.42, 590, 1090, 0, 114.9, 212.3, 109
|
||||
1461004507.04, 0, 3, 16, 152.26, 767.2, 100.96, 32, 181, 345, 64, 4.976, 81, 9.27, 1.42, 580, 1030, 0, 118.6, 222, 109
|
||||
1461004508.78319, 0, 3, 17, 154, 775.9, 100.29, 33, 182, 352, 65, 5.01, 82, 9.06, 1.42, 570, 1030, 0, 116.3, 208.4, 109
|
||||
1461004510.72907, 0, 3, 18, 155.96, 785.7, 100.51, 32, 182, 350, 65, 4.999, 83, 9.42, 1.48, 610, 1050, 0, 114.2, 220.8, 110
|
||||
1461004512.58978, 0, 3, 19, 157.79, 794.9, 100.32, 33, 182, 352, 66, 5.008, 84, 9.07, 1.39, 560, 1070, 0, 116.1, 211.2, 109
|
||||
1461004514.42009, 0, 3, 20, 159.65, 804, 101.03, 32, 182, 344, 67, 4.973, 85, 9.27, 1.45, 590, 1040, 0, 112.5, 199.5, 109
|
||||
1461004516.18978, 0, 4, 0, 161.46, 813, 101.62, 33, 182, 338, 68, 4.944, 86, 8.98, 1.39, 580, 1050, 0, 111.6, 200.2, 109
|
||||
1461004518.07996, 0, 4, 1, 163.33, 822.2, 101.73, 33, 181, 337, 68, 4.938, 87, 8.99, 1.39, 560, 1100, 0, 114.6, 206.4, 109
|
||||
1461004519.90999, 0, 4, 2, 165.15, 831.2, 102.27, 32, 181, 332, 69, 4.912, 88, 9.2, 1.39, 570, 1050, 0, 115.6, 204.9, 110
|
||||
1461004521.74184, 0, 4, 3, 166.98, 840.2, 101.9, 32, 182, 336, 70, 4.93, 89, 9.14, 1.45, 590, 1010, 0, 110, 200, 110
|
||||
1461004523.59979, 0, 4, 4, 168.81, 849.2, 101.98, 34, 182, 335, 71, 4.926, 90, 8.71, 1.36, 550, 1070, 0, 114.6, 206, 110
|
||||
1461004525.36992, 0, 4, 5, 170.62, 858, 102.27, 32, 181, 332, 71, 4.912, 91, 9.07, 1.39, 570, 1040, 0, 111.7, 185.3, 109
|
||||
1461004527.19977, 0, 4, 6, 172.43, 866.9, 102.47, 33, 181, 330, 72, 4.902, 92, 8.92, 1.39, 570, 1040, 0, 114, 195, 109
|
||||
1461004529.06276, 0, 4, 7, 174.32, 876.2, 102.31, 33, 181, 332, 73, 4.91, 93, 8.99, 1.42, 580, 1080, 0, 110.1, 199.7, 110
|
||||
1461004530.82905, 0, 4, 8, 176.02, 884.6, 102.69, 32, 181, 328, 74, 4.892, 94, 9.14, 1.42, 580, 1080, 0, 113.9, 216.3, 110
|
||||
1461004532.75083, 0, 4, 9, 177.99, 894.1, 102.44, 33, 181, 330, 74, 4.904, 95, 8.99, 1.42, 580, 1060, 0, 108.7, 196, 109
|
||||
1461004534.39983, 0, 4, 10, 179.66, 902.4, 102.85, 32, 181, 326, 75, 4.884, 96, 9.05, 1.42, 580, 1060, 0, 114.9, 199.6, 109
|
||||
1461004536.32286, 0, 4, 11, 181.59, 911.9, 101.55, 34, 182, 339, 76, 4.947, 97, 8.85, 1.45, 590, 1000, 0, 110.6, 199, 110
|
||||
1461004538.06289, 0, 4, 12, 183.32, 920.5, 101.39, 34, 181, 341, 76, 4.955, 98, 8.78, 1.39, 580, 970, 0, 109.7, 195.2, 110
|
||||
1461004539.89296, 0, 4, 13, 185.11, 929.4, 101.35, 34, 181, 341, 77, 4.957, 99, 8.71, 1.42, 570, 1010, 0, 112.5, 207.2, 109
|
||||
1461004541.6899, 0, 4, 14, 186.8, 937.8, 101.24, 33, 181, 342, 78, 4.962, 100, 8.92, 1.42, 570, 1010, 0, 112.9, 206.9, 109
|
||||
1461004543.39974, 0, 4, 15, 188.64, 946.9, 101.37, 34, 181, 341, 79, 4.956, 101, 8.85, 1.39, 560, 940, 0, 112.1, 200.7, 110
|
||||
1461004545.11348, 0, 4, 16, 190.34, 955.4, 101, 35, 181, 345, 79, 4.975, 102, 8.42, 1.36, 560, 970, 0, 113.8, 187.5, 110
|
||||
1461004546.85166, 0, 4, 17, 192.1, 964.1, 100.78, 35, 180, 347, 80, 4.985, 103, 8.57, 1.36, 550, 1010, 0, 109.9, 194.7, 110
|
||||
1461004548.65002, 0, 4, 18, 193.91, 973, 101.83, 34, 181, 336, 81, 4.934, 104, 8.78, 1.39, 580, 1010, 0, 107.6, 188.8, 110
|
||||
1461004550.35928, 0, 4, 19, 195.6, 981.3, 102.6, 34, 181, 329, 81, 4.896, 105, 8.71, 1.36, 560, 1010, 0, 108.9, 183.6, 110
|
||||
1461004552.30971, 0, 4, 20, 197.53, 990.5, 103.54, 33, 181, 320, 82, 4.852, 106, 8.86, 1.36, 560, 1040, 0, 102.4, 187.2, 110
|
||||
1461004554.19972, 0, 4, 21, 199.44, 999.5, 105.35, 33, 180, 304, 83, 4.768, 107, 8.7, 1.33, 590, 1110, 0, 100, 181.7, 109
|
||||
1461004554.35294, 0, 4, 22, 199.56, 1000, 105.35, 33, 180, 304, 83, 4.768, 107, 8.7, 1.33, 590, 1110, 0, 100, 181.7, 109
|
||||
1461004556.03066, 0, 6, 0, 199.57, 1000, 105.35, 33, 180, 304, 83, 4.768, 107, 8.7, 1.33, 590, 1110, 0, 100, 181.7, 109
|
||||
|
@@ -1,58 +0,0 @@
|
||||
index, AverageDriveForce (lbs), Cadence (stokes/min), DriveLength (meters), DriveTime (ms), ElapsedTime (sec), HRCur (bpm), Horizontal (meters), PeakDriveForce (lbs), Power (watts), Stroke500mPace (sec/500m), StrokeDistance (meters), StrokeRecoveryTime (ms), lapIdx,TimeStamp (sec)
|
||||
0,0.0,54,0.0,0.0,2.6,106,11.8,0.0,0.0,130.7,0.0,0.0,0.0,1461602536.6
|
||||
1,0.0,54,0.0,0.0,2.6,108,11.8,0.0,0.0,108.7,0.0,0.0,0.0,1461602536.6
|
||||
2,0.0,43,0.0,0.0,4.1,110,19.7,0.0,0.0,101.3,0.0,0.0,0.0,1461602538.1
|
||||
3,0.0,40,0.0,0.0,5.7,116,28.7,0.0,0.0,93.3,0.0,0.0,0.0,1461602539.7
|
||||
4,0.0,37,0.0,0.0,7.4,122,38.0,0.0,0.0,90.6,0.0,0.0,0.0,1461602541.4
|
||||
5,0.0,36,0.0,0.0,9.0,126,47.1,0.0,0.0,90.0,0.0,0.0,0.0,1461602543.0
|
||||
6,0.0,37,0.0,0.0,10.7,132,56.4,0.0,0.0,90.2,0.0,0.0,0.0,1461602544.7
|
||||
7,0.0,36,0.0,0.0,12.4,138,65.7,0.0,0.0,90.6,0.0,0.0,0.0,1461602546.4
|
||||
8,0.0,35,0.0,0.0,14.1,142,75.0,0.0,0.0,91.2,0.0,0.0,0.0,1461602548.1
|
||||
9,0.0,37,0.0,0.0,15.5,148,83.2,0.0,0.0,90.6,0.0,0.0,0.0,1461602549.5
|
||||
10,0.0,38,0.0,0.0,17.3,151,92.8,0.0,0.0,90.2,0.0,0.0,0.0,1461602551.3
|
||||
11,0.0,37,0.0,0.0,18.9,155,101.9,0.0,0.0,90.4,0.0,0.0,0.0,1461602552.9
|
||||
12,0.0,36,0.0,0.0,20.5,159,110.5,0.0,0.0,90.9,0.0,0.0,0.0,1461602554.5
|
||||
13,0.0,36,0.0,0.0,22.2,161,120.3,0.0,0.0,90.8,0.0,0.0,0.0,1461602556.2
|
||||
14,0.0,36,0.0,0.0,23.9,164,129.5,0.0,0.0,90.8,0.0,0.0,0.0,1461602557.9
|
||||
15,0.0,36,0.0,0.0,25.6,166,138.7,0.0,0.0,91.3,0.0,0.0,0.0,1461602559.6
|
||||
16,0.0,36,0.0,0.0,27.2,166,147.7,0.0,0.0,91.9,0.0,0.0,0.0,1461602561.2
|
||||
17,0.0,36,0.0,0.0,28.9,168,156.9,0.0,0.0,92.0,0.0,0.0,0.0,1461602562.9
|
||||
18,0.0,36,0.0,0.0,30.5,169,165.7,0.0,0.0,92.0,0.0,0.0,0.0,1461602564.5
|
||||
19,0.0,37,0.0,0.0,32.2,170,174.7,0.0,0.0,91.4,0.0,0.0,0.0,1461602566.2
|
||||
20,0.0,37,0.0,0.0,33.8,171,183.6,0.0,0.0,91.4,0.0,0.0,0.0,1461602567.8
|
||||
21,0.0,37,0.0,0.0,35.4,171,192.6,0.0,0.0,91.4,0.0,0.0,0.0,1461602569.4
|
||||
22,0.0,36,0.0,0.0,37.0,172,201.2,0.0,0.0,92.0,0.0,0.0,0.0,1461602571.0
|
||||
23,0.0,36,0.0,0.0,38.7,173,210.6,0.0,0.0,92.4,0.0,0.0,0.0,1461602572.7
|
||||
24,0.0,37,0.0,0.0,40.4,173,219.5,0.0,0.0,91.9,0.0,0.0,0.0,1461602574.4
|
||||
25,0.0,37,0.0,0.0,42.0,174,228.2,0.0,0.0,92.4,0.0,0.0,0.0,1461602576.0
|
||||
26,0.0,37,0.0,0.0,43.6,174,237.1,0.0,0.0,92.5,0.0,0.0,0.0,1461602577.6
|
||||
27,0.0,36,0.0,0.0,45.2,174,245.9,0.0,0.0,93.0,0.0,0.0,0.0,1461602579.2
|
||||
28,0.0,37,0.0,0.0,46.9,175,254.9,0.0,0.0,93.4,0.0,0.0,0.0,1461602580.9
|
||||
29,0.0,36,0.0,0.0,48.5,175,263.8,0.0,0.0,92.9,0.0,0.0,0.0,1461602582.5
|
||||
30,0.0,37,0.0,0.0,50.1,175,272.4,0.0,0.0,92.3,0.0,0.0,0.0,1461602584.1
|
||||
31,0.0,38,0.0,0.0,51.6,176,280.6,0.0,0.0,92.5,0.0,0.0,0.0,1461602585.6
|
||||
32,0.0,36,0.0,0.0,53.4,176,290.1,0.0,0.0,92.8,0.0,0.0,0.0,1461602587.4
|
||||
33,0.0,36,0.0,0.0,55.1,176,299.1,0.0,0.0,93.5,0.0,0.0,0.0,1461602589.1
|
||||
34,0.0,36,0.0,0.0,56.7,177,308.0,0.0,0.0,93.7,0.0,0.0,0.0,1461602590.7
|
||||
35,0.0,36,0.0,0.0,58.4,177,316.9,0.0,0.0,94.1,0.0,0.0,0.0,1461602592.4
|
||||
36,0.0,36,0.0,0.0,60.0,177,325.5,0.0,0.0,94.3,0.0,0.0,0.0,1461602594.0
|
||||
37,0.0,37,0.0,0.0,61.7,177,334.4,0.0,0.0,94.9,0.0,0.0,0.0,1461602595.7
|
||||
38,0.0,36,0.0,0.0,63.4,178,343.3,0.0,0.0,95.2,0.0,0.0,0.0,1461602597.4
|
||||
39,0.0,35,0.0,0.0,65.1,178,352.3,0.0,0.0,95.5,0.0,0.0,0.0,1461602599.1
|
||||
40,0.0,35,0.0,0.0,66.7,178,360.8,0.0,0.0,96.1,0.0,0.0,0.0,1461602600.7
|
||||
41,0.0,35,0.0,0.0,68.6,178,370.3,0.0,0.0,96.3,0.0,0.0,0.0,1461602602.6
|
||||
42,0.0,35,0.0,0.0,70.3,178,379.4,0.0,0.0,95.8,0.0,0.0,0.0,1461602604.3
|
||||
43,0.0,35,0.0,0.0,72.0,178,388.5,0.0,0.0,95.8,0.0,0.0,0.0,1461602606.0
|
||||
44,0.0,35,0.0,0.0,73.7,178,397.2,0.0,0.0,96.5,0.0,0.0,0.0,1461602607.7
|
||||
45,0.0,35,0.0,0.0,75.5,178,406.1,0.0,0.0,97.1,0.0,0.0,0.0,1461602609.5
|
||||
46,0.0,35,0.0,0.0,77.1,179,414.3,0.0,0.0,97.8,0.0,0.0,0.0,1461602611.1
|
||||
47,0.0,34,0.0,0.0,78.8,179,423.3,0.0,0.0,98.4,0.0,0.0,0.0,1461602612.8
|
||||
48,0.0,34,0.0,0.0,80.7,179,432.8,0.0,0.0,98.0,0.0,0.0,0.0,1461602614.7
|
||||
49,0.0,34,0.0,0.0,82.5,178,442.1,0.0,0.0,96.8,0.0,0.0,0.0,1461602616.5
|
||||
50,0.0,34,0.0,0.0,84.3,179,451.4,0.0,0.0,96.9,0.0,0.0,0.0,1461602618.3
|
||||
51,0.0,34,0.0,0.0,86.0,178,460.7,0.0,0.0,97.1,0.0,0.0,0.0,1461602620.0
|
||||
52,0.0,33,0.0,0.0,87.9,179,469.9,0.0,0.0,97.3,0.0,0.0,0.0,1461602621.9
|
||||
53,0.0,34,0.0,0.0,89.6,178,479.1,0.0,0.0,97.8,0.0,0.0,0.0,1461602623.6
|
||||
54,0.0,33,0.0,0.0,91.4,179,488.2,0.0,0.0,98.6,0.0,0.0,0.0,1461602625.4
|
||||
55,0.0,33,0.0,0.0,93.3,179,497.5,0.0,0.0,99.3,0.0,0.0,0.0,1461602627.3
|
||||
56,0.0,33,0.0,0.0,93.8,178,500.0,0.0,0.0,99.3,0.0,0.0,0.0,1461602627.8
|
||||
|
@@ -1,372 +0,0 @@
|
||||
<map version="1.0.1">
|
||||
<!-- To view this file, download free mind mapping software FreeMind from http://freemind.sourceforge.net -->
|
||||
<node CREATED="1461605460487" ID="ID_889197617" MODIFIED="1461606620316" TEXT="Rowsandall.com">
|
||||
<font BOLD="true" NAME="SansSerif" SIZE="12"/>
|
||||
<node CREATED="1461605502608" ID="ID_1180462618" MODIFIED="1464031892658" POSITION="left" TEXT="Views">
|
||||
<node CREATED="1461605473619" FOLDED="true" ID="ID_758526503" MODIFIED="1465560183674" TEXT="User">
|
||||
<node CREATED="1461605477168" ID="ID_1749649289" MODIFIED="1461605479404" TEXT="Standard">
|
||||
<node CREATED="1461605480370" ID="ID_738158750" MODIFIED="1462199805310" TEXT="login">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605482538" ID="ID_1405710591" MODIFIED="1462199807598" TEXT="logout">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605484671" ID="ID_1442326086" MODIFIED="1461605487813" TEXT="change password"/>
|
||||
<node CREATED="1461605488216" ID="ID_1649042094" MODIFIED="1461605492132" TEXT="forgotten password"/>
|
||||
</node>
|
||||
<node CREATED="1461605833468" ID="ID_1862101264" MODIFIED="1461605836329" TEXT="Profile">
|
||||
<node CREATED="1461605838740" ID="ID_1834978335" MODIFIED="1462199827098" TEXT="Profile Edit">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461605852258" FOLDED="true" ID="ID_1710060901" MODIFIED="1465560182848" TEXT="Workouts">
|
||||
<node CREATED="1461607050049" ID="ID_480866837" MODIFIED="1462372232456" TEXT="Add Workout">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461607067010" ID="ID_661274255" MODIFIED="1462372158825" TEXT="Import from C2">
|
||||
<arrowlink DESTINATION="ID_640810634" ENDARROW="Default" ENDINCLINATION="115;0;" ID="Arrow_ID_41303595" STARTARROW="None" STARTINCLINATION="115;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461607694156" ID="ID_1130452500" MODIFIED="1462199832919" TEXT="File Upload">
|
||||
<arrowlink DESTINATION="ID_640810634" ENDARROW="Default" ENDINCLINATION="293;0;" ID="Arrow_ID_758678018" STARTARROW="None" STARTINCLINATION="293;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461605874513" ID="ID_1782808892" MODIFIED="1461607669961" TEXT="Workouts (list of workouts with check boxes and buttons)">
|
||||
<font BOLD="true" NAME="SansSerif" SIZE="12"/>
|
||||
<node CREATED="1461605893808" ID="ID_518138659" MODIFIED="1462372195888" TEXT="Upload to C2 (can be bulk, grayed out if already)">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605938181" ID="ID_640810634" MODIFIED="1462372165831" TEXT="Edit Workout">
|
||||
<linktarget COLOR="#b0b0b0" DESTINATION="ID_640810634" ENDARROW="Default" ENDINCLINATION="115;0;" ID="Arrow_ID_41303595" SOURCE="ID_661274255" STARTARROW="None" STARTINCLINATION="115;0;"/>
|
||||
<linktarget COLOR="#b0b0b0" DESTINATION="ID_640810634" ENDARROW="Default" ENDINCLINATION="293;0;" ID="Arrow_ID_758678018" SOURCE="ID_1130452500" STARTARROW="None" STARTINCLINATION="293;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461605945605" ID="ID_1416204687" MODIFIED="1461605948881" TEXT="Re-upload"/>
|
||||
<node CREATED="1461606059232" ID="ID_1181900408" MODIFIED="1462372171747" TEXT="Delete">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605951301" ID="ID_1968531900" MODIFIED="1461605954953" TEXT="Add wind"/>
|
||||
<node CREATED="1461605965845" ID="ID_1834624609" MODIFIED="1462372174420" TEXT="Notes field">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461606989942" ID="ID_827883014" MODIFIED="1462372177224" TEXT="Type">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461607007953" ID="ID_1419094175" MODIFIED="1461607017030" TEXT="Workout Date"/>
|
||||
<node CREATED="1461607011010" ID="ID_609096815" MODIFIED="1461607021325" TEXT="Workout Time"/>
|
||||
<node CREATED="1462372184965" ID="ID_373832954" MODIFIED="1462372188425" TEXT="Add summary data"/>
|
||||
<node CREATED="1461606385679" ID="ID_630481471" MODIFIED="1461606394314" TEXT="Create new image"/>
|
||||
</node>
|
||||
<node CREATED="1461606355535" ID="ID_1087026303" MODIFIED="1461607115821" TEXT="Images">
|
||||
<node CREATED="1461606373437" ID="ID_103313544" MODIFIED="1462372204106" TEXT="Thumbnails of all images related to this workout">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461607873806" ID="ID_274993696" MODIFIED="1462372206775" TEXT="Image">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461607878860" ID="ID_1920400420" MODIFIED="1462372494580" TEXT="Download link "/>
|
||||
<node CREATED="1462372495159" ID="ID_95260866" MODIFIED="1462372498010" TEXT="Copy link"/>
|
||||
<node CREATED="1461607881802" ID="ID_98661252" MODIFIED="1461607883115" TEXT="Delete"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461607842284" ID="ID_1056962364" MODIFIED="1462372225114" TEXT="Dashboard (recent workouts and images)">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1462477981731" ID="ID_1584782313" MODIFIED="1462477992939" TEXT="Click on workout and get all related graphs"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461606171232" FOLDED="true" ID="ID_622814831" MODIFIED="1465560179352" TEXT="Static Pages">
|
||||
<node CREATED="1461606207102" ID="ID_578601651" MODIFIED="1465560156611" TEXT="Donate">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461606226184" ID="ID_1214482888" MODIFIED="1461606232099" TEXT="Help cover hosting costs!"/>
|
||||
</node>
|
||||
<node CREATED="1461606209720" ID="ID_915618144" MODIFIED="1465560159175" TEXT="About">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461606219407" ID="ID_1994937123" MODIFIED="1461606224396" TEXT="Bla bla bla about"/>
|
||||
</node>
|
||||
<node CREATED="1461606211560" ID="ID_1050734643" MODIFIED="1465560162149" TEXT="Contact">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461606214176" ID="ID_929787220" MODIFIED="1461606217618" TEXT="Email contact form"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461606535579" ID="ID_1450476512" MODIFIED="1461606537849" TEXT="Admin">
|
||||
<node CREATED="1461606539053" ID="ID_283722711" MODIFIED="1465560176760" TEXT="Delete old workouts"/>
|
||||
<node CREATED="1461606693893" ID="ID_1225613996" MODIFIED="1461606698106" TEXT="Remove user"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461605512814" FOLDED="true" ID="ID_367361758" MODIFIED="1464105541217" POSITION="right" TEXT="Data">
|
||||
<node CREATED="1461605518069" ID="ID_931509269" MODIFIED="1461859560704" TEXT="Users">
|
||||
<linktarget COLOR="#b0b0b0" DESTINATION="ID_931509269" ENDARROW="Default" ENDINCLINATION="200;0;" ID="Arrow_ID_793154745" SOURCE="ID_942353046" STARTARROW="None" STARTINCLINATION="200;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461605521065" ID="ID_1638831444" MODIFIED="1461859556854" TEXT="Standard">
|
||||
<linktarget COLOR="#b0b0b0" DESTINATION="ID_1638831444" ENDARROW="Default" ENDINCLINATION="191;0;" ID="Arrow_ID_304251916" SOURCE="ID_1797909649" STARTARROW="None" STARTINCLINATION="191;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461605527357" ID="ID_64076292" MODIFIED="1461675033560" TEXT="username">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605531205" ID="ID_1160148822" MODIFIED="1461675039348" TEXT="password">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605534005" ID="ID_468992723" MODIFIED="1461675044250" TEXT="email address">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605537685" ID="ID_658719592" MODIFIED="1461675047673" TEXT="First name">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605541815" ID="ID_780111581" MODIFIED="1461675049604" TEXT="Last Name">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461651836946" ID="ID_1797909649" MODIFIED="1461859566661" TEXT="Rower">
|
||||
<arrowlink DESTINATION="ID_1638831444" ENDARROW="Default" ENDINCLINATION="191;0;" ID="Arrow_ID_304251916" STARTARROW="None" STARTINCLINATION="191;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461605578428" ID="ID_1864317482" MODIFIED="1461675054304" TEXT="HR Bands (with default settings)">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461605582890" ID="ID_1036263090" MODIFIED="1461652489462" TEXT="max">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605584970" ID="ID_349101423" MODIFIED="1461674288201" TEXT="an">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605606354" ID="ID_658122284" MODIFIED="1461674293973" TEXT="tr">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605609690" ID="ID_64081034" MODIFIED="1461674300195" TEXT="at">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605612128" ID="ID_1685146538" MODIFIED="1461674305654" TEXT="ut1">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605614208" ID="ID_1935848031" MODIFIED="1461674312392" TEXT="ut2">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605618128" ID="ID_425502762" MODIFIED="1461674318162" TEXT="rest">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461605640561" ID="ID_1222231283" MODIFIED="1461674599803" TEXT="Default weight class">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605559179" ID="ID_707728856" MODIFIED="1461605560984" TEXT="Hidden">
|
||||
<node CREATED="1461605561891" ID="ID_1160097125" MODIFIED="1461674977236" TEXT="C2 log access token">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461605710787" ID="ID_1360155429" MODIFIED="1461605730720" TEXT="obtained when user grants permission through his C2 identity"/>
|
||||
</node>
|
||||
<node CREATED="1461605570034" ID="ID_1771848783" MODIFIED="1461674983016" TEXT="Access token expiration date">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461605740385" ID="ID_39541443" MODIFIED="1461605747853" TEXT="Workouts">
|
||||
<node CREATED="1461605749553" ID="ID_942353046" MODIFIED="1461652737920" TEXT="User">
|
||||
<arrowlink DESTINATION="ID_931509269" ENDARROW="Default" ENDINCLINATION="200;0;" ID="Arrow_ID_793154745" STARTARROW="None" STARTINCLINATION="200;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461606406059" ID="ID_454812442" MODIFIED="1461652703049" TEXT="Workout_name">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461606414638" ID="ID_928281830" MODIFIED="1461660580936" TEXT="Workout_date">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461606429486" ID="ID_484521014" MODIFIED="1461866244072" TEXT="Workout_starttime">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461606866528" ID="ID_1872216450" MODIFIED="1461866319482" TEXT="Workout_distance">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461866323170" ID="ID_936304102" MODIFIED="1461866324983" TEXT="meters"/>
|
||||
</node>
|
||||
<node CREATED="1461606878962" ID="ID_1128895938" MODIFIED="1461866381363" TEXT="Workout_duration">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461866327528" ID="ID_901906294" MODIFIED="1461866329945" TEXT="seconds"/>
|
||||
</node>
|
||||
<node CREATED="1461606908475" ID="ID_900580299" MODIFIED="1461866411448" TEXT="Workout_weight_class">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461606914708" FOLDED="true" ID="ID_931316189" MODIFIED="1461675065400" TEXT="Workout_type">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461606925676" ID="ID_1969400042" MODIFIED="1461606933440" TEXT="On-water"/>
|
||||
<node CREATED="1461606927748" ID="ID_1209065588" MODIFIED="1461606941465" TEXT="Indoor Rower"/>
|
||||
<node CREATED="1461606944797" ID="ID_610597647" MODIFIED="1461606949114" TEXT="Indoor Rower with Slides"/>
|
||||
<node CREATED="1461606952525" ID="ID_1889795917" MODIFIED="1461606956547" TEXT="Dynamic Indoor Rower"/>
|
||||
<node CREATED="1461606960376" ID="ID_294896523" MODIFIED="1461606962612" TEXT="Ski Erg"/>
|
||||
<node CREATED="1461606963397" ID="ID_490247020" MODIFIED="1461606967058" TEXT="Paddle Adapter"/>
|
||||
<node CREATED="1461606970534" ID="ID_1089182854" MODIFIED="1461606973571" TEXT="On-snow"/>
|
||||
</node>
|
||||
<node CREATED="1461605767159" ID="ID_156406" MODIFIED="1461866434741" TEXT="Filename (csv file)">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461606269644" ID="ID_196687111" MODIFIED="1461606286903" TEXT="I presume the data will be in painsled style csv files- not in the database"/>
|
||||
</node>
|
||||
<node CREATED="1461606317938" ID="ID_1830610935" MODIFIED="1463015586401" TEXT="Images">
|
||||
<linktarget COLOR="#b0b0b0" DESTINATION="ID_1830610935" ENDARROW="Default" ENDINCLINATION="115;0;" ID="Arrow_ID_576769313" SOURCE="ID_1806450802" STARTARROW="None" STARTINCLINATION="115;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605787942" ID="ID_6975390" MODIFIED="1461866507607" TEXT="Uploaded to c2 yes/no">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461605980412" ID="ID_739576489" MODIFIED="1461866856190" TEXT="Notes">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461823229485" ID="ID_1676270847" MODIFIED="1461836884659" TEXT="Image">
|
||||
<node CREATED="1461823234420" ID="ID_100561287" MODIFIED="1461866881645" TEXT="File name">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461823237869" ID="ID_366884875" MODIFIED="1461866884487" TEXT="Creation Date">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461823241228" ID="ID_1806450802" MODIFIED="1461866887134" TEXT="Workout">
|
||||
<arrowlink DESTINATION="ID_1830610935" ENDARROW="Default" ENDINCLINATION="115;0;" ID="Arrow_ID_576769313" STARTARROW="None" STARTINCLINATION="115;0;"/>
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461606622088" FOLDED="true" ID="ID_1185569412" MODIFIED="1464024669570" POSITION="right" TEXT="Design">
|
||||
<node CREATED="1461606626928" ID="ID_500274274" MODIFIED="1461606634684" TEXT="Header">
|
||||
<node CREATED="1461607250605" ID="ID_277736788" MODIFIED="1461607542769" TEXT="Logo">
|
||||
<node CREATED="1461831818073" ID="ID_1883988129" MODIFIED="1461831820242" TEXT="Top Left"/>
|
||||
</node>
|
||||
<node CREATED="1461831845737" ID="ID_1769289061" MODIFIED="1461831849446" TEXT="Navigation Bar"/>
|
||||
<node CREATED="1461832234311" ID="ID_890051640" MODIFIED="1461832238780" TEXT="Value Prop on Home Page"/>
|
||||
<node CREATED="1461607537352" ID="ID_9873263" MODIFIED="1461607540890" TEXT="Dashboard"/>
|
||||
<node CREATED="1461607651139" ID="ID_1816736051" MODIFIED="1461607653176" TEXT="Workouts"/>
|
||||
<node CREATED="1461607681100" ID="ID_963617159" MODIFIED="1461607683905" TEXT="Add Workout"/>
|
||||
<node CREATED="1461832024698" ID="ID_704022131" MODIFIED="1461835713046" TEXT="Contact ">
|
||||
<node CREATED="1461832031127" ID="ID_1522003911" MODIFIED="1461832033220" TEXT="top right"/>
|
||||
</node>
|
||||
<node CREATED="1461606804240" ID="ID_1832303281" MODIFIED="1461607544061" TEXT="User"/>
|
||||
</node>
|
||||
<node CREATED="1461606635252" ID="ID_1658688353" MODIFIED="1461606636679" TEXT="Footer">
|
||||
<node CREATED="1461607518944" ID="ID_1140796060" MODIFIED="1461607526372" TEXT="Links to static pages"/>
|
||||
</node>
|
||||
<node CREATED="1461606637239" ID="ID_371706656" MODIFIED="1461606641893" TEXT="Side menu?"/>
|
||||
</node>
|
||||
<node CREATED="1461823376524" ID="ID_1672286072" MODIFIED="1461823378932" POSITION="left" TEXT="Testing"/>
|
||||
<node CREATED="1461825250754" FOLDED="true" ID="ID_478462481" MODIFIED="1468564897482" POSITION="left" TEXT="Functionality">
|
||||
<node CREATED="1461859283282" ID="ID_671390350" MODIFIED="1461859648988" TEXT="Workouts take user data for making plot">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461859444717" ID="ID_231247258" MODIFIED="1461873064421" TEXT="File upload creates workout">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1461859462821" ID="ID_614435280" MODIFIED="1461873050715" TEXT="check for duplicate start times">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461861313345" ID="ID_1214320980" MODIFIED="1461868259008" TEXT="add workout starttime, workout distance, workout duration">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461876950835" ID="ID_1233516192" MODIFIED="1461939989747" TEXT="add DurationField instead of TimeField to Workout">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461939997214" ID="ID_623587817" MODIFIED="1461940003606" TEXT="Delete workouts">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461859314613" ID="ID_1989736428" MODIFIED="1461956084613" TEXT="Plot info stored in Image class - linked to Workout - linked to user">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461859340521" ID="ID_1835485102" MODIFIED="1461956098273" TEXT="Workout summary data added to workout on import">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461859363717" ID="ID_1063073204" MODIFIED="1461956100660" TEXT="Import TCX files">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462037395055" ID="ID_426454632" MODIFIED="1462089744303" TEXT="restrict file import to csv and tcx">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461868236440" ID="ID_998738107" MODIFIED="1462092856141" TEXT="add tenths in rowingdata.py line 455">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462090268399" ID="ID_1789786780" MODIFIED="1463013907767" TEXT="add proper error handling">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1462095525267" ID="ID_1485370247" MODIFIED="1462095529439" TEXT="Internal error handling"/>
|
||||
<node CREATED="1462095529783" ID="ID_1494810517" MODIFIED="1462095534480" TEXT="Error handling related to C2"/>
|
||||
</node>
|
||||
<node CREATED="1462385130662" ID="ID_1736622825" MODIFIED="1468564862058" TEXT="create C2 authorization/renewal functionality">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461859380482" ID="ID_286348211" MODIFIED="1462095448230" TEXT="Connect to C2 and store token">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461859292062" ID="ID_891978726" MODIFIED="1462114645928" TEXT="Upload simple workout to C2">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462114661522" ID="ID_108970697" MODIFIED="1462126485430" TEXT="Store C2 workout ID in database for checking duplicates">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462114646664" ID="ID_1219356268" MODIFIED="1462258165015" TEXT="Upload stroke data to C2">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462201886153" ID="ID_235589940" MODIFIED="1468564866385" TEXT="Add average and max HR to Workout model">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462220317016" ID="ID_247996816" MODIFIED="1462259473708" TEXT="Graph choices">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1462220324724" ID="ID_1722947819" MODIFIED="1462259470955" TEXT="Add pie chart">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1461859296985" ID="ID_697148148" MODIFIED="1462279669306" TEXT="Import workouts from C2">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462279678160" ID="ID_748439178" MODIFIED="1462372114571" TEXT="Upload TCX data to C2">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1461859495116" ID="ID_1813236024" MODIFIED="1461859511687" TEXT="Add HR bands to "open" rowingdata view (for anonymous users)">
|
||||
<node CREATED="1464099527327" ID="ID_1377500887" MODIFIED="1464099533644" TEXT="made it easier to sign up"/>
|
||||
</node>
|
||||
<node CREATED="1461859515623" ID="ID_871304706" MODIFIED="1464024676014" TEXT="get sendmail working on rowsandall.com">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462451790489" ID="ID_1246677164" MODIFIED="1463146936088" TEXT="make password change integral part of the site">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1463013921749" ID="ID_891957290" MODIFIED="1464773708543" TEXT="Make interactive plots possible">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1463013931611" ID="ID_1645855977" MODIFIED="1463013936147" TEXT="use mpld3"/>
|
||||
<node CREATED="1463146923529" ID="ID_111815361" MODIFIED="1463146928361" TEXT="change plotting to bokeh"/>
|
||||
</node>
|
||||
<node CREATED="1464033158984" ID="ID_1019464919" MODIFIED="1468564874119" TEXT="add label under graph thumbnails">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1463056343673" ID="ID_1704885677" MODIFIED="1464773723535" TEXT="Make duration field prettier in C2 Import view">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1463898326795" ID="ID_304310926" MODIFIED="1468564877186" TEXT="compare 2 workouts">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1465541522068" ID="ID_24723428" MODIFIED="1465541524989" TEXT="add Teams"/>
|
||||
</node>
|
||||
<node CREATED="1462372120147" FOLDED="true" ID="ID_1790010779" MODIFIED="1468564903194" POSITION="left" TEXT="Bugs">
|
||||
<node CREATED="1462372122865" ID="ID_791488808" MODIFIED="1463926727764" TEXT="Export to C2 - reimport - 2 hours difference ">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1462372134058" ID="ID_1541966541" MODIFIED="1462372139127" TEXT="Check time zone information"/>
|
||||
</node>
|
||||
<node CREATED="1462392509362" ID="ID_1974493580" MODIFIED="1464773734760" TEXT="Total time not correct">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1462393932924" ID="ID_1584420283" MODIFIED="1462393949048" TEXT="Could take max('Elapsed Time') but this doesn't work for intervals sessions"/>
|
||||
<node CREATED="1462393949569" ID="ID_601203201" MODIFIED="1462393955852" TEXT="Perhaps detect if intervals or not"/>
|
||||
<node CREATED="1462394984031" ID="ID_772280168" MODIFIED="1462395007935" TEXT="Interestingly, the old painsledtoc2 upload from cmd line gives a shorter duration"/>
|
||||
</node>
|
||||
<node CREATED="1462392520077" ID="ID_1005078711" MODIFIED="1462395039415" TEXT="Interval workout time not rendered correctly on C2">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1462393593313" ID="ID_1412686474" MODIFIED="1462393610112" TEXT="If user is not registered to C2 and wants to upload data, responses are not working well"/>
|
||||
<node CREATED="1462433524171" ID="ID_207392923" MODIFIED="1464773757964" TEXT="Workout duration from RowPro seems to be wrong">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
<node CREATED="1462433646218" ID="ID_1865445212" MODIFIED="1462433655083" TEXT="Elapsed Time resets at intervals"/>
|
||||
<node CREATED="1462434136715" ID="ID_862653660" MODIFIED="1462434152061" TEXT="There is no time stamp - need to solve this in rowingdata.py"/>
|
||||
</node>
|
||||
<node CREATED="1462986270815" ID="ID_355099188" MODIFIED="1468564882289" TEXT="TCX files without HR data">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
<node CREATED="1463056227952" ID="ID_1543943630" MODIFIED="1463056234311" TEXT="TCX files from Painsled">
|
||||
<node CREATED="1463056996127" ID="ID_1741508714" MODIFIED="1463057048432" TEXT="No lat/lon values present"/>
|
||||
<node CREATED="1464027094358" ID="ID_109801653" MODIFIED="1464027099523" TEXT="can import through strava?"/>
|
||||
</node>
|
||||
</node>
|
||||
<node CREATED="1462383105838" FOLDED="true" ID="ID_1770973494" MODIFIED="1464773783299" POSITION="right" TEXT="Other">
|
||||
<node CREATED="1462383109198" ID="ID_1666350492" MODIFIED="1463015612121" TEXT="Migrate from Celery to RQ">
|
||||
<icon BUILTIN="button_ok"/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</map>
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Here's your result{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
Waiting for task with result {{ task }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
{% block meta %} {% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<h1>My helpful rowing site</h1>
|
||||
{% if user.is_authenticated %}
|
||||
<p>Welcome, {{ user.first_name }}.</p>
|
||||
<p><a href="/logout/">logout</a></p>
|
||||
|
||||
{% else %}
|
||||
<p><a href="/login/">login</a> </p>
|
||||
|
||||
{% endif %}
|
||||
<hr>
|
||||
{% block content %}{% endblock %}
|
||||
{% block footer %}
|
||||
<hr>
|
||||
<p>{{ versionstring }}</p>
|
||||
<p>Thanks for visiting my site.</p>
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,22 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Contact us</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Contact us</h1>
|
||||
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="" method="post">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Current time{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>It is now {{ current_date }}.</p>
|
||||
{% endblock %}
|
||||
@@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}File loading{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>File Upload</h1>
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Submit">
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Future time{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>In {{ offset }} hours, it will be {{ future_time }}.</p>
|
||||
{% endblock %}
|
||||
@@ -1,10 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Here's your plot{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1> Here's your plot</h1>
|
||||
<p><image src="{% static imagename %}"/></p>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Workouts{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>My Workouts</h1>
|
||||
{% if workouts %}
|
||||
<table width="70%">
|
||||
<tr>
|
||||
<td> <strong>Date</strong> </td>
|
||||
<td> <strong>Time </strong></td>
|
||||
<td> <strong>Name </strong></td>
|
||||
<td> <strong>Type </strong></td>
|
||||
<td> <strong>Distance</strong> </td>
|
||||
<td> <strong>Duration</strong> </td>
|
||||
<td> <strong>Edit</strong></td>
|
||||
</tr>
|
||||
|
||||
{% for workout in workouts %}
|
||||
<tr>
|
||||
<td> {{ workout.date }} </td>
|
||||
<td> {{ workout.starttime }} </td>
|
||||
<td> {{ workout.name }} </td>
|
||||
<td> {{ workout.workouttype }} </td>
|
||||
<td> {{ workout.distance }}m</td>
|
||||
<td> {{ workout.duration |durationprint:"%H:%M:%S" }} </td>
|
||||
<td> <a href="/rowers/workout/{{ workout.id }}/edit">E</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<p> No workouts found </p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -1,23 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Login Form</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Login</h1>
|
||||
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,26 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'django.contrib.auth.views.login' %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
<tr>
|
||||
<td>{{ form.username.label_tag }}</td>
|
||||
<td>{{ form.username }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{ form.password.label_tag }}</td>
|
||||
<td>{{ form.password }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<input type="submit" value="login" />
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Change Rower {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<h1>Edit your Parameters</h1>
|
||||
|
||||
<form enctype="multipart/form-data" action="" method="post">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Save">
|
||||
|
||||
{% endblock %}
|
||||
@@ -1,7 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Rowingdata Version{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<p>{{ versionstring }}.</p>
|
||||
{% endblock %}
|
||||
@@ -1,11 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block title %}Waiting{% endblock %}
|
||||
{% block meta %}<meta http-equiv="refresh" content="30">{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Waiting for your image to be processed</h1>
|
||||
<p> {{ message }} </p>
|
||||
<p>Please do not close this page. Page should refresh automatically (or you can hit reload)</p>
|
||||
{% endblock %}
|
||||
@@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Change Workout {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% if form.errors %}
|
||||
<p style="color: red;">
|
||||
Please correct the error{{ form.errors|pluralize }} below.
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<h1>Edit Workout Data</h1>
|
||||
|
||||
<form enctype="multipart/form-data" action="" method="post">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Save">
|
||||
|
||||
{% endblock %}
|
||||
@@ -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
|
||||
|
||||
@@ -2,6 +2,6 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
# Store metadata for the app
|
||||
class RowersConfig(AppConfig):
|
||||
name = 'rowers'
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# The interactions with the Concept2 logbook API
|
||||
# All C2 related functions should be defined here
|
||||
# (There is still some stuff defined directly in views.py. Need to
|
||||
# move that here.)
|
||||
|
||||
# Python
|
||||
import oauth2 as oauth
|
||||
import cgi
|
||||
@@ -17,8 +22,7 @@ from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
# Project
|
||||
# from .models import Profile
|
||||
|
||||
from rowingdata import rowingdata
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
@@ -27,9 +31,9 @@ import sys
|
||||
import urllib
|
||||
from requests import Request, Session
|
||||
|
||||
|
||||
from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET
|
||||
|
||||
# Custom error class - to raise a NoTokenError
|
||||
class C2NoTokenError(Exception):
|
||||
def __init__(self,value):
|
||||
self.value=value
|
||||
@@ -37,8 +41,8 @@ class C2NoTokenError(Exception):
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
|
||||
# Custom exception handler, returns a 401 HTTP message
|
||||
# with exception details in the json data
|
||||
def custom_exception_handler(exc,message):
|
||||
|
||||
response = {
|
||||
@@ -56,7 +60,7 @@ def custom_exception_handler(exc,message):
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# Check if workout is owned by this user
|
||||
def checkworkoutuser(user,workout):
|
||||
try:
|
||||
r = Rower.objects.get(user=user)
|
||||
@@ -64,11 +68,12 @@ def checkworkoutuser(user,workout):
|
||||
except Rower.DoesNotExist:
|
||||
return(False)
|
||||
|
||||
|
||||
# convert datetime object to seconds
|
||||
def makeseconds(t):
|
||||
seconds = t.hour*3600.+t.minute*60.+t.second+0.1*int(t.microsecond/1.e5)
|
||||
return seconds
|
||||
|
||||
# convert our weight class code to Concept2 weight class code
|
||||
def c2wc(weightclass):
|
||||
if (weightclass=="lwt"):
|
||||
res = "L"
|
||||
@@ -77,7 +82,9 @@ def c2wc(weightclass):
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# Concept2 logbook sends over split data for each interval
|
||||
# We use it here to generate a custom summary
|
||||
# Some users complained about small differences
|
||||
def summaryfromsplitdata(splitdata,data,filename,sep='|'):
|
||||
|
||||
totaldist = data['distance']
|
||||
@@ -177,6 +184,8 @@ def summaryfromsplitdata(splitdata,data,filename,sep='|'):
|
||||
|
||||
return sums,sa,results
|
||||
|
||||
# Not used now. Could be used to add workout split data to Concept2
|
||||
# logbook but needs to be reviewed.
|
||||
def createc2workoutdata_as_splits(w):
|
||||
filename = w.csvfilename
|
||||
row = rowingdata(filename)
|
||||
@@ -216,7 +225,6 @@ def createc2workoutdata_as_splits(w):
|
||||
|
||||
data = {
|
||||
"type": w.workouttype,
|
||||
# "date": str(w.date)+" "+str(w.starttime),
|
||||
"date": w.startdatetime.isoformat(),
|
||||
"distance": int(w.distance),
|
||||
"time": int(10*makeseconds(durationstr)),
|
||||
@@ -233,59 +241,8 @@ def createc2workoutdata_as_splits(w):
|
||||
|
||||
return data
|
||||
|
||||
def createc2workoutdata_grouped(w):
|
||||
filename = w.csvfilename
|
||||
row = rowingdata(filename)
|
||||
|
||||
# resize per minute
|
||||
df = row.df.groupby(lambda x:x/10).mean()
|
||||
|
||||
averagehr = int(df[' HRCur (bpm)'].mean())
|
||||
maxhr = int(df[' HRCur (bpm)'].max())
|
||||
|
||||
# adding diff, trying to see if this is valid
|
||||
t = 10*df.ix[:,' ElapsedTime (sec)'].values
|
||||
t[0] = t[1]
|
||||
d = df.ix[:,' Horizontal (meters)'].values
|
||||
d[0] = d[1]
|
||||
p = 10*df.ix[:,' Stroke500mPace (sec/500m)'].values
|
||||
t = t.astype(int)
|
||||
d = d.astype(int)
|
||||
p = p.astype(int)
|
||||
spm = df[' Cadence (stokes/min)'].astype(int)
|
||||
spm[0] = spm[1]
|
||||
hr = df[' HRCur (bpm)'].astype(int)
|
||||
stroke_data = []
|
||||
for i in range(len(t)):
|
||||
thisrecord = {"t":t[i],"d":d[i],"p":p[i],"spm":spm[i],"hr":hr[i]}
|
||||
stroke_data.append(thisrecord)
|
||||
|
||||
|
||||
try:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S.%f")
|
||||
except ValueError:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S")
|
||||
|
||||
|
||||
data = {
|
||||
"type": w.workouttype,
|
||||
# "date": str(w.date)+" "+str(w.starttime),
|
||||
"date": w.startdatetime.isoformat(),
|
||||
"distance": int(w.distance),
|
||||
"time": int(10*makeseconds(durationstr)),
|
||||
"weight_class": c2wc(w.weightcategory),
|
||||
"timezone": "Etc/UTC",
|
||||
"comments": w.notes,
|
||||
"heart_rate": {
|
||||
"average": averagehr,
|
||||
"max": maxhr,
|
||||
},
|
||||
"stroke_data": stroke_data,
|
||||
}
|
||||
|
||||
|
||||
return data
|
||||
|
||||
# Create the Data object for the stroke data to be sent to Concept2 logbook
|
||||
# API
|
||||
def createc2workoutdata(w):
|
||||
filename = w.csvfilename
|
||||
row = rowingdata(filename)
|
||||
@@ -318,7 +275,6 @@ def createc2workoutdata(w):
|
||||
|
||||
data = {
|
||||
"type": w.workouttype,
|
||||
# "date": str(w.date)+" "+str(w.starttime),
|
||||
"date": w.startdatetime.isoformat(),
|
||||
"timezone": "Etc/UTC",
|
||||
"distance": int(w.distance),
|
||||
@@ -335,6 +291,7 @@ def createc2workoutdata(w):
|
||||
|
||||
return data
|
||||
|
||||
# Refresh Concept2 authorization token
|
||||
def do_refresh_token(refreshtoken):
|
||||
scope = "results:write,user:read"
|
||||
client_auth = requests.auth.HTTPBasicAuth(C2_CLIENT_ID, C2_CLIENT_SECRET)
|
||||
@@ -347,10 +304,6 @@ def do_refresh_token(refreshtoken):
|
||||
url = "https://log.concept2.com/oauth/access_token"
|
||||
s = Session()
|
||||
req = Request('POST',url, data=post_data, headers=headers)
|
||||
# response = requests.post("https://log.concept2.com/oauth/access_token",
|
||||
# data=post_data,
|
||||
# data=post_data,
|
||||
# headers=headers)
|
||||
|
||||
prepped = req.prepare()
|
||||
prepped.body+="&scope="
|
||||
@@ -374,13 +327,12 @@ def do_refresh_token(refreshtoken):
|
||||
|
||||
return [thetoken,expires_in,refresh_token]
|
||||
|
||||
|
||||
# Exchange authorization code for authorization token
|
||||
def get_token(code):
|
||||
scope = "user:read,results:write"
|
||||
client_auth = requests.auth.HTTPBasicAuth(C2_CLIENT_ID, C2_CLIENT_SECRET)
|
||||
post_data = {"grant_type": "authorization_code",
|
||||
"code": code,
|
||||
# "scope": scope,
|
||||
"redirect_uri": C2_REDIRECT_URI,
|
||||
"client_secret": C2_CLIENT_SECRET,
|
||||
"client_id":C2_CLIENT_ID,
|
||||
@@ -396,9 +348,6 @@ def get_token(code):
|
||||
|
||||
response = s.send(prepped)
|
||||
|
||||
# response = requests.post("https://log.concept2.com/oauth/access_token",
|
||||
# data=post_data,
|
||||
# headers=headers)
|
||||
token_json = response.json()
|
||||
thetoken = token_json['access_token']
|
||||
expires_in = token_json['expires_in']
|
||||
@@ -406,6 +355,7 @@ def get_token(code):
|
||||
|
||||
return [thetoken,expires_in,refresh_token]
|
||||
|
||||
# Make URL for authorization and load it
|
||||
def make_authorization_url(request):
|
||||
# Generate a random string for the state parameter
|
||||
# Save it for use later to prevent xsrf attacks
|
||||
@@ -421,6 +371,7 @@ def make_authorization_url(request):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Get workout from C2 ID
|
||||
def get_c2_workout(user,c2id):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.c2token == '') or (r.c2token is None):
|
||||
@@ -440,6 +391,7 @@ def get_c2_workout(user,c2id):
|
||||
|
||||
return s
|
||||
|
||||
# Get stroke data belonging to C2 ID
|
||||
def get_c2_workout_strokes(user,c2id):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.c2token == '') or (r.c2token is None):
|
||||
@@ -459,6 +411,8 @@ def get_c2_workout_strokes(user,c2id):
|
||||
|
||||
return s
|
||||
|
||||
# Get list of C2 workouts. We load only the first page,
|
||||
# assuming that users don't want to import their old workouts
|
||||
def get_c2_workout_list(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.c2token == '') or (r.c2token is None):
|
||||
@@ -479,7 +433,8 @@ def get_c2_workout_list(user):
|
||||
return s
|
||||
|
||||
|
||||
|
||||
# Get username, having access token.
|
||||
# Handy for checking if the API access is working
|
||||
def get_username(access_token):
|
||||
authorizationstring = str('Bearer ' + access_token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
@@ -495,6 +450,8 @@ def get_username(access_token):
|
||||
|
||||
return me_json['data']['username']
|
||||
|
||||
# Get user id, having access token
|
||||
# Handy for checking if the API access is working
|
||||
def get_userid(access_token):
|
||||
authorizationstring = str('Bearer ' + access_token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
@@ -510,6 +467,7 @@ def get_userid(access_token):
|
||||
|
||||
return me_json['data']['id']
|
||||
|
||||
# For debugging purposes
|
||||
def process_callback(request):
|
||||
# need error handling
|
||||
|
||||
@@ -521,6 +479,7 @@ def process_callback(request):
|
||||
|
||||
return HttpResponse("got a user name: %s" % username)
|
||||
|
||||
# Uploading workout
|
||||
def workout_c2_upload(user,w):
|
||||
response = 'trying C2 upload'
|
||||
r = Rower.objects.get(user=user)
|
||||
@@ -535,8 +494,6 @@ def workout_c2_upload(user,w):
|
||||
if (checkworkoutuser(user,w)):
|
||||
c2userid = get_userid(r.c2token)
|
||||
data = createc2workoutdata(w)
|
||||
# if (w.workouttype=='water'):
|
||||
# data = createc2workoutdata_as_splits(w)
|
||||
authorizationstring = str('Bearer ' + r.c2token)
|
||||
headers = {'Authorization': authorizationstring,
|
||||
'user-agent': 'sanderroosendaal',
|
||||
@@ -554,6 +511,7 @@ def workout_c2_upload(user,w):
|
||||
|
||||
return response
|
||||
|
||||
# This is token refresh. Looks for tokens in our database, then refreshes
|
||||
def rower_c2_token_refresh(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
res = do_refresh_token(r.c2refreshtoken)
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# All the data preparation, data cleaning and data mangling should
|
||||
# be defined here
|
||||
from rowers.models import Workout, User, Rower
|
||||
from rowingdata import rowingdata as rrdata
|
||||
|
||||
@@ -37,11 +39,13 @@ database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
|
||||
port=port,
|
||||
)
|
||||
|
||||
# Use SQLite local database when we're in debug mode
|
||||
if settings.DEBUG or user=='':
|
||||
# database_url = 'sqlite:///db.sqlite3'
|
||||
database_url = 'sqlite:///'+database_name
|
||||
|
||||
|
||||
# mapping the DB column names to the CSV file column names
|
||||
columndict = {
|
||||
'time':'TimeStamp (sec)',
|
||||
'hr':' HRCur (bpm)',
|
||||
@@ -63,6 +67,7 @@ from scipy.signal import savgol_filter
|
||||
|
||||
import datetime
|
||||
|
||||
# A string representation for time deltas
|
||||
def niceformat(values):
|
||||
out = []
|
||||
for v in values:
|
||||
@@ -71,6 +76,7 @@ def niceformat(values):
|
||||
|
||||
return out
|
||||
|
||||
# A nice printable format for time delta values
|
||||
def strfdelta(tdelta):
|
||||
try:
|
||||
minutes,seconds = divmod(tdelta.seconds,60)
|
||||
@@ -87,6 +93,7 @@ def strfdelta(tdelta):
|
||||
|
||||
return res
|
||||
|
||||
# A nice printable format for pace values
|
||||
def nicepaceformat(values):
|
||||
out = []
|
||||
for v in values:
|
||||
@@ -96,6 +103,7 @@ def nicepaceformat(values):
|
||||
|
||||
return out
|
||||
|
||||
# Convert seconds to a Time Delta value, replacing NaN with a 5:50 pace
|
||||
def timedeltaconv(x):
|
||||
if not np.isnan(x):
|
||||
dt = datetime.timedelta(seconds=x)
|
||||
@@ -105,6 +113,9 @@ def timedeltaconv(x):
|
||||
|
||||
return dt
|
||||
|
||||
# Create new workout from file and store it in the database
|
||||
# This routine should be used everywhere in views.py and mailprocessing.pu
|
||||
# Currently there is code duplication
|
||||
def new_workout_from_file(r,f2,
|
||||
workouttype='rower',
|
||||
title='Workout',
|
||||
@@ -263,6 +274,9 @@ def new_workout_from_file(r,f2,
|
||||
|
||||
return True
|
||||
|
||||
# Compare the data from the CSV file and the database
|
||||
# Currently only calculates number of strokes. To be expanded with
|
||||
# more elaborate testing if needed
|
||||
def compare_data(id):
|
||||
row = Workout.objects.get(id=id)
|
||||
f1 = row.csvfilename
|
||||
@@ -288,6 +302,8 @@ def compare_data(id):
|
||||
ldb = l2
|
||||
return l1==l2,ldb,lfile
|
||||
|
||||
# Repair data for workouts where the CSV file is lost (or the DB entries
|
||||
# don't exist)
|
||||
def repair_data(verbose=False):
|
||||
ws = Workout.objects.all()
|
||||
for w in ws:
|
||||
@@ -319,6 +335,7 @@ def repair_data(verbose=False):
|
||||
print str(sys.exc_info()[0])
|
||||
pass
|
||||
|
||||
# A wrapper around the rowingdata class, with some error catching
|
||||
def rdata(file,rower=rrower()):
|
||||
try:
|
||||
res = rrdata(file,rower=rower)
|
||||
@@ -330,6 +347,7 @@ def rdata(file,rower=rrower()):
|
||||
|
||||
return res
|
||||
|
||||
# Remove all stroke data for workout ID from database
|
||||
def delete_strokedata(id):
|
||||
engine = create_engine(database_url, echo=False)
|
||||
query = sa.text('DELETE FROM strokedata WHERE workoutid={id};'.format(
|
||||
@@ -343,10 +361,12 @@ def delete_strokedata(id):
|
||||
conn.close()
|
||||
engine.dispose()
|
||||
|
||||
# Replace stroke data in DB with data from CSV file
|
||||
def update_strokedata(id,df):
|
||||
delete_strokedata(id)
|
||||
rowdata = dataprep(df,id=id,bands=True,barchart=True,otwpower=True)
|
||||
|
||||
# Test that all data are of a numerical time
|
||||
def testdata(time,distance,pace,spm):
|
||||
t1 = np.issubdtype(time,np.number)
|
||||
t2 = np.issubdtype(distance,np.number)
|
||||
@@ -355,6 +375,8 @@ def testdata(time,distance,pace,spm):
|
||||
|
||||
return t1 and t2 and t3 and t4
|
||||
|
||||
# Get data from DB for one workout (fetches all data). If data
|
||||
# is not in DB, read from CSV file (and create DB entry)
|
||||
def getrowdata_db(id=0):
|
||||
data = read_df_sql(id)
|
||||
data['x_right'] = data['x_right']/1.0e6
|
||||
@@ -369,12 +391,14 @@ def getrowdata_db(id=0):
|
||||
|
||||
return data,row
|
||||
|
||||
# Fetch a subset of the data from the DB
|
||||
def getsmallrowdata_db(columns,ids=[]):
|
||||
prepmultipledata(ids)
|
||||
data = read_cols_df_sql(ids,columns)
|
||||
|
||||
return data
|
||||
|
||||
# Fetch both the workout and the workout stroke data (from CSV file)
|
||||
def getrowdata(id=0):
|
||||
|
||||
# check if valid ID exists (workout exists)
|
||||
@@ -395,7 +419,12 @@ def getrowdata(id=0):
|
||||
|
||||
return rowdata,row
|
||||
|
||||
|
||||
# Checks if all rows for a list of workout IDs have entries in the
|
||||
# stroke_data table. If this is not the case, it creates the stroke
|
||||
# data
|
||||
# In theory, this should never yield any work, but it's a good
|
||||
# safety net for programming errors elsewhere in the app
|
||||
# Also used heavily when I moved from CSV file only to CSV+Stroke data
|
||||
def prepmultipledata(ids,verbose=False):
|
||||
query = sa.text('SELECT DISTINCT workoutid FROM strokedata')
|
||||
engine = create_engine(database_url, echo=False)
|
||||
@@ -420,6 +449,8 @@ def prepmultipledata(ids,verbose=False):
|
||||
data = dataprep(rowdata.df,id=id,bands=True,barchart=True,otwpower=True)
|
||||
return res
|
||||
|
||||
# Read a set of columns for a set of workout ids, returns data as a
|
||||
# pandas dataframe
|
||||
def read_cols_df_sql(ids,columns):
|
||||
columns = list(columns)+['distance','spm']
|
||||
columns = [x for x in columns if x != 'None']
|
||||
@@ -450,7 +481,7 @@ def read_cols_df_sql(ids,columns):
|
||||
engine.dispose()
|
||||
return df
|
||||
|
||||
|
||||
# Read stroke data from the DB for a Workout ID. Returns a pandas dataframe
|
||||
def read_df_sql(id):
|
||||
engine = create_engine(database_url, echo=False)
|
||||
|
||||
@@ -460,10 +491,8 @@ def read_df_sql(id):
|
||||
engine.dispose()
|
||||
return df
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Get the necessary data from the strokedata table in the DB.
|
||||
# For the flex plot
|
||||
def smalldataprep(therows,xparam,yparam1,yparam2):
|
||||
df = pd.DataFrame()
|
||||
if yparam2 == 'None':
|
||||
@@ -503,7 +532,10 @@ def smalldataprep(therows,xparam,yparam1,yparam2):
|
||||
|
||||
return df
|
||||
|
||||
|
||||
# This is the main routine.
|
||||
# it reindexes, sorts, filters, and smooths the data, then
|
||||
# saves it to the stroke_data table in the database
|
||||
# Takes a rowingdata object's DataFrame as input
|
||||
def dataprep(rowdatadf,id=0,bands=True,barchart=True,otwpower=True,
|
||||
empower=True):
|
||||
rowdatadf.set_index([range(len(rowdatadf))],inplace=True)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,7 +27,7 @@ from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SEC
|
||||
from rowsandall_app.settings import SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI, SPORTTRACKS_CLIENT_SECRET
|
||||
import requests
|
||||
import json
|
||||
from rowsandall_app.rows import handle_uploaded_file
|
||||
from rowers.rows import handle_uploaded_file
|
||||
from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
@@ -60,6 +60,7 @@ import plots
|
||||
from io import BytesIO
|
||||
from scipy.special import lambertw
|
||||
|
||||
# used in shell to send a newsletter to all Rowers
|
||||
def emailall(emailfile,subject):
|
||||
rowers = Rower.objects.all()
|
||||
for rower in rowers:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django import forms
|
||||
from rowers.models import Workout
|
||||
from rowsandall_app.rows import validate_file_extension,must_be_csv
|
||||
from rowers.rows import validate_file_extension,must_be_csv
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.admin.widgets import AdminDateWidget
|
||||
@@ -9,10 +9,12 @@ from django.utils import timezone,translation
|
||||
|
||||
import datetime
|
||||
|
||||
# login form
|
||||
class LoginForm(forms.Form):
|
||||
username = forms.CharField()
|
||||
password = forms.CharField(widget=forms.PasswordInput())
|
||||
|
||||
# simple form for Contact page. Sends email to info@rowsandall.com
|
||||
class EmailForm(forms.Form):
|
||||
firstname = forms.CharField(max_length=255)
|
||||
lastname = forms.CharField(max_length=255)
|
||||
@@ -21,16 +23,21 @@ class EmailForm(forms.Form):
|
||||
botcheck = forms.CharField(max_length=5)
|
||||
message = forms.CharField()
|
||||
|
||||
|
||||
# Upload the CrewNerd Summary CSV
|
||||
class CNsummaryForm(forms.Form):
|
||||
file = forms.FileField(required=True,validators=[must_be_csv])
|
||||
|
||||
# The little window to type '4x2000m/500m' to update the workout summary
|
||||
class SummaryStringForm(forms.Form):
|
||||
intervalstring = forms.CharField(max_length=255,label='Workout Description')
|
||||
|
||||
# Used for testing the POST API for StrokeData
|
||||
class StrokeDataForm(forms.Form):
|
||||
strokedata = forms.CharField(label='payload',
|
||||
widget=forms.Textarea)
|
||||
|
||||
# The form used for uploading files
|
||||
class DocumentsForm(forms.Form):
|
||||
filetypechoices = (
|
||||
('csv' , 'Painsled iOS CSV'),
|
||||
@@ -50,9 +57,6 @@ class DocumentsForm(forms.Form):
|
||||
workouttype = forms.ChoiceField(required=True,
|
||||
choices=Workout.workouttypes,
|
||||
initial='rower')
|
||||
# fileformat = forms.ChoiceField(required=True,
|
||||
# choices=filetypechoices,
|
||||
# initial='csv')
|
||||
notes = forms.CharField(required=False,
|
||||
widget=forms.Textarea)
|
||||
|
||||
@@ -60,7 +64,8 @@ class DocumentsForm(forms.Form):
|
||||
fields = ['title','file','workouttype','fileformat']
|
||||
|
||||
|
||||
|
||||
# The form to indicate additional actions to be performed immediately
|
||||
# after a successful upload
|
||||
class UploadOptionsForm(forms.Form):
|
||||
plotchoices = (
|
||||
('timeplot','Time Plot'),
|
||||
@@ -76,6 +81,8 @@ class UploadOptionsForm(forms.Form):
|
||||
class Meta:
|
||||
fields = ['make_plot','plottype','upload_toc2']
|
||||
|
||||
# This form is used on the Analysis page to add a custom distance/time
|
||||
# trial and predict the pace
|
||||
class PredictedPieceForm(forms.Form):
|
||||
unitchoices = (
|
||||
('t','minutes'),
|
||||
@@ -88,6 +95,7 @@ class PredictedPieceForm(forms.Form):
|
||||
class Meta:
|
||||
fields = ['value','pieceunit']
|
||||
|
||||
# On the Geeky side, to update stream information for river dwellers
|
||||
class UpdateStreamForm(forms.Form):
|
||||
unitchoices = (
|
||||
('m','m/s'),
|
||||
@@ -107,6 +115,7 @@ class UpdateStreamForm(forms.Form):
|
||||
class Meta:
|
||||
fields = ['dist1','dist2','stream1', 'stream2','streamunit']
|
||||
|
||||
# add wind information to your workout
|
||||
class UpdateWindForm(forms.Form):
|
||||
unitchoices = (
|
||||
('m','m/s'),
|
||||
@@ -134,8 +143,7 @@ class UpdateWindForm(forms.Form):
|
||||
'windunit',
|
||||
'winddirection1','winddirection2']
|
||||
|
||||
|
||||
|
||||
# Form to select a data range to show workouts from a certain time period
|
||||
class DateRangeForm(forms.Form):
|
||||
startdate = forms.DateField(initial=timezone.now()-datetime.timedelta(days=365),
|
||||
widget=SelectDateWidget(years=range(1990,2050)),
|
||||
@@ -147,6 +155,7 @@ class DateRangeForm(forms.Form):
|
||||
class Meta:
|
||||
fields = ['startdate','enddate']
|
||||
|
||||
# Form used to select workouts for the past N days
|
||||
class DeltaDaysForm(forms.Form):
|
||||
deltadays = forms.IntegerField(initial=0,required=False,label='')
|
||||
|
||||
@@ -191,13 +200,14 @@ class RegistrationFormUniqueEmail(RegistrationFormTermsOfService):
|
||||
raise forms.ValidationError("This email address is already in use. Please supply a different email address.")
|
||||
return self.cleaned_data['email']
|
||||
|
||||
|
||||
# Time field supporting microseconds. Not used, I believe.
|
||||
class MyTimeField(forms.TimeField):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(MyTimeField, self).__init__(*args, **kwargs)
|
||||
supports_microseconds = True
|
||||
|
||||
# Form used to update interval stats
|
||||
class IntervalUpdateForm(forms.Form):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ from sqlite3 import OperationalError
|
||||
from django.utils import timezone
|
||||
import datetime
|
||||
|
||||
from rowers.rows import validate_file_extension
|
||||
|
||||
from rowsandall_app.settings import (
|
||||
TWEET_ACCESS_TOKEN_KEY,
|
||||
TWEET_ACCESS_TOKEN_SECRET,
|
||||
@@ -47,12 +49,12 @@ database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
|
||||
if settings.DEBUG or user=='':
|
||||
database_url = 'sqlite:///db.sqlite3'
|
||||
|
||||
|
||||
# Create your models here.
|
||||
# For future Team functionality
|
||||
class Team(models.Model):
|
||||
name = models.CharField(max_length=150)
|
||||
notes = models.CharField(blank=True,max_length=200)
|
||||
|
||||
# Extension of User with rowing specific data
|
||||
class Rower(models.Model):
|
||||
weightcategories = (
|
||||
('hwt','heavy-weight'),
|
||||
@@ -95,6 +97,7 @@ class Rower(models.Model):
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
||||
# Saving a chart as a favorite chart
|
||||
class FavoriteChart(models.Model):
|
||||
y1params = (
|
||||
('hr','Heart Rate'),
|
||||
@@ -181,6 +184,7 @@ class FavoriteForm(ModelForm):
|
||||
fields = ['xparam','yparam1','yparam2',
|
||||
'plottype','workouttype','reststrokes']
|
||||
|
||||
# To generate favorite chart forms on the fly
|
||||
class BaseFavoriteFormSet(BaseFormSet):
|
||||
def clean(self):
|
||||
if any(self.errors):
|
||||
@@ -209,6 +213,7 @@ class BaseFavoriteFormSet(BaseFormSet):
|
||||
if not yparam2:
|
||||
yparam2 = 'None'
|
||||
|
||||
# Workout
|
||||
class Workout(models.Model):
|
||||
workouttypes = (
|
||||
('water','On-water'),
|
||||
@@ -273,7 +278,7 @@ def auto_delete_file_on_delete(sender, instance, **kwargs):
|
||||
if os.path.isfile(instance.csvfilename+'.gz'):
|
||||
os.remove(instance.csvfilename+'.gz')
|
||||
|
||||
|
||||
# Delete stroke data from the database when a workout is deleted
|
||||
@receiver(models.signals.post_delete,sender=Workout)
|
||||
def auto_delete_strokedata_on_delete(sender, instance, **kwargs):
|
||||
if instance.id:
|
||||
@@ -289,6 +294,11 @@ def auto_delete_strokedata_on_delete(sender, instance, **kwargs):
|
||||
conn.close()
|
||||
engine.dispose()
|
||||
|
||||
# Model of StrokeData table
|
||||
# the definition here is used only to enable easy Django migration
|
||||
# when the StrokeData are expanded.
|
||||
# No Django Instances of this model are managed. Strokedata table is
|
||||
# accesssed directly with SQL commands
|
||||
class StrokeData(models.Model):
|
||||
class Meta:
|
||||
db_table = 'strokedata'
|
||||
@@ -330,6 +340,7 @@ class StrokeData(models.Model):
|
||||
wash = models.FloatField(default=0,null=True)
|
||||
peakforceangle = models.FloatField(default=0,null=True)
|
||||
|
||||
# A wrapper around the png files
|
||||
class GraphImage(models.Model):
|
||||
filename = models.CharField(default='',max_length=150,blank=True,null=True)
|
||||
creationdatetime = models.DateTimeField()
|
||||
@@ -338,7 +349,7 @@ class GraphImage(models.Model):
|
||||
def __str__(self):
|
||||
return self.filename
|
||||
|
||||
# delete related file object
|
||||
# delete related file object when image is deleted
|
||||
@receiver(models.signals.post_delete,sender=GraphImage)
|
||||
def auto_delete_image_on_delete(sender,instance, **kwargs):
|
||||
if instance.filename:
|
||||
@@ -347,11 +358,11 @@ def auto_delete_image_on_delete(sender,instance, **kwargs):
|
||||
else:
|
||||
print "couldn't find the file "+instance.filename
|
||||
|
||||
|
||||
# Date input utility
|
||||
class DateInput(forms.DateInput):
|
||||
input_type = 'date'
|
||||
|
||||
|
||||
# Form to update Workout data
|
||||
class WorkoutForm(ModelForm):
|
||||
duration = forms.TimeInput(format='%H:%M:%S.%f')
|
||||
class Meta:
|
||||
@@ -368,16 +379,20 @@ class WorkoutForm(ModelForm):
|
||||
if self.instance.workouttype != 'water':
|
||||
del self.fields['boattype']
|
||||
|
||||
# Used for the rowing physics calculations
|
||||
class AdvancedWorkoutForm(ModelForm):
|
||||
class Meta:
|
||||
model = Workout
|
||||
fields = ['boattype','weightvalue']
|
||||
|
||||
# Simple form to set rower's Functional Threshold Power
|
||||
class RowerPowerForm(ModelForm):
|
||||
class Meta:
|
||||
model = Rower
|
||||
fields = ['ftp']
|
||||
|
||||
# Form to set rower's Heart Rate zones, including test routines
|
||||
# to enable consistency
|
||||
class RowerForm(ModelForm):
|
||||
class Meta:
|
||||
model = Rower
|
||||
@@ -519,6 +534,8 @@ class RowerForm(ModelForm):
|
||||
if an>=max:
|
||||
raise forms.ValidationError("AN should be lower than Max")
|
||||
|
||||
# An announcement that goes to the right of the workouts list
|
||||
# optionally sends a tweet to our twitter account
|
||||
class SiteAnnouncement(models.Model):
|
||||
created = models.DateField(default=timezone.now)
|
||||
announcement = models.TextField(max_length=140)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Interactions with Rowsandall.com API. Not fully complete.
|
||||
|
||||
# Python
|
||||
import oauth2 as oauth
|
||||
import cgi
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Defines permissions for objects (API related)
|
||||
|
||||
from rest_framework import permissions
|
||||
from rowers.models import Rower
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -71,13 +71,3 @@ def handle_uploaded_file(f):
|
||||
|
||||
return fname,fname2
|
||||
|
||||
# this might work on windows
|
||||
|
||||
#import magic
|
||||
#def validate_mime_type(value):
|
||||
# supported_types=['text/csv','application/vnd.garmin.tcx+xml']
|
||||
# with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m:
|
||||
# mime_type=m.id_buffer(value.file.read())
|
||||
# value.file.seek(0)
|
||||
# if mime_type not in supported_types:
|
||||
# raise ValidationError(u'Unsupported file type.')
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# All the functionality to connect to SportTracks
|
||||
|
||||
# Python
|
||||
import oauth2 as oauth
|
||||
import cgi
|
||||
@@ -31,6 +33,8 @@ from rowers.models import Rower,Workout
|
||||
|
||||
from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, SPORTTRACKS_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI
|
||||
|
||||
# Custom exception handler, returns a 401 HTTP message
|
||||
# with exception details in the json data
|
||||
def custom_exception_handler(exc,message):
|
||||
|
||||
response = {
|
||||
@@ -48,6 +52,7 @@ def custom_exception_handler(exc,message):
|
||||
|
||||
return res
|
||||
|
||||
# Refresh ST token using refresh token
|
||||
def do_refresh_token(refreshtoken):
|
||||
client_auth = requests.auth.HTTPBasicAuth(SPORTTRACKS_CLIENT_ID, SPORTTRACKS_CLIENT_SECRET)
|
||||
post_data = {"grant_type": "refresh_token",
|
||||
@@ -75,7 +80,7 @@ def do_refresh_token(refreshtoken):
|
||||
|
||||
return [thetoken,expires_in,refresh_token]
|
||||
|
||||
|
||||
# Exchange ST access code for long-lived ST access token
|
||||
def get_token(code):
|
||||
client_auth = requests.auth.HTTPBasicAuth(SPORTTRACKS_CLIENT_ID, SPORTTRACKS_CLIENT_SECRET)
|
||||
post_data = {"grant_type": "authorization_code",
|
||||
@@ -100,6 +105,7 @@ def get_token(code):
|
||||
|
||||
return [thetoken,expires_in,refresh_token]
|
||||
|
||||
# Make authorization URL including random string
|
||||
def make_authorization_url(request):
|
||||
# Generate a random string for the state parameter
|
||||
# Save it for use later to prevent xsrf attacks
|
||||
@@ -118,7 +124,7 @@ def make_authorization_url(request):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
# This is token refresh. Looks for tokens in our database, then refreshes
|
||||
def rower_sporttracks_token_refresh(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
res = do_refresh_token(r.sporttracksrefreshtoken)
|
||||
@@ -135,6 +141,7 @@ def rower_sporttracks_token_refresh(user):
|
||||
r.save()
|
||||
return r.sporttrackstoken
|
||||
|
||||
# Get list of workouts available on SportTracks
|
||||
def get_sporttracks_workout_list(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
|
||||
@@ -154,7 +161,7 @@ def get_sporttracks_workout_list(user):
|
||||
|
||||
return s
|
||||
|
||||
|
||||
# Get workout summary data by SportTracks ID
|
||||
def get_sporttracks_workout(user,sporttracksid):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.sporttrackstoken == '') or (r.sporttrackstoken is None):
|
||||
@@ -174,6 +181,7 @@ def get_sporttracks_workout(user,sporttracksid):
|
||||
|
||||
return s
|
||||
|
||||
# Create Workout Data for upload to SportTracks
|
||||
def createsporttracksworkoutdata(w):
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
@@ -272,6 +280,8 @@ def createsporttracksworkoutdata(w):
|
||||
|
||||
return data
|
||||
|
||||
# Obtain SportTracks Workout ID from the response returned on successful
|
||||
# upload
|
||||
def getidfromresponse(response):
|
||||
t = json.loads(response.text)
|
||||
uri = t['uris'][0]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# All the functionality needed to connect to Strava
|
||||
|
||||
# Python
|
||||
import oauth2 as oauth
|
||||
import cgi
|
||||
@@ -30,6 +32,9 @@ import stravalib
|
||||
|
||||
from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET
|
||||
|
||||
# Exponentially weighted moving average
|
||||
# Used for data smoothing of the jagged data obtained by Strava
|
||||
# See bitbucket issue 72
|
||||
def ewmovingaverage(interval,window_size):
|
||||
# Experimental code using Exponential Weighted moving average
|
||||
|
||||
@@ -45,45 +50,11 @@ def ewmovingaverage(interval,window_size):
|
||||
|
||||
return interval2
|
||||
|
||||
def geo_distance(lat1,lon1,lat2,lon2):
|
||||
""" Approximate distance and bearing between two points
|
||||
defined by lat1,lon1 and lat2,lon2
|
||||
This is a slight underestimate but is close enough for our purposes,
|
||||
We're never moving more than 10 meters between trackpoints
|
||||
|
||||
Bearing calculation fails if one of the points is a pole.
|
||||
|
||||
"""
|
||||
|
||||
# radius of earth in km
|
||||
R = 6373.0
|
||||
|
||||
# pi
|
||||
pi = math.pi
|
||||
|
||||
lat1 = math.radians(lat1)
|
||||
lat2 = math.radians(lat2)
|
||||
lon1 = math.radians(lon1)
|
||||
lon2 = math.radians(lon2)
|
||||
|
||||
dlon = lon2 - lon1
|
||||
dlat = lat2 - lat1
|
||||
|
||||
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
|
||||
c = 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||
|
||||
distance = R * c
|
||||
|
||||
tc1 = atan2(sin(lon2-lon1)*cos(lat2),
|
||||
cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(lon2-lon1))
|
||||
|
||||
tc1 = tc1 % (2*pi)
|
||||
|
||||
bearing = math.degrees(tc1)
|
||||
|
||||
return [distance,bearing]
|
||||
from utils import geo_distance
|
||||
|
||||
|
||||
# Custom exception handler, returns a 401 HTTP message
|
||||
# with exception details in the json data
|
||||
def custom_exception_handler(exc,message):
|
||||
|
||||
response = {
|
||||
@@ -101,6 +72,7 @@ def custom_exception_handler(exc,message):
|
||||
|
||||
return res
|
||||
|
||||
# Exchange access code for long-lived access token
|
||||
def get_token(code):
|
||||
client_auth = requests.auth.HTTPBasicAuth(STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET)
|
||||
post_data = {"grant_type": "authorization_code",
|
||||
@@ -118,7 +90,7 @@ def get_token(code):
|
||||
|
||||
return [thetoken]
|
||||
|
||||
|
||||
# Make authorization URL including random string
|
||||
def make_authorization_url(request):
|
||||
# Generate a random string for the state parameter
|
||||
# Save it for use later to prevent xsrf attacks
|
||||
@@ -134,6 +106,7 @@ def make_authorization_url(request):
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
# Get list of workouts available on Strava
|
||||
def get_strava_workout_list(user):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None):
|
||||
@@ -150,6 +123,7 @@ def get_strava_workout_list(user):
|
||||
|
||||
return s
|
||||
|
||||
# Get a Strava workout summary data and stroke data by ID
|
||||
def get_strava_workout(user,stravaid):
|
||||
r = Rower.objects.get(user=user)
|
||||
if (r.stravatoken == '') or (r.stravatoken is None):
|
||||
@@ -236,6 +210,7 @@ def get_strava_workout(user,stravaid):
|
||||
|
||||
return [workoutsummary,df]
|
||||
|
||||
# Generate Workout data for Strava (a TCX file)
|
||||
def createstravaworkoutdata(w):
|
||||
filename = w.csvfilename
|
||||
try:
|
||||
@@ -247,6 +222,8 @@ def createstravaworkoutdata(w):
|
||||
|
||||
return tcxfilename
|
||||
|
||||
# Upload the TCX file to Strava and set the workout activity type
|
||||
# to rowing on Strava
|
||||
def handle_stravaexport(file,workoutname,stravatoken,description=''):
|
||||
# w = Workout.objects.get(id=workoutid)
|
||||
client = stravalib.Client(access_token=stravatoken)
|
||||
@@ -264,12 +241,6 @@ def handle_stravaexport(file,workoutname,stravatoken,description=''):
|
||||
errorlog.write(timestr+errorstring+"\r\n")
|
||||
errorlog.write("stravastuff.py line 262\r\n")
|
||||
|
||||
|
||||
|
||||
# w.uploadedtostrava = res.id
|
||||
# w.save()
|
||||
# file.close()
|
||||
|
||||
return res.id
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +1,59 @@
|
||||
# This is just a scratch pad to temporarily park code, just in case I need
|
||||
# it later. Hardly used since i have proper versioning
|
||||
|
||||
#
|
||||
def createc2workoutdata_grouped(w):
|
||||
filename = w.csvfilename
|
||||
row = rowingdata(filename)
|
||||
|
||||
# resize per minute
|
||||
df = row.df.groupby(lambda x:x/10).mean()
|
||||
|
||||
averagehr = int(df[' HRCur (bpm)'].mean())
|
||||
maxhr = int(df[' HRCur (bpm)'].max())
|
||||
|
||||
# adding diff, trying to see if this is valid
|
||||
t = 10*df.ix[:,' ElapsedTime (sec)'].values
|
||||
t[0] = t[1]
|
||||
d = df.ix[:,' Horizontal (meters)'].values
|
||||
d[0] = d[1]
|
||||
p = 10*df.ix[:,' Stroke500mPace (sec/500m)'].values
|
||||
t = t.astype(int)
|
||||
d = d.astype(int)
|
||||
p = p.astype(int)
|
||||
spm = df[' Cadence (stokes/min)'].astype(int)
|
||||
spm[0] = spm[1]
|
||||
hr = df[' HRCur (bpm)'].astype(int)
|
||||
stroke_data = []
|
||||
for i in range(len(t)):
|
||||
thisrecord = {"t":t[i],"d":d[i],"p":p[i],"spm":spm[i],"hr":hr[i]}
|
||||
stroke_data.append(thisrecord)
|
||||
|
||||
|
||||
try:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S.%f")
|
||||
except ValueError:
|
||||
durationstr = datetime.strptime(str(w.duration),"%H:%M:%S")
|
||||
|
||||
|
||||
data = {
|
||||
"type": w.workouttype,
|
||||
"date": w.startdatetime.isoformat(),
|
||||
"distance": int(w.distance),
|
||||
"time": int(10*makeseconds(durationstr)),
|
||||
"weight_class": c2wc(w.weightcategory),
|
||||
"timezone": "Etc/UTC",
|
||||
"comments": w.notes,
|
||||
"heart_rate": {
|
||||
"average": averagehr,
|
||||
"max": maxhr,
|
||||
},
|
||||
"stroke_data": stroke_data,
|
||||
}
|
||||
|
||||
|
||||
return data
|
||||
|
||||
@login_required()
|
||||
def workout_edit_view(request,id=0):
|
||||
if request.method == 'POST':
|
||||
|
||||
@@ -8,7 +8,7 @@ import rowers.interactiveplots as iplots
|
||||
import datetime
|
||||
from rowingdata import rowingdata as rdata
|
||||
from rowingdata import rower as rrower
|
||||
from rowsandall_app.rows import handle_uploaded_file
|
||||
from rowers.rows import handle_uploaded_file
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from time import strftime,strptime,mktime,time,daylight
|
||||
import os
|
||||
@@ -1037,8 +1037,7 @@ class subroutinetests(TestCase):
|
||||
jsond = json.dumps(data)
|
||||
data = c2stuff.createc2workoutdata_as_splits(w)
|
||||
jsond = json.dumps(data)
|
||||
data = c2stuff.createc2workoutdata_as_grouped(w)
|
||||
jsond = json.dumps(data)
|
||||
|
||||
|
||||
|
||||
class PlotTests(TestCase):
|
||||
|
||||
@@ -100,7 +100,6 @@ urlpatterns = [
|
||||
url(r'^api-docs$', views.schema_view),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
url(r'^api/workouts/(?P<id>\d+)/strokedata$',views.strokedatajson),
|
||||
url(r'^testbokeh$',views.testbokeh),
|
||||
url(r'^500/$', TemplateView.as_view(template_name='500.html'),name='500'),
|
||||
url(r'^404/$', TemplateView.as_view(template_name='404.html'),name='404'),
|
||||
url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'),
|
||||
@@ -113,10 +112,6 @@ urlpatterns = [
|
||||
url(r'^list-workouts/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.workouts_view),
|
||||
url(r'^list-workouts/$',views.workouts_view),
|
||||
url(r'^list-graphs/$',views.graphs_view),
|
||||
url(r'^dashboard/c/(?P<message>\w+.*)/$',views.dashboard_view),
|
||||
url(r'^dashboard/s/(?P<successmessage>\w+.*)/$',views.dashboard_view),
|
||||
url(r'^dashboard/c/(?P<message>\w+.*)/s/(?P<successmessage>\w+.*)$',views.dashboard_view),
|
||||
url(r'^dashboard/$',views.dashboard_view),
|
||||
url(r'^(?P<theuser>\d+)/ote-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.rankings_view),
|
||||
url(r'^(?P<theuser>\d+)/ote-bests/(?P<deltadays>\d+)$',views.rankings_view),
|
||||
url(r'^ote-bests/(?P<startdatestring>\w+.*)/(?P<enddatestring>\w+.*)$',views.rankings_view),
|
||||
@@ -194,7 +189,6 @@ urlpatterns = [
|
||||
url(r'^workout/sporttracksimport/$',views.workout_sporttracksimport_view),
|
||||
url(r'^workout/sporttracksimport/(\d+)/$',views.workout_getsporttracksworkout_view),
|
||||
url(r'^workout/(\d+)/deleteconfirm$',views.workout_delete_confirm_view),
|
||||
url(r'^workout/(\d+)/c2upload/$',views.list_c2_upload_view),
|
||||
url(r'^workout/(\d+)/c2uploadw/$',views.workout_c2_upload_view),
|
||||
url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view),
|
||||
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
|
||||
@@ -232,7 +226,6 @@ urlpatterns = [
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [
|
||||
url(r'^workout/uploadd/$',views.workout_upload_view_debug),
|
||||
url(r'^testreverse/$',views.test_reverse_view),
|
||||
url(r'^c2listug/$',views.c2listdebug_view),
|
||||
]
|
||||
|
||||
42
rowers/utils.py
Normal file
42
rowers/utils.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import math
|
||||
|
||||
def geo_distance(lat1,lon1,lat2,lon2):
|
||||
""" Approximate distance and bearing between two points
|
||||
defined by lat1,lon1 and lat2,lon2
|
||||
This is a slight underestimate but is close enough for our purposes,
|
||||
We're never moving more than 10 meters between trackpoints
|
||||
|
||||
Bearing calculation fails if one of the points is a pole.
|
||||
(Hey, from the North pole you can walk South, East, North and end up
|
||||
on the same spot!)
|
||||
|
||||
"""
|
||||
|
||||
# radius of our earth in km --> should be moved to settings if
|
||||
# rowing takes off on other planets
|
||||
R = 6373.0
|
||||
|
||||
# pi
|
||||
pi = math.pi
|
||||
|
||||
lat1 = math.radians(lat1)
|
||||
lat2 = math.radians(lat2)
|
||||
lon1 = math.radians(lon1)
|
||||
lon2 = math.radians(lon2)
|
||||
|
||||
dlon = lon2 - lon1
|
||||
dlat = lat2 - lat1
|
||||
|
||||
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
|
||||
c = 2 * atan2(sqrt(a), sqrt(1 - a))
|
||||
|
||||
distance = R * c
|
||||
|
||||
tc1 = atan2(sin(lon2-lon1)*cos(lat2),
|
||||
cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(lon2-lon1))
|
||||
|
||||
tc1 = tc1 % (2*pi)
|
||||
|
||||
bearing = math.degrees(tc1)
|
||||
|
||||
return [distance,bearing]
|
||||
631
rowers/views.py
631
rowers/views.py
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
|
||||
@@ -1,33 +1,5 @@
|
||||
|
||||
from django import forms
|
||||
from rowers.models import Workout
|
||||
from rows import validate_file_extension
|
||||
|
||||
class ContactForm(forms.Form):
|
||||
subject = forms.CharField()
|
||||
email = forms.EmailField(required=False)
|
||||
message = forms.CharField(widget=forms.Textarea)
|
||||
|
||||
class DocumentsForm(forms.Form):
|
||||
filetypechoices = (
|
||||
('tcx' , 'TCX'),
|
||||
('csv' , 'Painsled CSV')
|
||||
)
|
||||
title = forms.CharField(required=False)
|
||||
file = forms.FileField(required=True,
|
||||
validators=[validate_file_extension])
|
||||
workouttype = forms.ChoiceField(required=True,
|
||||
choices=Workout.workouttypes,
|
||||
initial='rower')
|
||||
fileformat = forms.ChoiceField(required=True,
|
||||
choices=filetypechoices,
|
||||
initial='csv')
|
||||
notes = forms.CharField(required=False,
|
||||
widget=forms.Textarea)
|
||||
|
||||
class Meta:
|
||||
fields = ['title','file','workouttype','fileformat']
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from django.db import models
|
||||
from rows import validate_file_extension
|
||||
|
||||
class Document(models.Model):
|
||||
docfile = models.FileField(upload_to='documents/%Y/%m/%d',
|
||||
validators=[validate_file_extension])
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
def addrc(x,y,z):
|
||||
return int(x)+int(y)
|
||||
@@ -1,121 +0,0 @@
|
||||
"""
|
||||
Django settings for rowsandall_app project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.9.5.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.9/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.9/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '&cg#y8h-8s#00ayk#gu)+l43j1j9^9r&qf$3!$x#ov@1houiph'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'rowsandall_app.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR,'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'rowsandall_app.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.9/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'Europe/Prague'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.9/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
@@ -1,3 +0,0 @@
|
||||
graph = {{ my_data|safe }};
|
||||
|
||||
mpld3.draw_figure("fig01", graph);
|
||||
@@ -17,9 +17,7 @@ from django.conf.urls import url,include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from rowsandall_app.views import rootview,version,rowingdata,showStaticImage,\
|
||||
add,wait,nrowingdata,waitforplot,showplot,interactiveplot
|
||||
from rowsandall_app.views import uploadfile
|
||||
from rowsandall_app.views import rootview
|
||||
from django.contrib.auth import views as auth_views
|
||||
from rowers import views as rowersviews
|
||||
|
||||
@@ -28,8 +26,6 @@ urlpatterns = [
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
url(r'^$',rootview),
|
||||
url(r'^version/$',version),
|
||||
url(r'^addresult/(.+.*)/$',wait),
|
||||
url(r'^login/',auth_views.login, name='login'),
|
||||
url(r'^logout/',auth_views.logout_then_login,name='logout'),
|
||||
url(r'^password_change_done/$',auth_views.password_change_done,name='password_change_done'),
|
||||
@@ -42,12 +38,10 @@ urlpatterns = [
|
||||
url(r'^password_reset_complete/$',auth_views.password_reset_complete,name='password_reset_complete'),
|
||||
url(r'^rowers/',include('rowers.urls')),
|
||||
url(r'^cvkbrno/',include('cvkbrno.urls')),
|
||||
url(r'^add/(\d+)/(\d+)/$',add),
|
||||
url(r'^call\_back',rowersviews.rower_process_callback),
|
||||
url(r'^stravacall\_back',rowersviews.rower_process_stravacallback),
|
||||
url(r'^sporttracks\_callback',rowersviews.rower_process_sporttrackscallback),
|
||||
url(r'^twitter\_callback',rowersviews.rower_process_twittercallback),
|
||||
url(r'^interactiveplot',interactiveplot),
|
||||
url(r'^i18n/', include('django.conf.urls.i18n')),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,258 +1,11 @@
|
||||
from django.http import HttpResponse,Http404,HttpResponseRedirect
|
||||
from django.template import Template, Context
|
||||
from django.template.loader import get_template
|
||||
from django.shortcuts import render, redirect, render_to_response
|
||||
from django.conf import settings
|
||||
from rowsandall_app.forms import ContactForm,DocumentsForm
|
||||
from django.core.mail import send_mail
|
||||
from django.core.urlresolvers import reverse
|
||||
from matplotlib.pyplot import figure, axes, pie, title
|
||||
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
||||
from matplotlib.ticker import MultipleLocator,FuncFormatter,NullFormatter
|
||||
import matplotlib.pyplot as plt
|
||||
from rowingdata import rower as rrower
|
||||
|
||||
from rowingdata import main as rmain
|
||||
from rowingdata import rowingdata as rdata
|
||||
from rowingdata import TCXParser
|
||||
import StringIO
|
||||
from django.contrib.auth.decorators import login_required
|
||||
|
||||
import django_rq
|
||||
queue = django_rq.get_queue('default')
|
||||
|
||||
import datetime
|
||||
import pandas as pd
|
||||
import os
|
||||
import numpy as np
|
||||
import json
|
||||
import mpld3
|
||||
|
||||
from rctasks import addrc
|
||||
|
||||
from rows import *
|
||||
from rowers.tasks import add as addtask
|
||||
from rowers.tasks import handle_makeplot
|
||||
from celery.result import AsyncResult
|
||||
from celery.exceptions import TimeoutError
|
||||
from rowers.models import Rower,User,Workout,GraphImage
|
||||
|
||||
|
||||
def showStaticImage(request,imagename):
|
||||
return render(request, 'image_page.html', {'imagename': imagename})
|
||||
|
||||
def rootview(request):
|
||||
magicsentence = rmain()
|
||||
return render(request, 'frontpage.html', {'versionstring': magicsentence})
|
||||
|
||||
|
||||
def version(request):
|
||||
magicsentence = rmain()
|
||||
return render(request, 'base.html', {'versionstring': magicsentence})
|
||||
|
||||
def rowingdata(request,formloc):
|
||||
if request.method == 'POST':
|
||||
form = DocumentsForm(request.POST,request.FILES)
|
||||
|
||||
if form.is_valid():
|
||||
f = request.FILES['file']
|
||||
t = request.POST['title']
|
||||
res = handle_uploaded_file(f)
|
||||
f1 = res[0]
|
||||
f2 = res[1]
|
||||
row = rdata(f2)
|
||||
fig1 = row.get_timeplot_erg(t)
|
||||
|
||||
canvas = FigureCanvasAgg(fig1)
|
||||
# response = HttpResponse(content_type='image/png')
|
||||
# canvas.print_png(response)
|
||||
imagename = f1+'.png'
|
||||
plt.savefig('static/plots/'+imagename,format='png')
|
||||
plt.close(fig1)
|
||||
response = render(request,'image_page.html',{'imagename':'plots/'+imagename})
|
||||
|
||||
else:
|
||||
response = HttpResponse("invalid form")
|
||||
|
||||
return response
|
||||
else:
|
||||
form = DocumentsForm()
|
||||
return render(request, 'document_form.html',
|
||||
{'form':form, 'formloc': formloc})
|
||||
|
||||
@login_required()
|
||||
def nrowingdata(request,formloc):
|
||||
if request.method == 'POST':
|
||||
form = DocumentsForm(request.POST,request.FILES)
|
||||
|
||||
if form.is_valid():
|
||||
f = request.FILES['file']
|
||||
res = handle_uploaded_file(f)
|
||||
t = request.POST['title']
|
||||
fileformat = request.POST['fileformat']
|
||||
workouttype = request.POST['workouttype']
|
||||
notes = request.POST['notes']
|
||||
|
||||
f1 = res[0] # file name
|
||||
f2 = res[1] # file name incl media directory
|
||||
|
||||
# handle TCX
|
||||
if (fileformat == 'tcx'):
|
||||
row = TCXParser(f2)
|
||||
f_to_be_deleted = f2
|
||||
# should delete file
|
||||
f2 = f2+'.csv'
|
||||
row.write_csv(f2)
|
||||
os.remove(f_to_be_deleted)
|
||||
|
||||
imagename = f1+'.png'
|
||||
fullpathimagename = 'static/plots/'+imagename
|
||||
u = request.user
|
||||
r = Rower.objects.get(user=request.user)
|
||||
hrdata = {
|
||||
'hrmax':r.max,
|
||||
'hrut2':r.ut2,
|
||||
'hrut1':r.ut1,
|
||||
'hrat':r.at,
|
||||
'hrtr':r.tr,
|
||||
'hran':r.an,
|
||||
}
|
||||
|
||||
# make plot - asynchronous task
|
||||
res = handle_makeplot.delay(f1,f2,t,hrdata)
|
||||
|
||||
# make workout and put in database
|
||||
rr = rrower(hrmax=r.max,hrut2=r.ut2,
|
||||
hrut1=r.ut1,hrat=r.at,
|
||||
hrtr=r.tr,hran=r.an)
|
||||
row = rdata(f2,rower=rr)
|
||||
totaldist = row.df['cum_dist'].max()
|
||||
totaltime = row.df['TimeStamp (sec)'].max()-row.df['TimeStamp (sec)'].min()
|
||||
totaltime = totaltime+row.df.ix[0,' ElapsedTime (sec)']
|
||||
|
||||
|
||||
hours = int(totaltime/3600.)
|
||||
minutes = int((totaltime - 3600.*hours)/60.)
|
||||
seconds = int(totaltime - 3600.*hours - 60.*minutes)
|
||||
|
||||
duration = "%s:%s:%s" % (hours,minutes,seconds)
|
||||
|
||||
|
||||
workoutdate = row.rowdatetime.strftime('%Y-%m-%d')
|
||||
workoutstarttime = row.rowdatetime.strftime('%H:%M:%S')
|
||||
|
||||
# check for duplicate start times
|
||||
r = Rower.objects.get(user=request.user)
|
||||
|
||||
ws = Workout.objects.filter(starttime=workoutstarttime,
|
||||
user=r)
|
||||
if (len(ws) != 0):
|
||||
print "Warning: This workout probably already exists in the database"
|
||||
|
||||
w = Workout(user=r,name=t,date=workoutdate,workouttype=workouttype,
|
||||
duration=duration,distance=totaldist,
|
||||
weightcategory=r.weightcategory,
|
||||
starttime=workoutstarttime,
|
||||
csvfilename=f2,notes=notes)
|
||||
w.save()
|
||||
|
||||
i = GraphImage(workout=w,creationdatetime=datetime.datetime.now(),
|
||||
filename=fullpathimagename)
|
||||
i.save()
|
||||
|
||||
url = reverse(waitforplot,args=[str(res.id)])
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
else:
|
||||
response = HttpResponse("invalid form")
|
||||
|
||||
return response
|
||||
else:
|
||||
form = DocumentsForm()
|
||||
return render(request, 'document_form.html',
|
||||
{'form':form, 'formloc': formloc})
|
||||
|
||||
|
||||
|
||||
def uploadfile(request,formloc):
|
||||
if request.method == 'POST':
|
||||
form = DocumentsForm(request.POST, request.FILES)
|
||||
|
||||
if form.is_valid():
|
||||
result = handle_uploaded_file(request.FILES['file'])
|
||||
return HttpResponse("succes! "+result)
|
||||
else:
|
||||
return HttpResponse("Invalid Form")
|
||||
else:
|
||||
form = DocumentsForm()
|
||||
return render(request, 'document_form.html',
|
||||
{'form': form, 'formloc':formloc})
|
||||
|
||||
@login_required()
|
||||
def add(request,x,y):
|
||||
if settings.DEBUG:
|
||||
task = addtask.apply_async((int(x),int(y)),countdown=10)
|
||||
task_id = AsyncResult(task)
|
||||
print task.id
|
||||
else:
|
||||
task = queue.enqueue(addrc,x,y,0)
|
||||
task_id = task.id
|
||||
|
||||
url = reverse(wait,args=[str(task_id)])
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
||||
|
||||
|
||||
def wait(request,task_id=""):
|
||||
if settings.DEBUG:
|
||||
res = AsyncResult(task_id)
|
||||
else:
|
||||
res = queue.fetch_job(task_id)
|
||||
if (res.status == 'SUCCESS') or (res.status == 'finished'):
|
||||
return HttpResponse("Task complete. Result: "+str(res.result))
|
||||
else:
|
||||
m = "Task status: "+str(res.status)
|
||||
return render(request,'waiting_page.html',{'message':m})
|
||||
return HttpResponse("Task status: "+str(res.status)+". <p>Hit reload to check again")
|
||||
|
||||
|
||||
def waitforplot(request,task_id=""):
|
||||
res = AsyncResult(task_id)
|
||||
if (res.status == 'SUCCESS'):
|
||||
url = reverse(showplot,args=[str(res.result)])
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
m = "Task status: "+str(res.status)
|
||||
|
||||
return render(request,'waiting_page.html',{'message':m})
|
||||
|
||||
def showplot(request,imagename=""):
|
||||
return render(request,'image_page.html',{'imagename':'plots/'+imagename})
|
||||
|
||||
|
||||
def contact(request):
|
||||
if request.method == 'POST':
|
||||
form = ContactForm(request.POST)
|
||||
if form.is_valid():
|
||||
cd = form.cleaned_data
|
||||
send_mail(
|
||||
cd['subject'],
|
||||
cd['message'],
|
||||
cd.get('email', 'noreply@example.com'),
|
||||
['siteowner@example.com'],
|
||||
)
|
||||
return HttpResponseRedirect('/contact/thanks/')
|
||||
else:
|
||||
form = ContactForm()
|
||||
return render(request, 'contact_form.html', {'form': form})
|
||||
|
||||
def interactiveplot(request):
|
||||
x = np.linspace(-10,10,num=1200)
|
||||
y = x**2-5*x+4
|
||||
fig = plt.figure(figsize=(6,5))
|
||||
graph = plt.plot(x,y)
|
||||
g = mpld3.fig_to_html(fig)
|
||||
# js_data = json.dumps(mpld3.fig_to_dict(fig))
|
||||
plt.close()
|
||||
# return render(request,'interactiveplot.html',{"my_data":js_data})
|
||||
return HttpResponse(g)
|
||||
|
||||
Reference in New Issue
Block a user