Private
Public Access
1
0

Merge branch 'release/v10.05'

This commit is contained in:
Sander Roosendaal
2019-07-31 20:22:16 +02:00
41 changed files with 63723 additions and 63309 deletions
+14 -11
View File
@@ -8,13 +8,13 @@ atomicwrites==1.3.0
attrs==19.1.0 attrs==19.1.0
backcall==0.1.0 backcall==0.1.0
beautifulsoup4==4.7.1 beautifulsoup4==4.7.1
billiard==3.5.0.5 billiard==3.6.0.0
bleach==3.1.0 bleach==3.1.0
bokeh==1.0.4 bokeh==1.0.4
boto==2.49.0 boto==2.49.0
braintree==3.51.0 braintree==3.55.0
cairocffi==1.0.2 cairocffi==1.0.2
celery==4.2.2 celery==4.3.0
certifi==2019.3.9 certifi==2019.3.9
cffi==1.12.2 cffi==1.12.2
chardet==3.0.4 chardet==3.0.4
@@ -30,7 +30,7 @@ cycler==0.10.0
dask==1.1.4 dask==1.1.4
decorator==4.4.0 decorator==4.4.0
defusedxml==0.5.0 defusedxml==0.5.0
Django==2.2.1 Django==2.1.7
django-analytical==2.5.0 django-analytical==2.5.0
django-async-messages==0.3.1 django-async-messages==0.3.1
django-braces==1.13.0 django-braces==1.13.0
@@ -46,9 +46,10 @@ django-leaflet==0.24.0
django-mailbox==4.7.1 django-mailbox==4.7.1
django-oauth-toolkit==1.2.0 django-oauth-toolkit==1.2.0
django-oauth2-provider==0.2.6.1 django-oauth2-provider==0.2.6.1
django-redis==4.10.0
django-rest-framework==0.1.0 django-rest-framework==0.1.0
django-rest-swagger==2.2.0 django-rest-swagger==2.2.0
django-rq==2.0 django-rq==1.3.1
django-rq-dashboard==0.3.3 django-rq-dashboard==0.3.3
django-ses==0.8.10 django-ses==0.8.10
django-shell-plus==1.1.7 django-shell-plus==1.1.7
@@ -66,7 +67,9 @@ Faker==1.0.4
fitparse==1.1.0 fitparse==1.1.0
Flask==1.0.2 Flask==1.0.2
future==0.17.1 future==0.17.1
#GDAL==2.3.3
geocoder==1.38.1 geocoder==1.38.1
geos==0.2.1
holoviews==1.11.3 holoviews==1.11.3
html5lib==1.0.1 html5lib==1.0.1
htmlmin==0.1.12 htmlmin==0.1.12
@@ -96,7 +99,7 @@ jupyterlab==0.35.4
jupyterlab-server==0.3.0 jupyterlab-server==0.3.0
keyring==18.0.0 keyring==18.0.0
kiwisolver==1.0.1 kiwisolver==1.0.1
kombu==4.3.0 kombu==4.5.0
lxml==4.3.2 lxml==4.3.2
Markdown==3.0.1 Markdown==3.0.1
MarkupSafe==1.1.1 MarkupSafe==1.1.1
@@ -129,9 +132,9 @@ pip-upgrader==1.4.6
pluggy==0.9.0 pluggy==0.9.0
prometheus-client==0.6.0 prometheus-client==0.6.0
prompt-toolkit==2.0.9 prompt-toolkit==2.0.9
psycopg2==2.8.1
ptyprocess==0.6.0 ptyprocess==0.6.0
py==1.8.0 py==1.8.0
pycairo==1.18.0
pycparser==2.19 pycparser==2.19
Pygments==2.3.1 Pygments==2.3.1
pyparsing==2.3.1 pyparsing==2.3.1
@@ -157,10 +160,9 @@ ratelim==0.1.6
redis==3.2.1 redis==3.2.1
requests==2.21.0 requests==2.21.0
requests-oauthlib==1.2.0 requests-oauthlib==1.2.0
rowingdata==2.4.9 rowingdata==2.4.4
rowingphysics==0.5.0 rowingphysics==0.5.0
rq==1.0 rq==0.13.0
rq-dashboard==0.4.0
scipy==1.2.1 scipy==1.2.1
SecretStorage==3.1.1 SecretStorage==3.1.1
Send2Trash==1.5.0 Send2Trash==1.5.0
@@ -188,8 +190,9 @@ VerbalExpressions==0.0.2
vine==1.3.0 vine==1.3.0
wcwidth==0.1.7 wcwidth==0.1.7
webencodings==0.5.1 webencodings==0.5.1
Werkzeug==0.15.2 Werkzeug==0.15.1
widgetsnbextension==3.4.2 widgetsnbextension==3.4.2
#winkerberos==0.7.0
xlrd==1.2.0 xlrd==1.2.0
xmltodict==0.12.0 xmltodict==0.12.0
yamjam==0.1.7 yamjam==0.1.7
+13
View File
@@ -101,6 +101,11 @@ def get_plans_costs():
def make_payment(rower,data): def make_payment(rower,data):
nonce_from_the_client = data['payment_method_nonce'] 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 = data['amount']
amount = '{amount:.f2}'.format(amount=amount) amount = '{amount:.f2}'.format(amount=amount)
@@ -131,6 +136,10 @@ def update_subscription(rower,data,method='up'):
planid = data['plan'] planid = data['plan']
plan = PaidPlan.objects.get(id=planid) plan = PaidPlan.objects.get(id=planid)
nonce_from_the_client = data['payment_method_nonce'] 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 = data['amount']
amount = '{amount:.2f}'.format(amount=amount) amount = '{amount:.2f}'.format(amount=amount)
@@ -229,6 +238,10 @@ def update_subscription(rower,data,method='up'):
def create_subscription(rower,data): def create_subscription(rower,data):
nonce_from_the_client = data['payment_method_nonce'] 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 = data['amount']
planid = data['plan'] planid = data['plan']
+4
View File
@@ -2208,6 +2208,9 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
else: else:
drivenergy = drivelength * averageforce drivenergy = drivelength * averageforce
powerhr = 60.*power/hr
powerhr = powerhr.fillna(value=0)
if driveenergy.mean() == 0 and driveenergy.std() == 0: if driveenergy.mean() == 0 and driveenergy.std() == 0:
driveenergy = 0*driveenergy+100 driveenergy = 0*driveenergy+100
@@ -2237,6 +2240,7 @@ def dataprep(rowdatadf, id=0, bands=True, barchart=True, otwpower=True,
drivespeed=drivespeed, drivespeed=drivespeed,
rhythm=rhythm, rhythm=rhythm,
distanceperstroke=distanceperstroke, distanceperstroke=distanceperstroke,
powerhr=powerhr,
) )
) )
+7
View File
@@ -1207,6 +1207,13 @@ class WorkoutRaceSelectForm(forms.Form):
# self.fields['evaluate_after'] = # 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 # Form to select team by rower
class RowerTeamForm(forms.Form): class RowerTeamForm(forms.Form):
team = forms.ModelChoiceField( team = forms.ModelChoiceField(
+22 -6
View File
@@ -34,8 +34,6 @@ from rowers.models import User,VirtualRace,Workout
from rowers.plannedsessions import email_submit_race 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! # If you find a solution that does not need the two paths, please comment!
sys.path.append('$path_to_root_of_project$') sys.path.append('$path_to_root_of_project$')
@@ -189,9 +187,31 @@ class Command(BaseCommand):
default=False, default=False,
help="Run in testing mode, don't send emails", 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 """ """Run the Email processing command """
def handle(self, *args, **options): 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
polar_available = polarstuff.get_polar_notifications() polar_available = polarstuff.get_polar_notifications()
res = polarstuff.get_all_new_workouts(polar_available) res = polarstuff.get_all_new_workouts(polar_available)
@@ -207,10 +227,6 @@ class Command(BaseCommand):
attachments = MessageAttachment.objects.filter( attachments = MessageAttachment.objects.filter(
message_id__in=message_ids message_id__in=message_ids
) )
if 'testing' in options:
testing = options['testing']
else:
testing = False
cntr = 0 cntr = 0
for attachment in attachments: for attachment in attachments:
filename, extension = os.path.splitext(attachment.document.name) filename, extension = os.path.splitext(attachment.document.name)
+9
View File
@@ -86,6 +86,15 @@ rowingmetrics = (
'mode':'both', 'mode':'both',
'type':'pro'}), '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',{ ('spm',{
'numtype':'float', 'numtype':'float',
'null':True, 'null':True,
+8 -7
View File
@@ -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 import datetime
from django.conf import settings from django.conf import settings
@@ -122,9 +122,9 @@ class Migration(migrations.Migration):
name='FavoriteChart', name='FavoriteChart',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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')), ('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)'), ('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')), ('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)'), ('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')), ('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')), ('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')), ('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')), ('reststrokes', models.BooleanField(default=True, verbose_name='Incl. Rest')),
@@ -192,7 +192,7 @@ class Migration(migrations.Migration):
name='PaidPlan', name='PaidPlan',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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)), ('name', models.CharField(max_length=200)),
('external_id', models.CharField(blank=True, default=None, max_length=200, null=True)), ('external_id', models.CharField(blank=True, default=None, max_length=200, null=True)),
('price', models.FloatField(blank=True, default=None, 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)), ('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)), ('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)), ('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')), ('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)), ('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)), ('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)')), ('hr', models.IntegerField(null=True, verbose_name='Heart Rate (bpm)')),
('pace', models.FloatField(null=True, verbose_name='Pace (/500m)')), ('pace', models.FloatField(null=True, verbose_name='Pace (/500m)')),
('velo', models.FloatField(default=0, null=True, verbose_name='Boat Speed (m/s)')), ('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)')), ('spm', models.FloatField(null=True, verbose_name='Stroke Rate (spm)')),
('driveenergy', models.FloatField(null=True, verbose_name='Work Per Stroke (J)')), ('driveenergy', models.FloatField(null=True, verbose_name='Work Per Stroke (J)')),
('power', models.FloatField(null=True, verbose_name='Power (W)')), ('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')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=150, null=True)), ('name', models.CharField(blank=True, max_length=150, null=True)),
('date', models.DateField()), ('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)), ('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')), ('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')), ('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
View File
@@ -272,7 +272,7 @@ class PowerTimeFitnessMetric(models.Model):
PowerTwoK = models.FloatField(default=0) PowerTwoK = models.FloatField(default=0)
PowerOneHour = models.FloatField(default=0) PowerOneHour = models.FloatField(default=0)
workoutmode = models.CharField(default='rower',choices=modechoices, workoutmode = models.CharField(default='rower',choices=modechoices,
max_length=40) max_length=41,)
class Meta: class Meta:
db_table = 'powertimefitnessmetric' db_table = 'powertimefitnessmetric'
@@ -861,7 +861,7 @@ class Rower(models.Model):
privacy = models.CharField(default='visible',max_length=30, privacy = models.CharField(default='visible',max_length=30,
choices=privacychoices) choices=privacychoices)
team = models.ManyToManyField(Team,blank=True) team = models.ManyToManyField(Team,blank=True,related_name='rower')
# Export and Time Zone Settings # Export and Time Zone Settings
defaulttimezone = models.CharField(default='UTC',max_length=100, defaulttimezone = models.CharField(default='UTC',max_length=100,
@@ -2908,9 +2908,10 @@ class Meta:
index_together = ['workoutid'] index_together = ['workoutid']
app_label = 'rowers' app_label = 'rowers'
attrs = {'__module__': '', 'Meta': Meta} attrs = {'__module__': 'rowers.models', 'Meta': Meta}
attrs.update(strokedatafields) attrs.update(strokedatafields)
# Model of StrokeData table # Model of StrokeData table
# the definition here is used only to enable easy Django migration # the definition here is used only to enable easy Django migration
# when the StrokeData are expanded. # when the StrokeData are expanded.
+1 -1
View File
@@ -45,7 +45,7 @@ stravamapping = collections.OrderedDict({
'water':'Rowing', 'water':'Rowing',
'rower':'Rowing', 'rower':'Rowing',
'skierg':'NordicSki', 'skierg':'NordicSki',
'bike':'Ride', 'Bike':'Ride',
'bikeerg':'Ride', 'bikeerg':'Ride',
'dynamic':'Rowing', 'dynamic':'Rowing',
'slides':'Rowing', 'slides':'Rowing',
+57
View File
@@ -1321,6 +1321,44 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
return 1 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 @app.task
def handle_sendemailfile(first_name, last_name, email, csvfile,**kwargs): def handle_sendemailfile(first_name, last_name, email, csvfile,**kwargs):
@@ -1982,6 +2020,25 @@ def handle_sendemailnewcomment(first_name,
return 1 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 @app.task
def handle_sendemail_request(email, name, code, teamname, requestor, id, def handle_sendemail_request(email, name, code, teamname, requestor, id,
+24 -3
View File
@@ -37,6 +37,7 @@ from rowers.tasks import (
handle_sendemail_coachoffer_accepted, handle_sendemail_coachoffer_accepted,
handle_sendemail_coachrequest_rejected, handle_sendemail_coachrequest_rejected,
handle_sendemail_coachrequest_accepted, handle_sendemail_coachrequest_accepted,
handle_sendemail_message,
) )
from rowers.models import ValidationError from rowers.models import ValidationError
@@ -170,11 +171,21 @@ def remove_coach(coach,rower):
return (1,'Coach removed') 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): def rower_get_coaches(rower):
coaches = [] coaches = []
for group in rower.coachinggroups.all(): for group in rower.coachinggroups.all():
coach = Rower.objects.get(mycoachgroup=group) try:
coaches.append(coach) coach = Rower.objects.get(mycoachgroup=group)
coaches.append(coach)
except Rower.DoesNotExist:
pass
return coaches return coaches
@@ -567,7 +578,17 @@ def send_invite_accept_email(invitation):
return (1,'Invitation email sent') 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): def send_request_email(rekwest):
email = rekwest.team.manager.email email = rekwest.team.manager.email
@@ -606,7 +627,7 @@ def process_request_code(manager,code):
rekwest.delete() rekwest.delete()
return result return (result,'The member was added')
def process_invite_code(user,code): def process_invite_code(user,code):
code = code.upper() code = code.upper()
+39 -5
View File
@@ -1,27 +1,61 @@
<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> <script>
var form = document.querySelector('#payment-form'); 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 }}'; var client_token = '{{ client_token }}';
braintree.dropin.create({ braintree.dropin.create({
authorization: client_token, authorization: client_token,
container: '#bt-dropin', container: '#bt-dropin',
paypal: { paypal: {
flow: 'vault' 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) { }, function (createErr, instance) {
form.addEventListener('submit', function (event) { form.addEventListener('submit', function (event) {
event.preventDefault(); event.preventDefault();
instance.requestPaymentMethod(function (err, payload) { instance.requestPaymentMethod(function (err, payload) {
if (err) { if (err) {
console.log('Error', err); console.log('Error', err);
return; return;
} }
// Add the nonce to the form and submit if (payload.liabilityShifted || payload.type !== 'CreditCard') {
document.querySelector('#nonce').value = payload.nonce; document.querySelector('#nonce').value = payload.nonce;
hiddenNonceInput.value = payload.nonce;
form.submit(); 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> </script>
<script type='text/javascript' <script type='text/javascript'
src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'> src='https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js'>
@@ -30,5 +64,5 @@
$('#payment-form').submit(function() { $('#payment-form').submit(function() {
$(this).find("button[type='submit']").prop('disabled',true); $(this).find("button[type='submit']").prop('disabled',true);
console.log("disabled"); console.log("disabled");
} });
</script> </script>
+1 -1
View File
@@ -142,7 +142,7 @@
<h2>Teams</h2> <h2>Teams</h2>
<p> <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 functionality related to interaction with your team, if you
are part of one. are part of one.
</p> </p>
+5 -1
View File
@@ -4,10 +4,14 @@
<p>Dear <strong>{{ first_name }}</strong>,</p> <p>Dear <strong>{{ first_name }}</strong>,</p>
<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. this file to your calendar app.
</p> </p>
<p>
You can see the training plan here: <a href="{{ siteurl }}{{ url }}">{{ siteurl }}{{ url }}</a>
</p>
<p> <p>
Best Regards, the Rowsandall Team Best Regards, the Rowsandall Team
</p> </p>
+2 -2
View File
@@ -220,8 +220,8 @@
<a href="/rowers/starttrial/">Free PRO trial</a> <a href="/rowers/starttrial/">Free PRO trial</a>
</button> </button>
{% else %} {% else %}
&nbsp; &nbsp;
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if user.is_anonymous %} {% if user.is_anonymous %}
+5
View File
@@ -1,8 +1,13 @@
{% extends "newbase.html" %} {% extends "newbase.html" %}
{% block title %}Rowsandall Paid Membership{% endblock title %} {% block title %}Rowsandall Paid Membership{% endblock title %}
{% load rowerfilters %} {% load rowerfilters %}
{% block main %} {% block main %}
<div id="paymenterror">
</div>
<h1>Confirm Your Payment</h1> <h1>Confirm Your Payment</h1>
<h2>Order Overview</h2> <h2>Order Overview</h2>
+2 -2
View File
@@ -79,10 +79,10 @@
<td> {{ ps.sessionvalue }} </td> <td> {{ ps.sessionvalue }} </td>
<td> {{ ps.sessionunit }} </td> <td> {{ ps.sessionunit }} </td>
<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>
<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>
<td> <td>
+2 -2
View File
@@ -96,11 +96,11 @@
<td> {{ ps.sessionvalue }} </td> <td> {{ ps.sessionvalue }} </td>
<td> {{ ps.sessionunit }} </td> <td> {{ ps.sessionunit }} </td>
<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>
<td> <td>
<a class="small" <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>
<td> <td>
<a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm">Delete</a> <a class="small" href="/rowers/sessions/{{ ps.id }}/deleteconfirm">Delete</a>
+13 -2
View File
@@ -6,9 +6,9 @@
{% block main %} {% block main %}
{% if theteam %} {% if theteam %}
<h1>Coach Overview. Team {{ theteam.name }}</h1> <h1>Group Overview. Team {{ theteam.name }}</h1>
{% else %} {% else %}
<h1>Coach Overview</h1> <h1>Group Overview</h1>
{% endif %} {% endif %}
<ul class="main-content"> <ul class="main-content">
@@ -83,6 +83,17 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </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> </li>
{% if unmatchedworkouts %} {% if unmatchedworkouts %}
+13
View File
@@ -72,6 +72,19 @@
</p> </p>
{% endif %} {% endif %}
</li> </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> </ul>
+14
View File
@@ -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 %}
+4 -1
View File
@@ -3,6 +3,9 @@
{% load rowerfilters %} {% load rowerfilters %}
{% block main %} {% block main %}
<div id="paymenterror">
</div>
<h1>Confirm Your Payment</h1> <h1>Confirm Your Payment</h1>
<h2>Order Overview</h2> <h2>Order Overview</h2>
@@ -90,7 +93,7 @@
<div id="bt-dropin"></div> <div id="bt-dropin"></div>
</div> </div>
</section> </section>
<div id="paymenterror2"> </div>
<input type="hidden" id="nonce" name="payment_method_nonce" /> <input type="hidden" id="nonce" name="payment_method_nonce" />
<input type="hidden" id="plan" name="plan" value="{{ plan.id }}"> <input type="hidden" id="plan" name="plan" value="{{ plan.id }}">
<p> <p>
+20
View File
@@ -66,6 +66,17 @@ def strfdelta(tdelta):
return res 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): def strfdeltah(tdelta):
hours, rest = divmod(tdelta.seconds,3600) hours, rest = divmod(tdelta.seconds,3600)
minutes,seconds = divmod(rest,60) minutes,seconds = divmod(rest,60)
@@ -484,8 +495,17 @@ def future_date_only(the_date):
def is_future_date(the_date): def is_future_date(the_date):
return the_date >= date.today() return the_date >= date.today()
@register.filter
def amount(value):
vs = '{v}.00'.format(v=int(value))
return vs
@register.filter @register.filter
def date_dif(the_date): def date_dif(the_date):
if the_date == date(1970,1,1):
return 1
if the_date: if the_date:
return the_date - date.today() return the_date - date.today()
else: else:
+4 -8
View File
@@ -173,10 +173,6 @@ def mocked_getsmallrowdata_db(*args, **kwargs):
return df 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): def mocked_getsmallrowdata_db_water(*args, **kwargs):
df = pd.read_csv('rowers/tests/testdata/colsfromdb.csv') 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 return df, extracols
def mocked_read_df_cols_sql(ids, columns, convertnewtons=True): def mocked_read_cols_df_sql(*args, **kwargs):
df = pd.read_csv('rowers/tests/testdata/fake_strokedata.csv') df = pd.read_csv('rowers/tests/testdata/readcols.csv')
extracols = []
return df,[]
return df, extracols
def mock_workout_summaries(*args, **kwargs): def mock_workout_summaries(*args, **kwargs):
df = pd.read_csv('rowers/tests/testdata/workout_summaries.csv') df = pd.read_csv('rowers/tests/testdata/workout_summaries.csv')
+76 -15
View File
@@ -24,7 +24,7 @@ class EmailUpload(TestCase):
rowerplan='coach') rowerplan='coach')
nu = datetime.datetime.now() nu = datetime.datetime.now()
workoutsbox = Mailbox.objects.create(name='workouts') workoutsbox = Mailbox.objects.create(name='workouts1')
workoutsbox.save() workoutsbox.save()
failbox = Mailbox.objects.create(name='Failed') failbox = Mailbox.objects.create(name='Failed')
failbox.save() failbox.save()
@@ -57,7 +57,7 @@ workout run
def test_emailupload( def test_emailupload(
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
out = StringIO() 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()) self.assertIn('Successfully processed email attachments',out.getvalue())
ws = Workout.objects.filter(name="run") ws = Workout.objects.filter(name="run")
@@ -86,7 +86,7 @@ class EmailBikeErgUpload(TestCase):
rowerplan='coach') rowerplan='coach')
nu = datetime.datetime.now() nu = datetime.datetime.now()
workoutsbox = Mailbox.objects.create(name='workouts') workoutsbox = Mailbox.objects.create(name='workouts2')
workoutsbox.save() workoutsbox.save()
failbox = Mailbox.objects.create(name='Failed') failbox = Mailbox.objects.create(name='Failed')
failbox.save() failbox.save()
@@ -104,13 +104,13 @@ workout bikeerg
a.save() a.save()
def tearDown(self): def tearDown(self):
for filename in os.listdir('media/mailbox_attachments'): filename = 'colin3.csv'
path = os.path.join('media/mailbox_attachments/',filename) path = os.path.join('media/mailbox_attachments/',filename)
if not os.path.isdir(path): if not os.path.isdir(path):
try: try:
os.remove(path) os.remove(path)
except (IOError,FileNotFoundError,OSError): except (IOError,FileNotFoundError,OSError):
pass pass
@patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.create_engine')
@patch('rowers.polarstuff.get_polar_notifications') @patch('rowers.polarstuff.get_polar_notifications')
@@ -119,7 +119,7 @@ workout bikeerg
def test_emailupload( def test_emailupload(
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
out = StringIO() 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()) self.assertIn('Successfully processed email attachments',out.getvalue())
ws = Workout.objects.filter(name="bikeerg") ws = Workout.objects.filter(name="bikeerg")
@@ -128,6 +128,67 @@ workout bikeerg
w = ws[0] w = ws[0]
self.assertEqual(w.workouttype,'bikeerg') 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 #@pytest.mark.django_db
@@ -150,7 +211,7 @@ class EmailTests(TestCase):
rowerplan='coach') rowerplan='coach')
nu = datetime.datetime.now() nu = datetime.datetime.now()
workoutsbox = Mailbox.objects.create(name='workouts') workoutsbox = Mailbox.objects.create(name='workouts4')
workoutsbox.save() workoutsbox.save()
failbox = Mailbox.objects.create(name='Failed') failbox = Mailbox.objects.create(name='Failed')
failbox.save() failbox.save()
@@ -209,7 +270,7 @@ race 1
def test_emailprocessing( def test_emailprocessing(
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
out = StringIO() 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()) self.assertIn('Successfully processed email attachments',out.getvalue())
@@ -236,7 +297,7 @@ class EmailAdminUpload(TestCase):
self.race = RaceFactory(manager = self.theadmin) self.race = RaceFactory(manager = self.theadmin)
nu = datetime.datetime.now() nu = datetime.datetime.now()
workoutsbox = Mailbox.objects.create(name='workouts') workoutsbox = Mailbox.objects.create(name='workouts5')
workoutsbox.save() workoutsbox.save()
failbox = Mailbox.objects.create(name='Failed') failbox = Mailbox.objects.create(name='Failed')
failbox.save() failbox.save()
@@ -270,7 +331,7 @@ race 1
def test_email_admin_upload( def test_email_admin_upload(
self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post): self, mocked_sqlalchemy,mocked_polar_notifications, mock_get, mock_post):
out = StringIO() 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()) self.assertIn('Successfully processed email attachments',out.getvalue())
ws = Workout.objects.filter(name="johnsworkout") ws = Workout.objects.filter(name="johnsworkout")
+40507 -40507
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+21921 -21921
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
+5
View File
@@ -511,11 +511,13 @@ urlpatterns = [
# re_path(r'^videos', TemplateView.as_view(template_name='videos.html'),name='videos'), # 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'^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'^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'^analysis/$', views.analysis_view,name='analysis'),
re_path(r'^laboratory/$', views.laboratory_view,name='laboratory'), 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'^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'^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'^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'^downgradecheckout/(?P<planid>\d+)/$',views.downgrade_confirm_view,name='downgrade_confirm_view'),
re_path(r'^billing/$',views.billing_view,name='billing'), re_path(r'^billing/$',views.billing_view,name='billing'),
re_path(r'^upgrade/$',views.upgrade_view,name='upgrade'), re_path(r'^upgrade/$',views.upgrade_view,name='upgrade'),
@@ -671,6 +673,9 @@ urlpatterns = [
name='plannedsessions_coach_view'), name='plannedsessions_coach_view'),
re_path(r'^sessions/coach/user/(?P<userid>\d+)/$',views.plannedsessions_coach_view, re_path(r'^sessions/coach/user/(?P<userid>\d+)/$',views.plannedsessions_coach_view,
name='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, re_path(r'^sessions/print/?/$',views.plannedsessions_print_view,
name='plannedsessions_print_view'), name='plannedsessions_print_view'),
re_path(r'^sessions/(?P<id>\d+)/comments/user/(?P<userid>\d+)/$',views.plannedsession_comment_view, re_path(r'^sessions/(?P<id>\d+)/comments/user/(?P<userid>\d+)/$',views.plannedsession_comment_view,
+2 -1
View File
@@ -514,10 +514,10 @@ def statsdata(workouts, options):
} }
fieldlist,fielddict = dataprep.getstatsfields() fieldlist,fielddict = dataprep.getstatsfields()
# prepare data frame # prepare data frame
datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist) datadf,extracols = dataprep.read_cols_df_sql(ids,fieldlist)
datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly) datadf = dataprep.clean_df_stats(datadf,workstrokesonly=workstrokesonly)
# Create stats # Create stats
@@ -525,6 +525,7 @@ def statsdata(workouts, options):
fielddict.pop('workoutstate') fielddict.pop('workoutstate')
fielddict.pop('workoutid') fielddict.pop('workoutid')
for field,verbosename in fielddict.items(): for field,verbosename in fielddict.items():
thedict = { thedict = {
'mean':datadf[field].mean(), 'mean':datadf[field].mean(),
+69
View File
@@ -71,6 +71,75 @@ def plannedsessions_icsemail_view(request,userid=0):
return response 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() @login_required()
def course_kmldownload_view(request,id=0): def course_kmldownload_view(request,id=0):
-1
View File
@@ -1410,7 +1410,6 @@ def workout_getimportview(request,externalid,source = 'c2'):
if strokedata.empty: if strokedata.empty:
print('aap')
distance = data['distance'] distance = data['distance']
c2id = data['id'] c2id = data['id']
workouttype = mytypes.c2mappinginv[data['type']] workouttype = mytypes.c2mappinginv[data['type']]
+9
View File
@@ -5,6 +5,15 @@ from __future__ import unicode_literals
from rowers.views.statements import * 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() @login_required()
def deactivate_user(request): def deactivate_user(request):
+1
View File
@@ -390,6 +390,7 @@ def upgrade_checkouts_view(request):
url = reverse('billing_view') url = reverse('billing_view')
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
print('noot',form)
messages.error(request,"There was an error in the payment form") messages.error(request,"There was an error in the payment form")
url = reverse(upgrade_view) url = reverse(upgrade_view)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
+2 -1
View File
@@ -48,7 +48,7 @@ from django.http import (
) )
from django.contrib.auth import authenticate, login, logout from django.contrib.auth import authenticate, login, logout
from rowers.forms import ( from rowers.forms import (
ForceCurveOptionsForm,HistoForm, ForceCurveOptionsForm,HistoForm,TeamMessageForm,
LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm, LoginForm,DocumentsForm,UploadOptionsForm,ImageForm,CourseForm,
TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm, TeamUploadOptionsForm,WorkFlowLeftPanelForm,WorkFlowMiddlePanelForm,
WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement, WorkFlowLeftPanelElement,WorkFlowMiddlePanelElement,
@@ -183,6 +183,7 @@ from rowers.tasks import (
handle_sendemail_userdeleted, handle_sendemail_userdeleted,
handle_sendemail_raceregistration, handle_sendemail_raceregistration,
handle_sendemail_racesubmission, handle_sendemail_racesubmission,
handle_sendemail_ical,
) )
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
+23 -10
View File
@@ -27,7 +27,7 @@ def team_view(request,id=0,userid=0):
q = q | q2 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 = TeamInviteForm(request.POST)
inviteform.fields['user'].queryset = q inviteform.fields['user'].queryset = q
@@ -45,12 +45,24 @@ def team_view(request,id=0,userid=0):
else: else:
message = text message = text
messages.error(request,message) 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: elif request.user == t.manager:
inviteform = TeamInviteForm() inviteform = TeamInviteForm()
inviteform.fields['user'].queryset = q inviteform.fields['user'].queryset = q
groupmessageform = TeamMessageForm()
else: else:
inviteform = '' inviteform = ''
groupmessageform = ''
members = Rower.objects.filter(team=t).order_by('user__last_name','user__first_name') members = Rower.objects.filter(team=t).order_by('user__last_name','user__first_name')
thisteammyrequests = TeamRequest.objects.filter(team=t,user=request.user) thisteammyrequests = TeamRequest.objects.filter(team=t,user=request.user)
@@ -63,7 +75,7 @@ def team_view(request,id=0,userid=0):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(rower_teams_view), 'url':reverse(rower_teams_view),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), '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), 'teams':get_my_teams(request.user),
'myteams':myteams, 'myteams':myteams,
'memberteams':memberteams, 'memberteams':memberteams,
'groupmessageform': groupmessageform,
'members':members, 'members':members,
'breadcrumbs':breadcrumbs, 'breadcrumbs':breadcrumbs,
'active':'nav-teams', 'active':'nav-teams',
@@ -98,7 +111,7 @@ def team_leaveconfirm_view(request,id=0):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(rower_teams_view), 'url':reverse(rower_teams_view),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'id':id}),
@@ -242,7 +255,7 @@ def rower_teams_view(request,message='',successmessage=''):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(rower_teams_view), 'url':reverse(rower_teams_view),
'name': 'Teams' 'name': 'Groups'
} }
] ]
@@ -328,7 +341,7 @@ def athlete_drop_coach_confirm_view(request,id):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse('rower_teams_view'), 'url':reverse('rower_teams_view'),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse('athlete_drop_coach_confirm_view',kwargs={'id':id}), 'url':reverse('athlete_drop_coach_confirm_view',kwargs={'id':id}),
@@ -355,7 +368,7 @@ def coach_drop_athlete_confirm_view(request,id):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse('rower_teams_view'), 'url':reverse('rower_teams_view'),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse('coach_drop_athlete_confirm_view',kwargs={'id':id}), 'url':reverse('coach_drop_athlete_confirm_view',kwargs={'id':id}),
@@ -609,7 +622,7 @@ def team_edit_view(request,id=0):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(rower_teams_view), 'url':reverse(rower_teams_view),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'id':id}),
@@ -675,7 +688,7 @@ def team_create_view(request):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(rower_teams_view), 'url':reverse(rower_teams_view),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse(team_create_view), 'url':reverse(team_create_view),
@@ -708,7 +721,7 @@ def team_deleteconfirm_view(request,id):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(rower_teams_view), 'url':reverse(rower_teams_view),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'id':id}),
@@ -764,7 +777,7 @@ def team_members_stats_view(request,id):
breadcrumbs = [ breadcrumbs = [
{ {
'url':reverse(rower_teams_view), 'url':reverse(rower_teams_view),
'name': 'Teams' 'name': 'Groups'
}, },
{ {
'url':reverse(team_view,kwargs={'id':id}), 'url':reverse(team_view,kwargs={'id':id}),
+2
View File
@@ -166,7 +166,9 @@ DATABASES = {
}, },
'TEST': { 'TEST': {
'CHARSET': 'utf8', 'CHARSET': 'utf8',
'ENGINE': 'django.db.backends.mysql',
'COLLATION': 'utf8_general_ci', 'COLLATION': 'utf8_general_ci',
'NAME': CFG['db_name']
}, },
} }
+32 -20
View File
@@ -10,28 +10,40 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/ https://docs.djangoproject.com/en/1.9/ref/settings/
""" """
from .settings import * from .settings import *
import sys
DATABASES = { try:
'default': { use_sqlite = CFG['use_sqlite']
'ENGINE': 'django.db.backends.sqlite3', except KeyError:
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), use_sqlite = False
'HOST': 'localhost',
},
# 'TEST': {
# 'CHARSET': 'utf8',
# 'COLLATION': 'utf8_general_ci',
# },
# 'slave': { if 'test' in sys.argv:
# 'ENGINE': 'django.db.backends.mysql', TESTING = True
# 'NAME': 'rowsanda_107501',
# 'USER': 'rowsanda_107501',
# 'PASSWORD': 'roeidata', if TESTING or use_sqlite:
# 'HOST': 'store3.rosti.cz', DATABASES = {
# 'PORT': '3306', 'default': {
# } 'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'HOST': 'localhost',
},
# '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',
# }
} }
BROKER_URL = 'redis://localhost:6379/0' BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_IGNORE_RESULT = False CELERY_IGNORE_RESULT = False
@@ -58,7 +70,7 @@ ALLOWED_HOSTS = ['localhost']
CACHES = { CACHES = {
'default': { 'default': {
'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', '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_URL = '/static/'
STATIC_ROOT = 'C:/python/rowsandallapp' #STATIC_ROOT = 'C:/python/rowsandallapp'
INTERNAL_IPS = ['127.0.0.1'] INTERNAL_IPS = ['127.0.0.1']
+9
View File
@@ -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',
},
}
+6 -6
View File
@@ -31,20 +31,20 @@ import django.views.i18n
urlpatterns = [ urlpatterns = [
re_path('^', include('django.contrib.auth.urls')), 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_done/$',auth_views.PasswordChangeDoneView.as_view(),name='password_change_done'),
re_path(r'^password_change/$',auth_views.PasswordChangeView,name='password_change'), re_path(r'^password_change/$',auth_views.PasswordChangeView.as_view(),name='password_change'),
re_path(r'^password_reset/$', re_path(r'^password_reset/$',
auth_views.PasswordResetView, auth_views.PasswordResetView.as_view(),
{'template_name': 'rowers/templates/registration/password_reset.html'}, {'template_name': 'rowers/templates/registration/password_reset.html'},
name='password_reset'), name='password_reset'),
re_path(r'^password_reset/done/$', re_path(r'^password_reset/done/$',
auth_views.PasswordResetDoneView, auth_views.PasswordResetDoneView.as_view(),
name='password_reset_done'), name='password_reset_done'),
re_path(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$', re_path(r'^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$',
auth_views.PasswordResetConfirmView, auth_views.PasswordResetConfirmView.as_view(),
name='password_reset_confirm'), name='password_reset_confirm'),
re_path(r'^reset/done/$', re_path(r'^reset/done/$',
auth_views.PasswordResetCompleteView, auth_views.PasswordResetCompleteView.as_view(),
name='password_reset_complete'), name='password_reset_complete'),
] ]