Merge branch 'release/v10.05'
This commit is contained in:
+14
-11
@@ -8,13 +8,13 @@ atomicwrites==1.3.0
|
||||
attrs==19.1.0
|
||||
backcall==0.1.0
|
||||
beautifulsoup4==4.7.1
|
||||
billiard==3.5.0.5
|
||||
billiard==3.6.0.0
|
||||
bleach==3.1.0
|
||||
bokeh==1.0.4
|
||||
boto==2.49.0
|
||||
braintree==3.51.0
|
||||
braintree==3.55.0
|
||||
cairocffi==1.0.2
|
||||
celery==4.2.2
|
||||
celery==4.3.0
|
||||
certifi==2019.3.9
|
||||
cffi==1.12.2
|
||||
chardet==3.0.4
|
||||
@@ -30,7 +30,7 @@ cycler==0.10.0
|
||||
dask==1.1.4
|
||||
decorator==4.4.0
|
||||
defusedxml==0.5.0
|
||||
Django==2.2.1
|
||||
Django==2.1.7
|
||||
django-analytical==2.5.0
|
||||
django-async-messages==0.3.1
|
||||
django-braces==1.13.0
|
||||
@@ -46,9 +46,10 @@ django-leaflet==0.24.0
|
||||
django-mailbox==4.7.1
|
||||
django-oauth-toolkit==1.2.0
|
||||
django-oauth2-provider==0.2.6.1
|
||||
django-redis==4.10.0
|
||||
django-rest-framework==0.1.0
|
||||
django-rest-swagger==2.2.0
|
||||
django-rq==2.0
|
||||
django-rq==1.3.1
|
||||
django-rq-dashboard==0.3.3
|
||||
django-ses==0.8.10
|
||||
django-shell-plus==1.1.7
|
||||
@@ -66,7 +67,9 @@ Faker==1.0.4
|
||||
fitparse==1.1.0
|
||||
Flask==1.0.2
|
||||
future==0.17.1
|
||||
#GDAL==2.3.3
|
||||
geocoder==1.38.1
|
||||
geos==0.2.1
|
||||
holoviews==1.11.3
|
||||
html5lib==1.0.1
|
||||
htmlmin==0.1.12
|
||||
@@ -96,7 +99,7 @@ jupyterlab==0.35.4
|
||||
jupyterlab-server==0.3.0
|
||||
keyring==18.0.0
|
||||
kiwisolver==1.0.1
|
||||
kombu==4.3.0
|
||||
kombu==4.5.0
|
||||
lxml==4.3.2
|
||||
Markdown==3.0.1
|
||||
MarkupSafe==1.1.1
|
||||
@@ -129,9 +132,9 @@ pip-upgrader==1.4.6
|
||||
pluggy==0.9.0
|
||||
prometheus-client==0.6.0
|
||||
prompt-toolkit==2.0.9
|
||||
psycopg2==2.8.1
|
||||
ptyprocess==0.6.0
|
||||
py==1.8.0
|
||||
pycairo==1.18.0
|
||||
pycparser==2.19
|
||||
Pygments==2.3.1
|
||||
pyparsing==2.3.1
|
||||
@@ -157,10 +160,9 @@ ratelim==0.1.6
|
||||
redis==3.2.1
|
||||
requests==2.21.0
|
||||
requests-oauthlib==1.2.0
|
||||
rowingdata==2.4.9
|
||||
rowingdata==2.4.4
|
||||
rowingphysics==0.5.0
|
||||
rq==1.0
|
||||
rq-dashboard==0.4.0
|
||||
rq==0.13.0
|
||||
scipy==1.2.1
|
||||
SecretStorage==3.1.1
|
||||
Send2Trash==1.5.0
|
||||
@@ -188,8 +190,9 @@ VerbalExpressions==0.0.2
|
||||
vine==1.3.0
|
||||
wcwidth==0.1.7
|
||||
webencodings==0.5.1
|
||||
Werkzeug==0.15.2
|
||||
Werkzeug==0.15.1
|
||||
widgetsnbextension==3.4.2
|
||||
#winkerberos==0.7.0
|
||||
xlrd==1.2.0
|
||||
xmltodict==0.12.0
|
||||
yamjam==0.1.7
|
||||
|
||||
@@ -101,6 +101,11 @@ def get_plans_costs():
|
||||
|
||||
def make_payment(rower,data):
|
||||
nonce_from_the_client = data['payment_method_nonce']
|
||||
nonce = gateway.payment_method_nonce.find(nonce_from_the_client)
|
||||
info = nonce.three_d_secure_info
|
||||
if info is None or not info.liability_shifted:
|
||||
return False,0
|
||||
|
||||
amount = data['amount']
|
||||
amount = '{amount:.f2}'.format(amount=amount)
|
||||
|
||||
@@ -131,6 +136,10 @@ def update_subscription(rower,data,method='up'):
|
||||
planid = data['plan']
|
||||
plan = PaidPlan.objects.get(id=planid)
|
||||
nonce_from_the_client = data['payment_method_nonce']
|
||||
nonce = gateway.payment_method_nonce.find(nonce_from_the_client)
|
||||
info = nonce.three_d_secure_info
|
||||
if info is None or not info.liability_shifted:
|
||||
return False,0
|
||||
amount = data['amount']
|
||||
amount = '{amount:.2f}'.format(amount=amount)
|
||||
|
||||
@@ -229,6 +238,10 @@ def update_subscription(rower,data,method='up'):
|
||||
|
||||
def create_subscription(rower,data):
|
||||
nonce_from_the_client = data['payment_method_nonce']
|
||||
nonce = gateway.payment_method_nonce.find(nonce_from_the_client)
|
||||
info = nonce.three_d_secure_info
|
||||
if info is None or not info.liability_shifted:
|
||||
return False,0
|
||||
amount = data['amount']
|
||||
|
||||
planid = data['plan']
|
||||
|
||||
@@ -2208,6 +2208,9 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
|
||||
else:
|
||||
drivenergy = drivelength * averageforce
|
||||
|
||||
powerhr = 60.*power/hr
|
||||
powerhr = powerhr.fillna(value=0)
|
||||
|
||||
if driveenergy.mean() == 0 and driveenergy.std() == 0:
|
||||
driveenergy = 0*driveenergy+100
|
||||
|
||||
@@ -2237,6 +2240,7 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
|
||||
drivespeed=drivespeed,
|
||||
rhythm=rhythm,
|
||||
distanceperstroke=distanceperstroke,
|
||||
powerhr=powerhr,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1207,6 +1207,13 @@ class WorkoutRaceSelectForm(forms.Form):
|
||||
|
||||
# self.fields['evaluate_after'] =
|
||||
|
||||
# form to send messages to team members
|
||||
class TeamMessageForm(forms.Form):
|
||||
message = forms.CharField(required=True,
|
||||
initial='',
|
||||
widget=forms.Textarea()
|
||||
)
|
||||
|
||||
# Form to select team by rower
|
||||
class RowerTeamForm(forms.Form):
|
||||
team = forms.ModelChoiceField(
|
||||
|
||||
@@ -34,8 +34,6 @@ from rowers.models import User,VirtualRace,Workout
|
||||
from rowers.plannedsessions import email_submit_race
|
||||
|
||||
|
||||
workoutmailbox = Mailbox.objects.get(name='workouts')
|
||||
failedmailbox = Mailbox.objects.get(name='Failed')
|
||||
|
||||
# If you find a solution that does not need the two paths, please comment!
|
||||
sys.path.append('$path_to_root_of_project$')
|
||||
@@ -189,9 +187,31 @@ class Command(BaseCommand):
|
||||
default=False,
|
||||
help="Run in testing mode, don't send emails",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--mailbox',
|
||||
action='store_true',
|
||||
dest='mailbox',
|
||||
default='workouts',
|
||||
help="Changing mailbox name",
|
||||
)
|
||||
|
||||
"""Run the Email processing command """
|
||||
def handle(self, *args, **options):
|
||||
if 'testing' in options:
|
||||
testing = options['testing']
|
||||
else:
|
||||
testing = False
|
||||
|
||||
if 'mailbox' in options:
|
||||
workoutmailbox = Mailbox.objects.get(name=options['mailbox'])
|
||||
else:
|
||||
workoutmailbox = Mailbox.objects.get(name='workouts')
|
||||
|
||||
if 'failedmailbox' in options:
|
||||
failedmailbox = Mailbox.objects.get(name=options['failedmailbox'])
|
||||
else:
|
||||
failedmailbox = Mailbox.objects.get(name='Failed')
|
||||
|
||||
# Polar
|
||||
polar_available = polarstuff.get_polar_notifications()
|
||||
res = polarstuff.get_all_new_workouts(polar_available)
|
||||
@@ -207,10 +227,6 @@ class Command(BaseCommand):
|
||||
attachments = MessageAttachment.objects.filter(
|
||||
message_id__in=message_ids
|
||||
)
|
||||
if 'testing' in options:
|
||||
testing = options['testing']
|
||||
else:
|
||||
testing = False
|
||||
cntr = 0
|
||||
for attachment in attachments:
|
||||
filename, extension = os.path.splitext(attachment.document.name)
|
||||
|
||||
@@ -86,6 +86,15 @@ rowingmetrics = (
|
||||
'mode':'both',
|
||||
'type':'pro'}),
|
||||
|
||||
('powerhr',{
|
||||
'numtype':'float',
|
||||
'null':True,
|
||||
'verbose_name': 'Power Heart Rate Efficiency (J/beat)',
|
||||
'ax_min':0,
|
||||
'ax_max':300,
|
||||
'mode':'both',
|
||||
'type':'pro'}),
|
||||
|
||||
('spm',{
|
||||
'numtype':'float',
|
||||
'null':True,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 2.1.7 on 2019-04-24 20:56
|
||||
# Generated by Django 2.1.7 on 2019-07-26 09:28
|
||||
|
||||
import datetime
|
||||
from django.conf import settings
|
||||
@@ -122,9 +122,9 @@ class Migration(migrations.Migration):
|
||||
name='FavoriteChart',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('yparam1', models.CharField(choices=[('averageforce', 'Average Drive Force (N)'), ('forceratio', 'Average/Peak Force Ratio'), ('velo', 'Boat Speed (m/s)'), ('catch', 'Catch Angle (degrees)'), ('cumdist', 'Cumulative Distance (m)'), ('distance', 'Distance (m)'), ('distanceperstroke', 'Distance per Stroke (m)'), ('totalangle', 'Drive Length (deg)'), ('drivelength', 'Drive Length (m)'), ('drivespeed', 'Drive Speed (m/s)'), ('effectiveangle', 'Effective Drive Length (deg)'), ('finish', 'Finish Angle (degrees)'), ('hr', 'Heart Rate (bpm)'), ('efficiency', 'OTW Efficiency (%)'), ('pace', 'Pace (/500m)'), ('peakforce', 'Peak Drive Force (N)'), ('peakforceangle', 'Peak Force Angle'), ('power', 'Power (W)'), ('slip', 'Slip (degrees)'), ('spm', 'Stroke Rate (spm)'), ('rhythm', 'Stroke Rhythm'), ('time', 'Time'), ('wash', 'Wash (degrees)'), ('driveenergy', 'Work Per Stroke (J)')], max_length=50, verbose_name='Y1')),
|
||||
('yparam2', models.CharField(blank=True, choices=[('None', '<None>'), ('averageforce', 'Average Drive Force (N)'), ('forceratio', 'Average/Peak Force Ratio'), ('velo', 'Boat Speed (m/s)'), ('catch', 'Catch Angle (degrees)'), ('cumdist', 'Cumulative Distance (m)'), ('distance', 'Distance (m)'), ('distanceperstroke', 'Distance per Stroke (m)'), ('totalangle', 'Drive Length (deg)'), ('drivelength', 'Drive Length (m)'), ('drivespeed', 'Drive Speed (m/s)'), ('effectiveangle', 'Effective Drive Length (deg)'), ('finish', 'Finish Angle (degrees)'), ('hr', 'Heart Rate (bpm)'), ('efficiency', 'OTW Efficiency (%)'), ('pace', 'Pace (/500m)'), ('peakforce', 'Peak Drive Force (N)'), ('peakforceangle', 'Peak Force Angle'), ('power', 'Power (W)'), ('slip', 'Slip (degrees)'), ('spm', 'Stroke Rate (spm)'), ('rhythm', 'Stroke Rhythm'), ('time', 'Time'), ('wash', 'Wash (degrees)'), ('driveenergy', 'Work Per Stroke (J)')], default='None', max_length=50, verbose_name='Y2')),
|
||||
('xparam', models.CharField(choices=[('None', '<None>'), ('averageforce', 'Average Drive Force (N)'), ('forceratio', 'Average/Peak Force Ratio'), ('velo', 'Boat Speed (m/s)'), ('catch', 'Catch Angle (degrees)'), ('cumdist', 'Cumulative Distance (m)'), ('distance', 'Distance (m)'), ('distanceperstroke', 'Distance per Stroke (m)'), ('totalangle', 'Drive Length (deg)'), ('drivelength', 'Drive Length (m)'), ('drivespeed', 'Drive Speed (m/s)'), ('effectiveangle', 'Effective Drive Length (deg)'), ('finish', 'Finish Angle (degrees)'), ('hr', 'Heart Rate (bpm)'), ('efficiency', 'OTW Efficiency (%)'), ('pace', 'Pace (/500m)'), ('peakforce', 'Peak Drive Force (N)'), ('peakforceangle', 'Peak Force Angle'), ('power', 'Power (W)'), ('slip', 'Slip (degrees)'), ('spm', 'Stroke Rate (spm)'), ('rhythm', 'Stroke Rhythm'), ('time', 'Time'), ('wash', 'Wash (degrees)'), ('driveenergy', 'Work Per Stroke (J)')], max_length=50, verbose_name='X')),
|
||||
('yparam1', models.CharField(choices=[('averageforce', 'Average Drive Force (N)'), ('forceratio', 'Average/Peak Force Ratio'), ('velo', 'Boat Speed (m/s)'), ('catch', 'Catch Angle (degrees)'), ('cumdist', 'Cumulative Distance (m)'), ('distance', 'Distance (m)'), ('distanceperstroke', 'Distance per Stroke (m)'), ('totalangle', 'Drive Length (deg)'), ('drivelength', 'Drive Length (m)'), ('drivespeed', 'Drive Speed (m/s)'), ('effectiveangle', 'Effective Drive Length (deg)'), ('finish', 'Finish Angle (degrees)'), ('hr', 'Heart Rate (bpm)'), ('efficiency', 'OTW Efficiency (%)'), ('pace', 'Pace (/500m)'), ('peakforce', 'Peak Drive Force (N)'), ('peakforceangle', 'Peak Force Angle'), ('power', 'Power (W)'), ('powerhr', 'Power Heart Rate Efficiency (J/beat)'), ('slip', 'Slip (degrees)'), ('spm', 'Stroke Rate (spm)'), ('rhythm', 'Stroke Rhythm'), ('time', 'Time'), ('wash', 'Wash (degrees)'), ('driveenergy', 'Work Per Stroke (J)')], max_length=50, verbose_name='Y1')),
|
||||
('yparam2', models.CharField(blank=True, choices=[('None', '<None>'), ('averageforce', 'Average Drive Force (N)'), ('forceratio', 'Average/Peak Force Ratio'), ('velo', 'Boat Speed (m/s)'), ('catch', 'Catch Angle (degrees)'), ('cumdist', 'Cumulative Distance (m)'), ('distance', 'Distance (m)'), ('distanceperstroke', 'Distance per Stroke (m)'), ('totalangle', 'Drive Length (deg)'), ('drivelength', 'Drive Length (m)'), ('drivespeed', 'Drive Speed (m/s)'), ('effectiveangle', 'Effective Drive Length (deg)'), ('finish', 'Finish Angle (degrees)'), ('hr', 'Heart Rate (bpm)'), ('efficiency', 'OTW Efficiency (%)'), ('pace', 'Pace (/500m)'), ('peakforce', 'Peak Drive Force (N)'), ('peakforceangle', 'Peak Force Angle'), ('power', 'Power (W)'), ('powerhr', 'Power Heart Rate Efficiency (J/beat)'), ('slip', 'Slip (degrees)'), ('spm', 'Stroke Rate (spm)'), ('rhythm', 'Stroke Rhythm'), ('time', 'Time'), ('wash', 'Wash (degrees)'), ('driveenergy', 'Work Per Stroke (J)')], default='None', max_length=50, verbose_name='Y2')),
|
||||
('xparam', models.CharField(choices=[('None', '<None>'), ('averageforce', 'Average Drive Force (N)'), ('forceratio', 'Average/Peak Force Ratio'), ('velo', 'Boat Speed (m/s)'), ('catch', 'Catch Angle (degrees)'), ('cumdist', 'Cumulative Distance (m)'), ('distance', 'Distance (m)'), ('distanceperstroke', 'Distance per Stroke (m)'), ('totalangle', 'Drive Length (deg)'), ('drivelength', 'Drive Length (m)'), ('drivespeed', 'Drive Speed (m/s)'), ('effectiveangle', 'Effective Drive Length (deg)'), ('finish', 'Finish Angle (degrees)'), ('hr', 'Heart Rate (bpm)'), ('efficiency', 'OTW Efficiency (%)'), ('pace', 'Pace (/500m)'), ('peakforce', 'Peak Drive Force (N)'), ('peakforceangle', 'Peak Force Angle'), ('power', 'Power (W)'), ('powerhr', 'Power Heart Rate Efficiency (J/beat)'), ('slip', 'Slip (degrees)'), ('spm', 'Stroke Rate (spm)'), ('rhythm', 'Stroke Rhythm'), ('time', 'Time'), ('wash', 'Wash (degrees)'), ('driveenergy', 'Work Per Stroke (J)')], max_length=50, verbose_name='X')),
|
||||
('plottype', models.CharField(choices=[('line', 'Line Chart'), ('scatter', 'Scatter Chart')], default='line', max_length=50, verbose_name='Chart Type')),
|
||||
('workouttype', models.CharField(choices=[('ote', 'Erg/SkiErg'), ('otw', 'On The Water'), ('all', 'All'), ('strava', 'strava'), ('concept2', 'concept2'), ('sporttracks', 'sporttracks'), ('runkeeper', 'runkeeper'), ('mapmyfitness', 'mapmyfitness'), ('csv', 'painsled'), ('tcx', 'tcx'), ('rp', 'rowperfect'), ('mystery', 'mystery'), ('rowperfect3', 'rowperfect3'), ('ergdata', 'ergdata'), ('boatcoach', 'boatcoach'), ('boatcoachotw', 'boatcoachotw'), ('painsleddesktop', 'painsleddesktop'), ('speedcoach', 'speedcoach'), ('speedcoach2', 'speedcoach2'), ('ergstick', 'ergstick'), ('fit', 'fit'), ('unknown', 'unknown')], default='both', max_length=50, verbose_name='Workout Type')),
|
||||
('reststrokes', models.BooleanField(default=True, verbose_name='Incl. Rest')),
|
||||
@@ -192,7 +192,7 @@ class Migration(migrations.Migration):
|
||||
name='PaidPlan',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('shortname', models.CharField(choices=[('basic', 'basic'), ('pro', 'pro'), ('plan', 'plan'), ('coach', 'coach')], max_length=50)),
|
||||
('shortname', models.CharField(choices=[('basic', 'basic'), ('pro', 'pro'), ('plan', 'plan'), ('coach', 'coach'), ('freecoach', 'freecoach')], max_length=50)),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('external_id', models.CharField(blank=True, default=None, max_length=200, null=True)),
|
||||
('price', models.FloatField(blank=True, default=None, null=True)),
|
||||
@@ -268,7 +268,7 @@ class Migration(migrations.Migration):
|
||||
('postal_code', models.CharField(blank=True, default='', max_length=200, null=True)),
|
||||
('customer_id', models.CharField(blank=True, default=None, max_length=200, null=True)),
|
||||
('subscription_id', models.CharField(blank=True, default=None, max_length=200, null=True)),
|
||||
('rowerplan', models.CharField(choices=[('basic', 'basic'), ('pro', 'pro'), ('plan', 'plan'), ('coach', 'coach')], default='basic', max_length=30)),
|
||||
('rowerplan', models.CharField(choices=[('basic', 'basic'), ('pro', 'pro'), ('plan', 'plan'), ('coach', 'coach'), ('freecoach', 'freecoach')], default='basic', max_length=30)),
|
||||
('paymenttype', models.CharField(choices=[('single', 'single'), ('recurring', 'recurring')], default='single', max_length=30, verbose_name='Payment Type')),
|
||||
('paymentprocessor', models.CharField(blank=True, choices=[('paypal', 'PayPal'), ('braintree', 'BrainTree')], default='braintree', max_length=50, null=True)),
|
||||
('planexpires', models.DateField(default=rowers.models.current_day)),
|
||||
@@ -390,6 +390,7 @@ class Migration(migrations.Migration):
|
||||
('hr', models.IntegerField(null=True, verbose_name='Heart Rate (bpm)')),
|
||||
('pace', models.FloatField(null=True, verbose_name='Pace (/500m)')),
|
||||
('velo', models.FloatField(default=0, null=True, verbose_name='Boat Speed (m/s)')),
|
||||
('powerhr', models.FloatField(null=True, verbose_name='Power Heart Rate Efficiency (J/beat)')),
|
||||
('spm', models.FloatField(null=True, verbose_name='Stroke Rate (spm)')),
|
||||
('driveenergy', models.FloatField(null=True, verbose_name='Work Per Stroke (J)')),
|
||||
('power', models.FloatField(null=True, verbose_name='Power (W)')),
|
||||
@@ -568,7 +569,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(blank=True, max_length=150, null=True)),
|
||||
('date', models.DateField()),
|
||||
('workouttype', models.CharField(choices=[('water', 'Standard Racing Shell'), ('rower', 'Indoor Rower'), ('skierg', 'Ski Erg'), ('bike', 'Bike Erg'), ('dynamic', 'Dynamic Indoor Rower'), ('slides', 'Indoor Rower on Slides'), ('paddle', 'Paddle Adapter'), ('snow', 'On-snow'), ('coastal', 'Coastal'), ('c-boat', 'Dutch C boat'), ('churchboat', 'Finnish Church boat'), ('Ride', 'Ride'), ('Run', 'Run'), ('NordicSki', 'NordicSki'), ('Swim', 'Swim'), ('Hike', 'Hike'), ('Walk', 'Walk'), ('Canoeing', 'Canoeing'), ('Crossfit', 'Crossfit'), ('StandUpPaddling', 'StandUpPaddling'), ('IceSkate', 'IceSkate'), ('WeightTraining', 'WeightTraining'), ('InlineSkate', 'InlineSkate'), ('Kayaking', 'Kayaking'), ('Workout', 'Workout'), ('other', 'Other')], max_length=50, verbose_name='Exercise/Boat Class')),
|
||||
('workouttype', models.CharField(choices=[('water', 'Standard Racing Shell'), ('rower', 'Indoor Rower'), ('skierg', 'Ski Erg'), ('bikeerg', 'Bike Erg'), ('dynamic', 'Dynamic Indoor Rower'), ('slides', 'Indoor Rower on Slides'), ('paddle', 'Paddle Adapter'), ('snow', 'On-snow'), ('coastal', 'Coastal'), ('c-boat', 'Dutch C boat'), ('churchboat', 'Finnish Church boat'), ('Ride', 'Ride'), ('Bike', 'Bike'), ('Run', 'Run'), ('NordicSki', 'NordicSki'), ('Swim', 'Swim'), ('Hike', 'Hike'), ('Walk', 'Walk'), ('Canoeing', 'Canoeing'), ('Crossfit', 'Crossfit'), ('StandUpPaddling', 'StandUpPaddling'), ('IceSkate', 'IceSkate'), ('WeightTraining', 'WeightTraining'), ('InlineSkate', 'InlineSkate'), ('Kayaking', 'Kayaking'), ('Workout', 'Workout'), ('Yoga', 'Yoga'), ('other', 'Other')], max_length=50, verbose_name='Exercise/Boat Class')),
|
||||
('workoutsource', models.CharField(default='unknown', max_length=100)),
|
||||
('boattype', models.CharField(choices=[('1x', '1x (single)'), ('2x', '2x (double)'), ('2x+', '2x+ (coxed double)'), ('2-', '2- (pair)'), ('2+', '2+ (coxed pair)'), ('3x+', '3x+ (coxed triple)'), ('3x-', '3x- (triple)'), ('4x', '4x (quad)'), ('4x+', '4x+ (coxed quad)'), ('4-', '4- (four)'), ('4+', '4+ (coxed four)'), ('8+', '8+ (eight)'), ('8x+', '8x+ (octuple scull)')], default='1x', max_length=50, verbose_name='Boat Type')),
|
||||
('adaptiveclass', models.CharField(choices=[('None', 'None'), ('PR1', 'PR1 (Arms and Shoulders)'), ('PR2', 'PR2 (Trunk and Arms)'), ('PR3', 'PR3 (Leg Trunk and Arms)'), ('FES', 'FES (Functional Electrical Stimulation)')], default='None', max_length=50, verbose_name='Adaptive Classification')),
|
||||
|
||||
+4
-3
@@ -272,7 +272,7 @@ class PowerTimeFitnessMetric(models.Model):
|
||||
PowerTwoK = models.FloatField(default=0)
|
||||
PowerOneHour = models.FloatField(default=0)
|
||||
workoutmode = models.CharField(default='rower',choices=modechoices,
|
||||
max_length=40)
|
||||
max_length=41,)
|
||||
class Meta:
|
||||
db_table = 'powertimefitnessmetric'
|
||||
|
||||
@@ -861,7 +861,7 @@ class Rower(models.Model):
|
||||
privacy = models.CharField(default='visible',max_length=30,
|
||||
choices=privacychoices)
|
||||
|
||||
team = models.ManyToManyField(Team,blank=True)
|
||||
team = models.ManyToManyField(Team,blank=True,related_name='rower')
|
||||
|
||||
# Export and Time Zone Settings
|
||||
defaulttimezone = models.CharField(default='UTC',max_length=100,
|
||||
@@ -2908,9 +2908,10 @@ class Meta:
|
||||
index_together = ['workoutid']
|
||||
app_label = 'rowers'
|
||||
|
||||
attrs = {'__module__': '', 'Meta': Meta}
|
||||
attrs = {'__module__': 'rowers.models', 'Meta': Meta}
|
||||
attrs.update(strokedatafields)
|
||||
|
||||
|
||||
# Model of StrokeData table
|
||||
# the definition here is used only to enable easy Django migration
|
||||
# when the StrokeData are expanded.
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ stravamapping = collections.OrderedDict({
|
||||
'water':'Rowing',
|
||||
'rower':'Rowing',
|
||||
'skierg':'NordicSki',
|
||||
'bike':'Ride',
|
||||
'Bike':'Ride',
|
||||
'bikeerg':'Ride',
|
||||
'dynamic':'Rowing',
|
||||
'slides':'Rowing',
|
||||
|
||||
@@ -1321,6 +1321,44 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_ical(first_name, last_name, email, url, icsfile, **kwargs):
|
||||
# send email with attachment
|
||||
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
|
||||
subject = "Calendar File for your sessions from Rowsandall.com"
|
||||
|
||||
if 'debug' in kwargs:
|
||||
debug = kwargs['debug']
|
||||
else:
|
||||
debug = False
|
||||
|
||||
siteurl = SITE_URL
|
||||
if debug:
|
||||
progressurl = SITE_URL_DEV
|
||||
siteurl = SITE_URL_DEV
|
||||
|
||||
|
||||
d = {'first_name':first_name,
|
||||
'siteurl':siteurl,
|
||||
'url':url,
|
||||
}
|
||||
|
||||
from_email = 'Rowsandall <info@rowsandall.com>'
|
||||
|
||||
|
||||
res = send_template_email(from_email,[fullemail],
|
||||
subject,'icsemail.html',d,
|
||||
attach_file=icsfile,**kwargs)
|
||||
|
||||
|
||||
try:
|
||||
os.remove(csvfile)
|
||||
except:
|
||||
pass
|
||||
|
||||
return 1
|
||||
|
||||
|
||||
@app.task
|
||||
def handle_sendemailfile(first_name, last_name, email, csvfile,**kwargs):
|
||||
|
||||
@@ -1982,6 +2020,25 @@ def handle_sendemailnewcomment(first_name,
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_message(email,fromemail,rowername,message,teamname,managername,
|
||||
debug=False,**kwargs):
|
||||
|
||||
fullemail = email
|
||||
subject = 'New message from team ' + teamname
|
||||
from_email = fromemail
|
||||
|
||||
d = {
|
||||
'rowername':rowername,
|
||||
'teamname':teamname,
|
||||
'managername':managername,
|
||||
'message':message,
|
||||
}
|
||||
|
||||
res = send_template_email(from_email,[fullemail],subject,
|
||||
'teammessage.html',d,**kwargs)
|
||||
|
||||
return 1
|
||||
|
||||
@app.task
|
||||
def handle_sendemail_request(email, name, code, teamname, requestor, id,
|
||||
|
||||
+22
-1
@@ -37,6 +37,7 @@ from rowers.tasks import (
|
||||
handle_sendemail_coachoffer_accepted,
|
||||
handle_sendemail_coachrequest_rejected,
|
||||
handle_sendemail_coachrequest_accepted,
|
||||
handle_sendemail_message,
|
||||
)
|
||||
|
||||
from rowers.models import ValidationError
|
||||
@@ -170,11 +171,21 @@ def remove_coach(coach,rower):
|
||||
|
||||
return (1,'Coach removed')
|
||||
|
||||
def rower_get_managers(rower):
|
||||
managers = []
|
||||
for team in rower.team.all():
|
||||
managers.append(team.manager.rower)
|
||||
|
||||
return managers
|
||||
|
||||
def rower_get_coaches(rower):
|
||||
coaches = []
|
||||
for group in rower.coachinggroups.all():
|
||||
try:
|
||||
coach = Rower.objects.get(mycoachgroup=group)
|
||||
coaches.append(coach)
|
||||
except Rower.DoesNotExist:
|
||||
pass
|
||||
|
||||
return coaches
|
||||
|
||||
@@ -567,7 +578,17 @@ def send_invite_accept_email(invitation):
|
||||
|
||||
return (1,'Invitation email sent')
|
||||
|
||||
def send_team_message(team,message):
|
||||
rowers = team.rower.all()
|
||||
managername = team.manager.first_name + " " + team.manager.last_name
|
||||
|
||||
for rower in rowers:
|
||||
rowername = rower.user.first_name + " " + rower.user.last_name
|
||||
res = myqueue(queuehigh,
|
||||
handle_sendemail_message,
|
||||
rower.user.email,team.manager.email,rowername,message,team.name,managername)
|
||||
|
||||
return (1,'message sent')
|
||||
|
||||
def send_request_email(rekwest):
|
||||
email = rekwest.team.manager.email
|
||||
@@ -606,7 +627,7 @@ def process_request_code(manager,code):
|
||||
|
||||
|
||||
rekwest.delete()
|
||||
return result
|
||||
return (result,'The member was added')
|
||||
|
||||
def process_invite_code(user,code):
|
||||
code = code.upper()
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
<script src="https://js.braintreegateway.com/web/dropin/1.14.1/js/dropin.min.js"></script>
|
||||
{% load rowerfilters %}
|
||||
<script src="https://js.braintreegateway.com/web/dropin/1.19.0/js/dropin.min.js"></script>
|
||||
<script>
|
||||
var form = document.querySelector('#payment-form');
|
||||
var hiddenNonceInput = document.querySelector('#nonce');
|
||||
var paymenterrordiv = document.getElementById("paymenterror");
|
||||
var paymenterrordiv2 = document.getElementById("paymenterror2");
|
||||
var client_token = '{{ client_token }}';
|
||||
braintree.dropin.create({
|
||||
authorization: client_token,
|
||||
container: '#bt-dropin',
|
||||
paypal: {
|
||||
flow: 'vault'
|
||||
},
|
||||
threeDSecure: {
|
||||
amount: '{{ plan.price|amount }}',
|
||||
email: '{{ rower.user.email }}',
|
||||
billingAddress: {
|
||||
givenName: '{{ user.first_name }}',
|
||||
surname: '{{ user.last_name }}',
|
||||
streetAddress: '{{ user.rower.street_address }}',
|
||||
locality: '{{ user.rower.city }}',
|
||||
postalCode: '{{ user.rower.postal_code }}',
|
||||
countryCodeAlpha2: '{{ user.rower.country }}'
|
||||
},
|
||||
}
|
||||
}, function (createErr, instance) {
|
||||
form.addEventListener('submit', function (event) {
|
||||
@@ -16,10 +32,28 @@
|
||||
console.log('Error', err);
|
||||
return;
|
||||
}
|
||||
// Add the nonce to the form and submit
|
||||
if (payload.liabilityShifted || payload.type !== 'CreditCard') {
|
||||
document.querySelector('#nonce').value = payload.nonce;
|
||||
hiddenNonceInput.value = payload.nonce;
|
||||
form.submit();
|
||||
} else {
|
||||
// Decide if you will force the user to enter a different
|
||||
// payment method if liablity was not shifted
|
||||
instance.clearSelectedPaymentMethod();
|
||||
paymenterrordiv.innerHTML = '<p class="message">3D Secure Card Verification Error. Please check your card details.</p>';
|
||||
var xhr = new XMLHttpRequest();
|
||||
console.log('preparing message');
|
||||
xhr.open('GET','/rowers/errormessage/3dsecure/');
|
||||
console.log('sending message');
|
||||
xhr.send();
|
||||
console.log('message sent');
|
||||
xhr.onreadystatechange = function() {
|
||||
console.log('reloading');
|
||||
window.location.reload();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -30,5 +64,5 @@
|
||||
$('#payment-form').submit(function() {
|
||||
$(this).find("button[type='submit']").prop('disabled',true);
|
||||
console.log("disabled");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
<h2>Teams</h2>
|
||||
|
||||
<p>
|
||||
The <a href="/rowers/teams/">Teams</a> tab brings you to
|
||||
The <a href="/rowers/me/teams/">Teams</a> tab brings you to
|
||||
functionality related to interaction with your team, if you
|
||||
are part of one.
|
||||
</p>
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
<p>Dear <strong>{{ first_name }}</strong>,</p>
|
||||
|
||||
<p>
|
||||
Please find attached the requested ICS Calendar file. You can import
|
||||
Please find attached the ICS Calendar file for your training sessions. You can import
|
||||
this file to your calendar app.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can see the training plan here: <a href="{{ siteurl }}{{ url }}">{{ siteurl }}{{ url }}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Best Regards, the Rowsandall Team
|
||||
</p>
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
{% extends "newbase.html" %}
|
||||
{% block title %}Rowsandall Paid Membership{% endblock title %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
|
||||
{% block main %}
|
||||
|
||||
<div id="paymenterror">
|
||||
</div>
|
||||
|
||||
<h1>Confirm Your Payment</h1>
|
||||
|
||||
<h2>Order Overview</h2>
|
||||
|
||||
@@ -79,10 +79,10 @@
|
||||
<td> {{ ps.sessionvalue }} </td>
|
||||
<td> {{ ps.sessionunit }} </td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/user/{{ rower.user.id }}/?when={{ timeperiod }}">Edit</a>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/user/{{ rower.user.id }}/?when={{ timeperiod }}/?when={{ timeperiod }}">Edit</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/clone/user/{{ rower.user.id }}/?when={{ timeperiod }}">Clone</a>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/clone/user/{{ rower.user.id }}/?when={{ timeperiod }}/?when={{ timeperiod }}">Clone</a>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
|
||||
@@ -96,11 +96,11 @@
|
||||
<td> {{ ps.sessionvalue }} </td>
|
||||
<td> {{ ps.sessionunit }} </td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/user/{{ rower.user.id }}">Edit</a>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/edit/user/{{ rower.user.id }}/?when={{ timeperiod }}">Edit</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small"
|
||||
href="/rowers/sessions/{{ ps.id }}/clone/user/{{ rower.user.id }}">Clone</a>
|
||||
href="/rowers/sessions/{{ ps.id }}/clone/user/{{ rower.user.id }}/?when={{ timeperiod }}">Clone</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm">Delete</a>
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
|
||||
{% block main %}
|
||||
{% if theteam %}
|
||||
<h1>Coach Overview. Team {{ theteam.name }}</h1>
|
||||
<h1>Group Overview. Team {{ theteam.name }}</h1>
|
||||
{% else %}
|
||||
<h1>Coach Overview</h1>
|
||||
<h1>Group Overview</h1>
|
||||
{% endif %}
|
||||
|
||||
<ul class="main-content">
|
||||
@@ -83,6 +83,17 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
<a href="/rowers/sessions/sendcalendar/user/{{ rower.user.id }}/?when={{ timeperiod }}">
|
||||
Get Calendar File</a>
|
||||
</p>
|
||||
{% if rower|is_coach:rowers %}
|
||||
<p>
|
||||
<a href="/rowers/sessions/coach/sendcalendar/user/{{ rower.user.id }}/?when={{ timeperiod }}">
|
||||
Send Calendar File to all rowers
|
||||
</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% if unmatchedworkouts %}
|
||||
|
||||
@@ -72,6 +72,19 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="grid_2">
|
||||
{% if team.manager == user %}
|
||||
<h2>Message</h2>
|
||||
<p>Use this form to send a message to all team members by email. </p>
|
||||
<form enctype="multipart/form-date" action="" method="post">
|
||||
<table>
|
||||
{{ groupmessageform.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Send">
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{% extends "emailbase.html" %}
|
||||
|
||||
{% block body %}
|
||||
<p>Dear <strong>{{ first_name }}</strong>,</p>
|
||||
|
||||
<p>
|
||||
Here is a message from {{ managername }}, the team manager of rowsandall.com team {{ teamname }}.
|
||||
</p>
|
||||
<p>
|
||||
<code>
|
||||
{{ message }}
|
||||
</code>
|
||||
</p>
|
||||
{% endblock %}
|
||||
@@ -3,6 +3,9 @@
|
||||
{% load rowerfilters %}
|
||||
{% block main %}
|
||||
|
||||
<div id="paymenterror">
|
||||
</div>
|
||||
|
||||
<h1>Confirm Your Payment</h1>
|
||||
|
||||
<h2>Order Overview</h2>
|
||||
@@ -90,7 +93,7 @@
|
||||
<div id="bt-dropin"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div id="paymenterror2"> </div>
|
||||
<input type="hidden" id="nonce" name="payment_method_nonce" />
|
||||
<input type="hidden" id="plan" name="plan" value="{{ plan.id }}">
|
||||
<p>
|
||||
|
||||
@@ -66,6 +66,17 @@ def strfdelta(tdelta):
|
||||
|
||||
return res
|
||||
|
||||
from rowers.teams import rower_get_managers
|
||||
|
||||
@register.filter
|
||||
def is_coach(rower,rowers):
|
||||
for r in rowers:
|
||||
if rower not in rower_get_managers(r):
|
||||
print(r,rower)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def strfdeltah(tdelta):
|
||||
hours, rest = divmod(tdelta.seconds,3600)
|
||||
minutes,seconds = divmod(rest,60)
|
||||
@@ -484,8 +495,17 @@ def future_date_only(the_date):
|
||||
def is_future_date(the_date):
|
||||
return the_date >= date.today()
|
||||
|
||||
@register.filter
|
||||
def amount(value):
|
||||
vs = '{v}.00'.format(v=int(value))
|
||||
return vs
|
||||
|
||||
|
||||
|
||||
@register.filter
|
||||
def date_dif(the_date):
|
||||
if the_date == date(1970,1,1):
|
||||
return 1
|
||||
if the_date:
|
||||
return the_date - date.today()
|
||||
else:
|
||||
|
||||
@@ -173,10 +173,6 @@ def mocked_getsmallrowdata_db(*args, **kwargs):
|
||||
|
||||
return df
|
||||
|
||||
def mocked_read_cols_df_sql(*args, **kwargs):
|
||||
df = pd.read_csv('rowers/tests/testdata/readcols.csv')
|
||||
|
||||
return df,[]
|
||||
|
||||
def mocked_getsmallrowdata_db_water(*args, **kwargs):
|
||||
df = pd.read_csv('rowers/tests/testdata/colsfromdb.csv')
|
||||
@@ -204,11 +200,11 @@ def mocked_read_df_cols_sql_multistats(ids,columns,convertnewtons=True):
|
||||
|
||||
return df, extracols
|
||||
|
||||
def mocked_read_df_cols_sql(ids, columns, convertnewtons=True):
|
||||
df = pd.read_csv('rowers/tests/testdata/fake_strokedata.csv')
|
||||
extracols = []
|
||||
def mocked_read_cols_df_sql(*args, **kwargs):
|
||||
df = pd.read_csv('rowers/tests/testdata/readcols.csv')
|
||||
|
||||
return df,[]
|
||||
|
||||
return df, extracols
|
||||
|
||||
def mock_workout_summaries(*args, **kwargs):
|
||||
df = pd.read_csv('rowers/tests/testdata/workout_summaries.csv')
|
||||
|
||||
@@ -24,7 +24,7 @@ class EmailUpload(TestCase):
|
||||
rowerplan='coach')
|
||||
|
||||
nu = datetime.datetime.now()
|
||||
workoutsbox = Mailbox.objects.create(name='workouts')
|
||||
workoutsbox = Mailbox.objects.create(name='workouts1')
|
||||
workoutsbox.save()
|
||||
failbox = Mailbox.objects.create(name='Failed')
|
||||
failbox.save()
|
||||
@@ -57,7 +57,7 @@ workout run
|
||||
def test_emailupload(
|
||||
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
|
||||
out = StringIO()
|
||||
call_command('processemail', stdout=out,testing=True)
|
||||
call_command('processemail', stdout=out,testing=True,mailbox='workouts1')
|
||||
self.assertIn('Successfully processed email attachments',out.getvalue())
|
||||
|
||||
ws = Workout.objects.filter(name="run")
|
||||
@@ -86,7 +86,7 @@ class EmailBikeErgUpload(TestCase):
|
||||
rowerplan='coach')
|
||||
|
||||
nu = datetime.datetime.now()
|
||||
workoutsbox = Mailbox.objects.create(name='workouts')
|
||||
workoutsbox = Mailbox.objects.create(name='workouts2')
|
||||
workoutsbox.save()
|
||||
failbox = Mailbox.objects.create(name='Failed')
|
||||
failbox.save()
|
||||
@@ -104,7 +104,7 @@ workout bikeerg
|
||||
a.save()
|
||||
|
||||
def tearDown(self):
|
||||
for filename in os.listdir('media/mailbox_attachments'):
|
||||
filename = 'colin3.csv'
|
||||
path = os.path.join('media/mailbox_attachments/',filename)
|
||||
if not os.path.isdir(path):
|
||||
try:
|
||||
@@ -119,7 +119,7 @@ workout bikeerg
|
||||
def test_emailupload(
|
||||
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
|
||||
out = StringIO()
|
||||
call_command('processemail', stdout=out,testing=True)
|
||||
call_command('processemail', stdout=out,testing=True,mailbox='workouts2')
|
||||
self.assertIn('Successfully processed email attachments',out.getvalue())
|
||||
|
||||
ws = Workout.objects.filter(name="bikeerg")
|
||||
@@ -128,6 +128,67 @@ workout bikeerg
|
||||
w = ws[0]
|
||||
self.assertEqual(w.workouttype,'bikeerg')
|
||||
|
||||
@override_settings(TESTING=True)
|
||||
class EmailBikeUpload(TestCase):
|
||||
def setUp(self):
|
||||
redis_connection.publish('tasks','KILL')
|
||||
u = User.objects.create_user('john',
|
||||
'sander@ds.ds',
|
||||
'koeinsloot')
|
||||
r = Rower.objects.create(user=u,gdproptin=True,
|
||||
gdproptindate=timezone.now()
|
||||
)
|
||||
|
||||
self.theadmin = UserFactory(is_staff=True)
|
||||
self.rtheadmin = Rower.objects.create(user=self.theadmin,
|
||||
birthdate = faker.profile()['birthdate'],
|
||||
gdproptin=True,
|
||||
gdproptindate=timezone.now(),
|
||||
rowerplan='coach')
|
||||
|
||||
nu = datetime.datetime.now()
|
||||
workoutsbox = Mailbox.objects.create(name='workouts3')
|
||||
workoutsbox.save()
|
||||
failbox = Mailbox.objects.create(name='Failed')
|
||||
failbox.save()
|
||||
|
||||
m = Message(mailbox=workoutsbox,
|
||||
from_header = u.email,
|
||||
subject = "bike",
|
||||
body = """
|
||||
workout bike
|
||||
""")
|
||||
m.save()
|
||||
a2 = 'media/mailbox_attachments/colin4.csv'
|
||||
copy('rowers/tests/testdata/emails/colin.csv',a2)
|
||||
a = MessageAttachment(message=m,document=a2[6:])
|
||||
a.save()
|
||||
|
||||
def tearDown(self):
|
||||
filename = 'colin4.csv'
|
||||
path = os.path.join('media/mailbox_attachments/',filename)
|
||||
if not os.path.isdir(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except (IOError,FileNotFoundError,OSError):
|
||||
pass
|
||||
|
||||
@patch('rowers.dataprep.create_engine')
|
||||
@patch('rowers.polarstuff.get_polar_notifications')
|
||||
@patch('rowers.c2stuff.requests.get', side_effect=mocked_requests)
|
||||
@patch('rowers.c2stuff.requests.post', side_effect=mocked_requests)
|
||||
def test_emailupload(
|
||||
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
|
||||
out = StringIO()
|
||||
call_command('processemail', stdout=out,testing=True,mailbox='workouts3')
|
||||
self.assertIn('Successfully processed email attachments',out.getvalue())
|
||||
|
||||
ws = Workout.objects.filter(name="bike")
|
||||
|
||||
self.assertEqual(len(ws),1)
|
||||
w = ws[0]
|
||||
self.assertEqual(w.workouttype,'Bike')
|
||||
|
||||
|
||||
|
||||
#@pytest.mark.django_db
|
||||
@@ -150,7 +211,7 @@ class EmailTests(TestCase):
|
||||
rowerplan='coach')
|
||||
|
||||
nu = datetime.datetime.now()
|
||||
workoutsbox = Mailbox.objects.create(name='workouts')
|
||||
workoutsbox = Mailbox.objects.create(name='workouts4')
|
||||
workoutsbox.save()
|
||||
failbox = Mailbox.objects.create(name='Failed')
|
||||
failbox.save()
|
||||
@@ -209,7 +270,7 @@ race 1
|
||||
def test_emailprocessing(
|
||||
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
|
||||
out = StringIO()
|
||||
call_command('processemail', stdout=out,testing=True)
|
||||
call_command('processemail', stdout=out,testing=True,mailbox='workouts4')
|
||||
self.assertIn('Successfully processed email attachments',out.getvalue())
|
||||
|
||||
|
||||
@@ -236,7 +297,7 @@ class EmailAdminUpload(TestCase):
|
||||
self.race = RaceFactory(manager = self.theadmin)
|
||||
|
||||
nu = datetime.datetime.now()
|
||||
workoutsbox = Mailbox.objects.create(name='workouts')
|
||||
workoutsbox = Mailbox.objects.create(name='workouts5')
|
||||
workoutsbox.save()
|
||||
failbox = Mailbox.objects.create(name='Failed')
|
||||
failbox.save()
|
||||
@@ -270,7 +331,7 @@ race 1
|
||||
def test_email_admin_upload(
|
||||
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
|
||||
out = StringIO()
|
||||
call_command('processemail', stdout=out,testing=True)
|
||||
call_command('processemail', stdout=out,testing=True,mailbox='workouts5')
|
||||
self.assertIn('Successfully processed email attachments',out.getvalue())
|
||||
|
||||
ws = Workout.objects.filter(name="johnsworkout")
|
||||
|
||||
Vendored
+40507
-40507
File diff suppressed because it is too large
Load Diff
+768
-768
File diff suppressed because it is too large
Load Diff
Vendored
+21921
-21921
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
@@ -511,11 +511,13 @@ urlpatterns = [
|
||||
# re_path(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'),
|
||||
re_path(r'^analysis/user/(?P<userid>\d+)/$', views.analysis_view,name='analysis'),
|
||||
re_path(r'^laboratory/user/(?P<userid>\d+)/$', views.laboratory_view,name='laboratory'),
|
||||
re_path(r'^errormessage/(?P<errormessage>[\w\ ]+.*)/$',views.errormessage_view,name='errormessage_view'),
|
||||
re_path(r'^analysis/$', views.analysis_view,name='analysis'),
|
||||
re_path(r'^laboratory/$', views.laboratory_view,name='laboratory'),
|
||||
re_path(r'^promembership', TemplateView.as_view(template_name='promembership.html'),name='promembership'),
|
||||
re_path(r'^checkout/(?P<planid>\d+)/$',views.payment_confirm_view,name='payment_confirm_view'),
|
||||
re_path(r'^upgradecheckout/(?P<planid>\d+)/$',views.upgrade_confirm_view,name='upgrade_confirm_view'),
|
||||
re_path(r'^upgradecheckout/(?P<planid>\d+)/$',views.upgrade_confirm_view,name='upgrade_confirm_view'),
|
||||
re_path(r'^downgradecheckout/(?P<planid>\d+)/$',views.downgrade_confirm_view,name='downgrade_confirm_view'),
|
||||
re_path(r'^billing/$',views.billing_view,name='billing'),
|
||||
re_path(r'^upgrade/$',views.upgrade_view,name='upgrade'),
|
||||
@@ -671,6 +673,9 @@ urlpatterns = [
|
||||
name='plannedsessions_coach_view'),
|
||||
re_path(r'^sessions/coach/user/(?P<userid>\d+)/$',views.plannedsessions_coach_view,
|
||||
name='plannedsessions_coach_view'),
|
||||
re_path(r'^sessions/coach/sendcalendar/user/(?P<userid>\d+)/$',
|
||||
views.plannedsessions_coach_icsemail_view,
|
||||
name='plannedsessions_coach_icsemail_view'),
|
||||
re_path(r'^sessions/print/?/$',views.plannedsessions_print_view,
|
||||
name='plannedsessions_print_view'),
|
||||
re_path(r'^sessions/(?P<id>\d+)/comments/user/(?P<userid>\d+)/$',views.plannedsession_comment_view,
|
||||
|
||||
@@ -514,10 +514,10 @@ def statsdata(workouts, options):
|
||||
}
|
||||
|
||||
fieldlist,fielddict = dataprep.getstatsfields()
|
||||
|
||||
# prepare data frame
|
||||
datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist)
|
||||
|
||||
|
||||
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
|
||||
|
||||
# Create stats
|
||||
@@ -525,6 +525,7 @@ def statsdata(workouts, options):
|
||||
fielddict.pop('workoutstate')
|
||||
fielddict.pop('workoutid')
|
||||
|
||||
|
||||
for field,verbosename in fielddict.items():
|
||||
thedict = {
|
||||
'mean':datadf[field].mean(),
|
||||
|
||||
@@ -71,6 +71,75 @@ def plannedsessions_icsemail_view(request,userid=0):
|
||||
|
||||
return response
|
||||
|
||||
@login_required()
|
||||
def plannedsessions_coach_icsemail_view(request,userid=0):
|
||||
therower = getrequestplanrower(request,userid=userid)
|
||||
startdate,enddate = get_dates_timeperiod(request)
|
||||
|
||||
if 'coach' in request.user.rower.rowerplan:
|
||||
sps = get_sessions_manager(request.user,teamid=0,
|
||||
enddate=enddate,
|
||||
startdate=startdate)
|
||||
else:
|
||||
rteams = therower.team.filter(viewing='allmembers')
|
||||
sps = get_sessions(therower,startdate=startdate,enddate=enddate)
|
||||
|
||||
if therower.rowerplan != 'freecoach':
|
||||
rowers = [therower]
|
||||
else:
|
||||
rowers = []
|
||||
|
||||
for ps in sps:
|
||||
if 'coach' in request.user.rower.rowerplan:
|
||||
rowers += ps.rower.all().exclude(rowerplan='freecoach')
|
||||
else:
|
||||
rowers += ps.rower.filter(team__in=rteams).exclude(rowerplan='freecoach')
|
||||
|
||||
rowers = list(set(rowers))
|
||||
|
||||
cal = Calendar()
|
||||
cal.add('prodid','rowsandall')
|
||||
cal.add('version','1.0')
|
||||
|
||||
for ps in sps:
|
||||
event = Event()
|
||||
comment = '{d} {u} {c}'.format(
|
||||
d=ps.sessionvalue,
|
||||
u = ps.sessionunit,
|
||||
c = ps.criterium)
|
||||
event.add('summary',ps.name)
|
||||
event.add('dtstart',ps.preferreddate)
|
||||
event.add('dtend',ps.preferreddate)
|
||||
event['uid'] = 'plannedsession_'+str(ps.id)
|
||||
event.add('description',ps.comment)
|
||||
event.add('comment',comment)
|
||||
cal.add_component(event)
|
||||
|
||||
|
||||
icalstring = cal.to_ical()
|
||||
fname = "media/training_plan_{d1}_{d2}".format(
|
||||
d1 = startdate.strftime("%Y%m%d"),
|
||||
d2 = enddate.strftime("%Y%m%d"),
|
||||
)
|
||||
|
||||
url = reverse('plannedsessions_coach_view')+'?when={d1}/{d2}'.format(
|
||||
d1 = startdate.strftime("%Y-%m-%d"),
|
||||
d2 = enddate.strftime("%Y-%m-%d"),
|
||||
)
|
||||
|
||||
for rower in rowers:
|
||||
fname2 = fname+"_{u}.ics".format(u=rower.id)
|
||||
with open(fname2,'wb') as fop:
|
||||
fop.write(icalstring)
|
||||
|
||||
job = myqueue(queue,handle_sendemail_ical,
|
||||
rower.user.first_name,
|
||||
rower.user.last_name,
|
||||
rower.user.email,
|
||||
url,
|
||||
fname2,debug=False)
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@login_required()
|
||||
def course_kmldownload_view(request,id=0):
|
||||
|
||||
@@ -1410,7 +1410,6 @@ def workout_getimportview(request,externalid,source = 'c2'):
|
||||
|
||||
|
||||
if strokedata.empty:
|
||||
print('aap')
|
||||
distance = data['distance']
|
||||
c2id = data['id']
|
||||
workouttype = mytypes.c2mappinginv[data['type']]
|
||||
|
||||
@@ -5,6 +5,15 @@ from __future__ import unicode_literals
|
||||
|
||||
from rowers.views.statements import *
|
||||
|
||||
@login_required()
|
||||
def errormessage_view(request,errormessage='aap'):
|
||||
if (errormessage=='3dsecure'):
|
||||
errormessage = '3D Secure Card Verification Error. Please check your card details.'
|
||||
messages.error(request,errormessage)
|
||||
|
||||
return JSONResponse({
|
||||
"result":1,
|
||||
})
|
||||
|
||||
@login_required()
|
||||
def deactivate_user(request):
|
||||
|
||||
@@ -390,6 +390,7 @@ def upgrade_checkouts_view(request):
|
||||
url = reverse('billing_view')
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
print('noot',form)
|
||||
messages.error(request,"There was an error in the payment form")
|
||||
url = reverse(upgrade_view)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@@ -48,7 +48,7 @@ from django.http import (
|
||||
)
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from rowers.forms import (
|
||||
ForceCurveOptionsForm,HistoForm,
|
||||
ForceCurveOptionsForm,HistoForm,TeamMessageForm,
|
||||
LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm,
|
||||
TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
|
||||
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
|
||||
@@ -183,6 +183,7 @@ from rowers.tasks import (
|
||||
handle_sendemail_userdeleted,
|
||||
handle_sendemail_raceregistration,
|
||||
handle_sendemail_racesubmission,
|
||||
handle_sendemail_ical,
|
||||
)
|
||||
|
||||
from scipy.signal import savgol_filter
|
||||
|
||||
+23
-10
@@ -27,7 +27,7 @@ def team_view(request,id=0,userid=0):
|
||||
|
||||
q = q | q2
|
||||
|
||||
if request.method == 'POST' and request.user == t.manager:
|
||||
if request.method == 'POST' and request.user == t.manager and 'email' in request.POST:
|
||||
inviteform = TeamInviteForm(request.POST)
|
||||
|
||||
inviteform.fields['user'].queryset = q
|
||||
@@ -45,12 +45,24 @@ def team_view(request,id=0,userid=0):
|
||||
else:
|
||||
message = text
|
||||
messages.error(request,message)
|
||||
groupmessageform = TeamMessageForm()
|
||||
elif request.method == 'POST' and request.user == t.manager and 'message' in request.POST:
|
||||
groupmessageform = TeamMessageForm(request.POST)
|
||||
inviteform = TeamInviteForm()
|
||||
if groupmessageform.is_valid():
|
||||
message = groupmessageform.cleaned_data['message']
|
||||
teams.send_team_message(t,message)
|
||||
messages.info(request,'Message was sent to all team members')
|
||||
groupmessageform = TeamMessageForm()
|
||||
|
||||
elif request.user == t.manager:
|
||||
inviteform = TeamInviteForm()
|
||||
inviteform.fields['user'].queryset = q
|
||||
groupmessageform = TeamMessageForm()
|
||||
else:
|
||||
inviteform = ''
|
||||
groupmessageform = ''
|
||||
|
||||
|
||||
members = Rower.objects.filter(team=t).order_by('user__last_name','user__first_name')
|
||||
thisteammyrequests = TeamRequest.objects.filter(team=t,user=request.user)
|
||||
@@ -63,7 +75,7 @@ def team_view(request,id=0,userid=0):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse(rower_teams_view),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse(team_view,kwargs={'id':id}),
|
||||
@@ -78,6 +90,7 @@ def team_view(request,id=0,userid=0):
|
||||
'teams':get_my_teams(request.user),
|
||||
'myteams':myteams,
|
||||
'memberteams':memberteams,
|
||||
'groupmessageform': groupmessageform,
|
||||
'members':members,
|
||||
'breadcrumbs':breadcrumbs,
|
||||
'active':'nav-teams',
|
||||
@@ -98,7 +111,7 @@ def team_leaveconfirm_view(request,id=0):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse(rower_teams_view),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse(team_view,kwargs={'id':id}),
|
||||
@@ -242,7 +255,7 @@ def rower_teams_view(request,message='',successmessage=''):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse(rower_teams_view),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -328,7 +341,7 @@ def athlete_drop_coach_confirm_view(request,id):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse('rower_teams_view'),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse('athlete_drop_coach_confirm_view',kwargs={'id':id}),
|
||||
@@ -355,7 +368,7 @@ def coach_drop_athlete_confirm_view(request,id):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse('rower_teams_view'),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse('coach_drop_athlete_confirm_view',kwargs={'id':id}),
|
||||
@@ -609,7 +622,7 @@ def team_edit_view(request,id=0):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse(rower_teams_view),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse(team_view,kwargs={'id':id}),
|
||||
@@ -675,7 +688,7 @@ def team_create_view(request):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse(rower_teams_view),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse(team_create_view),
|
||||
@@ -708,7 +721,7 @@ def team_deleteconfirm_view(request,id):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse(rower_teams_view),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse(team_view,kwargs={'id':id}),
|
||||
@@ -764,7 +777,7 @@ def team_members_stats_view(request,id):
|
||||
breadcrumbs = [
|
||||
{
|
||||
'url':reverse(rower_teams_view),
|
||||
'name': 'Teams'
|
||||
'name': 'Groups'
|
||||
},
|
||||
{
|
||||
'url':reverse(team_view,kwargs={'id':id}),
|
||||
|
||||
@@ -166,7 +166,9 @@ DATABASES = {
|
||||
},
|
||||
'TEST': {
|
||||
'CHARSET': 'utf8',
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'COLLATION': 'utf8_general_ci',
|
||||
'NAME': CFG['db_name']
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -10,28 +10,40 @@ For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.9/ref/settings/
|
||||
"""
|
||||
from .settings import *
|
||||
import sys
|
||||
|
||||
DATABASES = {
|
||||
try:
|
||||
use_sqlite = CFG['use_sqlite']
|
||||
except KeyError:
|
||||
use_sqlite = False
|
||||
|
||||
if 'test' in sys.argv:
|
||||
TESTING = True
|
||||
|
||||
|
||||
if TESTING or use_sqlite:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
'HOST': 'localhost',
|
||||
},
|
||||
# 'TEST': {
|
||||
# 'CHARSET': 'utf8',
|
||||
# 'COLLATION': 'utf8_general_ci',
|
||||
# },
|
||||
# 'TEST': {
|
||||
# 'CHARSET': 'utf8',
|
||||
# 'COLLATION': 'utf8_general_ci',
|
||||
# },
|
||||
|
||||
# 'slave': {
|
||||
# 'ENGINE': 'django.db.backends.mysql',
|
||||
# 'NAME': 'rowsanda_107501',
|
||||
# 'USER': 'rowsanda_107501',
|
||||
# 'PASSWORD': 'roeidata',
|
||||
# 'HOST': 'store3.rosti.cz',
|
||||
# 'PORT': '3306',
|
||||
# }
|
||||
# 'slave': {
|
||||
# 'ENGINE': 'django.db.backends.mysql',
|
||||
# 'NAME': 'rowsanda_107501',
|
||||
# 'USER': 'rowsanda_107501',
|
||||
# 'PASSWORD': 'roeidata',
|
||||
# 'HOST': 'store3.rosti.cz',
|
||||
# 'PORT': '3306',
|
||||
# }
|
||||
}
|
||||
|
||||
|
||||
BROKER_URL = 'redis://localhost:6379/0'
|
||||
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
|
||||
CELERY_IGNORE_RESULT = False
|
||||
@@ -58,7 +70,7 @@ ALLOWED_HOSTS = ['localhost']
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
|
||||
'LOCATION': 'C:/python/rowsandallapp/django_cache',
|
||||
'LOCATION': os.path.join(BASE_DIR, 'django_cache'),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +78,7 @@ CACHES = {
|
||||
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = 'C:/python/rowsandallapp'
|
||||
#STATIC_ROOT = 'C:/python/rowsandallapp'
|
||||
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from .settings_dev import *
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
'HOST': 'localhost',
|
||||
},
|
||||
}
|
||||
@@ -31,20 +31,20 @@ import django.views.i18n
|
||||
|
||||
urlpatterns = [
|
||||
re_path('^', include('django.contrib.auth.urls')),
|
||||
re_path(r'^password_change_done/$',auth_views.PasswordChangeDoneView,name='password_change_done'),
|
||||
re_path(r'^password_change/$',auth_views.PasswordChangeView,name='password_change'),
|
||||
re_path(r'^password_change_done/$',auth_views.PasswordChangeDoneView.as_view(),name='password_change_done'),
|
||||
re_path(r'^password_change/$',auth_views.PasswordChangeView.as_view(),name='password_change'),
|
||||
re_path(r'^password_reset/$',
|
||||
auth_views.PasswordResetView,
|
||||
auth_views.PasswordResetView.as_view(),
|
||||
{'template_name': 'rowers/templates/registration/password_reset.html'},
|
||||
name='password_reset'),
|
||||
re_path(r'^password_reset/done/$',
|
||||
auth_views.PasswordResetDoneView,
|
||||
auth_views.PasswordResetDoneView.as_view(),
|
||||
name='password_reset_done'),
|
||||
re_path(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
|
||||
auth_views.PasswordResetConfirmView,
|
||||
auth_views.PasswordResetConfirmView.as_view(),
|
||||
name='password_reset_confirm'),
|
||||
re_path(r'^reset/done/$',
|
||||
auth_views.PasswordResetCompleteView,
|
||||
auth_views.PasswordResetCompleteView.as_view(),
|
||||
name='password_reset_complete'),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user