diff --git a/requirements.txt b/requirements.txt index d7f1e8b0..4b13966d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 cairocffi==1.0.2 -celery==4.2.2 +celery==4.3.0 certifi==2019.3.9 cffi==1.12.2 chardet==3.0.4 @@ -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.3.1 +rowingdata==2.3.8 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,10 @@ 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 yamllint==1.15.0 diff --git a/rowers/braintreestuff.py b/rowers/braintreestuff.py index c7238887..184cc58e 100644 --- a/rowers/braintreestuff.py +++ b/rowers/braintreestuff.py @@ -241,7 +241,6 @@ def create_subscription(rower,data): "payment_method_nonce": nonce_from_the_client }) - if result.is_success: payment_method_token = result.payment_method.token else: @@ -252,6 +251,7 @@ def create_subscription(rower,data): "plan_id": plan.external_id }) + if result.is_success: rower.paidplan = plan rower.planexpires = result.subscription.billing_period_end_date diff --git a/rowers/dataprep.py b/rowers/dataprep.py index f4a20ba2..89266aba 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -3,6 +3,8 @@ from __future__ import division from __future__ import print_function from __future__ import unicode_literals + + # All the data preparation, data cleaning and data mangling should # be defined here from __future__ import unicode_literals, absolute_import @@ -28,6 +30,7 @@ from pandas import DataFrame, Series from django.utils import timezone from django.utils.timezone import get_current_timezone from django_mailbox.models import Message,Mailbox,MessageAttachment +from django.core.exceptions import ValidationError from time import strftime import arrow @@ -1100,8 +1103,11 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', try: w.save() except ValidationError: - w.startdatetime = timezone.now() - w.save() + try: + w.startdatetime = timezone.now() + w.save() + except ValidationError: + return (0,'Unable to create your workout') if privacy == 'visible': ts = Team.objects.filter(rower=r) @@ -1189,7 +1195,6 @@ parsers = { } def parsenonpainsled(fileformat,f2,summary): - try: row = parsers[fileformat](f2) hasrecognized = True @@ -1197,7 +1202,6 @@ def parsenonpainsled(fileformat,f2,summary): hasrecognized = False return None, hasrecognized, '', 'unknown' - # handle speed coach GPS 2 if (fileformat == 'speedcoach2'): oarlength, inboard = get_empower_rigging(f2) @@ -1211,7 +1215,6 @@ def parsenonpainsled(fileformat,f2,summary): except ZeroDivisionError: summary = '' - # handle FIT if (fileformat == 'fit'): try: diff --git a/rowers/forms.py b/rowers/forms.py index fcc54568..3c3cef2d 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -632,16 +632,16 @@ class RegistrationFormSex(RegistrationFormUniqueEmail): raise forms.ValidationError('Must be at least 16 years old to register') return self.cleaned_data['birthdate'] - sex = forms.ChoiceField(required=True, + sex = forms.ChoiceField(required=False, choices=sexcategories, initial='not specified', label='Sex') weightcategory = forms.ChoiceField(label='Weight Category', - choices=weightcategories) + choices=weightcategories,initial='hwt',required=False) adaptiveclass = forms.ChoiceField(label='Adaptive Classification', - choices=adaptivecategories) + choices=adaptivecategories,initial='None',required=False) # def __init__(self, *args, **kwargs): # self.fields['sex'].initial = 'not specified' diff --git a/rowers/middleware.py b/rowers/middleware.py index d2fbd425..c1b968f2 100644 --- a/rowers/middleware.py +++ b/rowers/middleware.py @@ -124,7 +124,7 @@ class RowerPlanMiddleWare(object): self.get_response = get_response def __call__(self, request): - if request.user.is_authenticated and request.user.rower.rowerplan != 'basic': + if request.user.is_authenticated and request.user.rower.rowerplan not in ['basic','freecoach']: if request.user.rower.paymenttype == 'single': if request.user.rower.planexpires < timezone.now().date(): messg = 'Your paid plan has expired. We have reset you to a free basic plan.' diff --git a/rowers/models.py b/rowers/models.py index f124ac36..7f50af9f 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -601,7 +601,8 @@ plans = ( ('basic','basic'), ('pro','pro'), ('plan','plan'), - ('coach','coach') + ('coach','coach'), + ('freecoach','freecoach'), ) paymenttypes = ( @@ -913,7 +914,7 @@ def check_teams_on_change(sender, **kwargs): team = Team.objects.get(id=id) if team.manager.rower.rowerplan not in ['coach']: raise ValidationError( - "You cannot join a team led by a Pro or Self-Coach user" + "You cannot join a team led by a Pro, Free Coach Plan or Self-Coach user" ) m2m_changed.connect(check_teams_on_change, sender=Rower.team.through) @@ -1074,7 +1075,7 @@ def checkaccessplanuser(user,rower): r = Rower.objects.get(user=user) if rower == r: return True - team_managers = [t.manager for t in rower.team.all() if t.manager.rower.rowerplan in ['plan','coach']] + team_managers = [t.manager for t in rower.team.all() if t.manager.rower.rowerplan in ['plan','coach','freecoach']] if user.rower.rowerplan != 'basic': return user in team_managers else: @@ -2575,6 +2576,13 @@ class Workout(models.Model): choices=privacychoices) rankingpiece = models.BooleanField(default=False,verbose_name='Ranking Piece') duplicate = models.BooleanField(default=False,verbose_name='Duplicate Workout') + + def save(self, *args, **kwargs): + user = self.user + if self.user.rowerplan == 'freecoach': + raise forms.ValidationError("Free Coach User cannot have any workouts") + + super(Workout, self).save(*args, **kwargs) def __str__(self): @@ -3263,7 +3271,7 @@ class AccountRowerForm(ModelForm): def __init__(self, *args, **kwargs): super(AccountRowerForm, self).__init__(*args, **kwargs) - if self.instance.rowerplan != 'coach': + if 'coach' not in self.instance.rowerplan: self.fields.pop('offercoaching') diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index d62b50eb..34642a46 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -465,12 +465,12 @@ def add_team_session(t,ps): def add_rower_session(r,ps): teams = Team.objects.filter(manager=ps.manager) members = Rower.objects.filter(team__in=teams).distinct() - if r in members: + if r in members and r.rowerplan != 'freecoach': ps.rower.add(r) ps.save() return 1 - elif ps.manager.rower == r: + elif ps.manager.rower == r and r.rowerplan != 'freecoach': ps.rower.add(r) ps.save() diff --git a/rowers/teams.py b/rowers/teams.py index 3dc3f310..7be8d03a 100644 --- a/rowers/teams.py +++ b/rowers/teams.py @@ -77,7 +77,7 @@ def create_team(name,manager,private='open',notes='',viewing='allmembers'): if manager.rower.rowerplan == 'basic': if manager.rower.protrialexpires < timezone.now().date() and manager.rower.plantrialexpires < timezone.now().date(): return (0,'You need to upgrade to a paid plan to establish a team') - if manager.rower.rowerplan != 'coach': + if manager.rower.rowerplan not in ('coach','freecoach'): ts = Team.objects.filter(manager=manager) if len(ts)>=1: return (0,'You need to upgrade to the Coach plan to have more than one team') @@ -180,7 +180,7 @@ def rower_get_coaches(rower): def coach_getcoachees(coach): - if coach.mycoachgroup and coach.rowerplan == 'coach': + if coach.mycoachgroup and coach.rowerplan in ('coach','freecoach'): return Rower.objects.filter( coachinggroups__in=[coach.mycoachgroup] ).distinct().order_by("user__last_name","user__first_name") @@ -237,7 +237,7 @@ def create_coaching_request(coach,user): while code in codes: code = uuid.uuid4().hex[:10].upper() - if coach.rowerplan == 'coach': + if 'coach' in coach.rowerplan: rekwest = CoachRequest(coach=coach,user=user,code=code) rekwest.save() @@ -316,7 +316,7 @@ def create_coaching_offer(coach,user): while code in codes: code = uuid.uuid4().hex[:10].upper() - if coach.rowerplan == 'coach' and get_coach_club_size(coach)Dear {{ first_name }},

+ +

+ Thank you for registering on rowsandall.com. + You can now login using the credentials you provided. + The first thing you should do is go to the + Settings page + and make yourself familiar with the various options + available to personalize your experience on the website. + To get there, use the link in the email or click the user icon + in the upper right corner of the opening page. +

+ +

+ As a new member on the free coach plan, you can start with your first training + group and invite athletes to join it. + For more information about training groups and managing athletes, please read + our explanation at + https://analytics.rowsandall.com/2019/02/24/managing-a-rowing-training-group-coaching-and-remote-coaching-on-rowsandall-com/. +

+ +

+ As long as you are on the free coach plan, you can only managing athletes who are + on one of the paid plans. To remove this limitation, upgrade to one of the paid + coaching plans: + https://rowsandall.com/rowers/paidplans +

+ +

+ Oh, one more thing. The site is + developing fast. Bear with us. Don't hesitate to + contact me at info@rowsandall.com if anything is broken + or doesn't seem to work as advertised. +

+ +

+ Best Regards, the Rowsandall Team +

+{% endblock %} diff --git a/rowers/templates/freecoach_registration_form.html b/rowers/templates/freecoach_registration_form.html new file mode 100644 index 00000000..0c1f6a5d --- /dev/null +++ b/rowers/templates/freecoach_registration_form.html @@ -0,0 +1,79 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} +{% block title %}New Coach Registration{% endblock title %} +{% block meta %} + +{% endblock %} +{% block main %} + + + +{% endblock main %} + +{% block sidebar %} +{% include 'menu_help.html' %} +{% endblock %} diff --git a/rowers/templates/otwrankings.html b/rowers/templates/otwrankings.html index a4b2cdd6..a627b362 100644 --- a/rowers/templates/otwrankings.html +++ b/rowers/templates/otwrankings.html @@ -120,8 +120,8 @@ Duration - Power (upper) - Power + Power Estimate 1 + Power Estimate 2 diff --git a/rowers/templates/paidplans.html b/rowers/templates/paidplans.html index e8ced5c7..4038f032 100644 --- a/rowers/templates/paidplans.html +++ b/rowers/templates/paidplans.html @@ -178,6 +178,8 @@ {% if rower.rowerplan == 'coach' %}

COACH ({{ rower.paymenttype }})

+ {% elif rower.rowerplan == 'freecoach' %} +

Free COACH

{% else %}   {% endif %} @@ -248,6 +250,16 @@ {% endif %} + {% elif rower and rower.rowerplan == 'freecoach' %} + + + {% elif rower and rower.rowerplan == 'pro' %}   @@ -298,6 +310,14 @@ can purchase upgrades to "Pro" and "Self-Coach" plans.

+

+ The Coach plans come in two versions: free and paid. On the free coach plan, + you can only have athletes who are on the PRO paid plans or higher, and you + cannot upload any workouts to your own account. On the paid + plans, your athletes can be on the free plan, and you have full access to the + site functionality for your own workouts and those of your athletes. +

+

If you would like to find a coach who helps you plan your training through rowsandall.com, contact me throught the contact form.

diff --git a/rowers/templates/registeremail.html b/rowers/templates/registeremail.html index 20c81907..267358b3 100644 --- a/rowers/templates/registeremail.html +++ b/rowers/templates/registeremail.html @@ -10,8 +10,8 @@ Settings page and make yourself familiar with the various options available to personalize your experience on the website. - To get there, use the link in the email or click the button - with your first name on it in the upper right corner of the opening page. + To get there, use the link in the email or click the user icon + in the upper right corner of the opening page.

diff --git a/rowers/templates/registration_form.html b/rowers/templates/registration_form.html index 6ddf9632..7408e158 100644 --- a/rowers/templates/registration_form.html +++ b/rowers/templates/registration_form.html @@ -6,8 +6,36 @@ {% endblock %} {% block main %} -

New User Registration

@@ -234,7 +237,7 @@

{% endif %} - {% if user.rower.planexpires and user.rower.rowerplan != 'basic' and user.rower.paymenttype == 'single'%} + {% if user.rower.planexpires and user.rower|notfree and user.rower.paymenttype == 'single'%} {% if user.rower.planexpires|is_future_date %} {% if user.rower.planexpires|date_dif|ddays < 4 %}
  • diff --git a/templates/newbasefront.html b/templates/newbasefront.html index 5e761149..99275c14 100644 --- a/templates/newbasefront.html +++ b/templates/newbasefront.html @@ -169,7 +169,7 @@ {% if user.rower.rowerplan == 'pro' %} - {% elif user.rower.rowerplan == 'coach' %} + {% elif 'coach' in user.rower.rowerplan %} {% elif user.rower.rowerplan == 'plan' %} @@ -229,6 +229,9 @@