diff --git a/rowers/models.py b/rowers/models.py index 8c75db10..1e35b73e 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -11,6 +11,7 @@ from datetimewidget.widgets import DateTimeWidget from django.core.validators import validate_email import os import twitter +import re from django.conf import settings from sqlalchemy import create_engine @@ -63,6 +64,22 @@ class PowerZonesField(models.TextField): if not value: return if isinstance(value, list): return value + # remove double quotes and brackets + print value + value = re.sub(r'u\"','',value) + value = re.sub(r'u\'','',value) + value = re.sub(r'\\','',value) + value = re.sub(r'\"','',value) + value = re.sub(r'\'','',value) + value = re.sub(r'\[','',value) + value = re.sub(r'\]','',value) + value = re.sub(r'\[\[','[',value) + value = re.sub(r'\]\]',']',value) + value = re.sub(r'\ \ ',' ',value) + value = re.sub(r', ',',',value) + print "aap" + print value + return value.split(self.token) def from_db_value(self,value, expression, connection, context): @@ -100,7 +117,7 @@ class TeamInvite(models.Model): user = models.ForeignKey(User,null=True) issuedate = models.DateField(default=timezone.now) code = models.CharField(max_length=150,unique=True) - + email = models.CharField(max_length=150,null=True,blank=True) # Extension of User with rowing specific data class Rower(models.Model): @@ -310,7 +327,7 @@ class Workout(models.Model): ) user = models.ForeignKey(Rower) - team = models.ForeignKey(Team,blank=True,null=True) + team = models.ManyToManyField(Team,blank=True,null=True) name = models.CharField(max_length=150) date = models.DateField() workouttype = models.CharField(choices=workouttypes,max_length=50) diff --git a/rowers/tasks.py b/rowers/tasks.py index b89d11a8..fc32e777 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -54,6 +54,40 @@ def handle_sendemail_unrecognized(unrecognizedfile,useremail): os.remove(unrecognizedfile) return 1 +# Send email with team invitation +@app.task +def handle_sendemail_invite(email,name,code,teamname,manager): + fullemail = name+' <'+email+'>' + subject = 'Invitation to join team '+teamname + message = 'Dear '+name+',\n\n' + message += manager+' is inviting you to join his team '+teamname + message += ' on rowsandall.com\n\n' + message += 'If you already have an account on rowsandall.com, you can login to the site and you will find the invitation here on the Teams page:\n' + message += ' https://rowsandall.com/rowers/me/teams \n\n' + message += 'You can also click the direct link: \n' + message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n' + message += 'If you are not yet registered to rowsandall.com, ' + message += 'you can register for free at https://rowsandall.com/rowers/register\n' + message += 'After you set up your account, you can use the direct link: ' + message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n' + + message += 'You can also manually accept your team membership with the code.\n' + message += 'You will need to do this if you registered under a differnt email address than this one.\n' + message += 'Code: '+code+'\n' + message += 'Link to manually accept your team membership: ' + message += 'https://rowsandall.com/rowers/me/invitation\n\n' + message += "Best Regards, the Rowsandall Team" + + email = EmailMessage(subject, message, + 'Rowsandall ', + [fullemail]) + + + res = email.send() + + return 1 + + # Send email with TCX attachment @app.task def handle_sendemailtcx(first_name,last_name,email,tcxfile): diff --git a/rowers/teams.py b/rowers/teams.py index 1d52b44a..0b5b4b92 100644 --- a/rowers/teams.py +++ b/rowers/teams.py @@ -8,12 +8,16 @@ import time from django.db import IntegrityError import uuid +from rowers.tasks import handle_sendemail_invite + from rowers.models import ( Rower, Workout, Team, TeamInvite,User ) # Low level functions - to be called by higher level methods +inviteduration = 14 # days + def create_team(name,manager,notes=''): # needs some error testing try: @@ -32,23 +36,41 @@ def remove_team(id): def add_member(id,rower): t= Team.objects.get(id=id) rower.team.add(t) + # code to add all workouts + ws = Workout.objects.filter(user=rower) + for w in ws: + w.team.add(t) + return (1,'Member added') def remove_member(id,rower): t = Team.objects.get(id=id) rower.team.remove(t) + # remove the team from rower's workouts: + ws = Workout.objects.filter(user=rower) + for w in ws: + w.team.remove(t) + return (1,'Member removed') def mgr_remove_member(id,manager,rower): t = Team.objects.get(id=id) if t.manager == manager: - rower.team.remove(t) - return (1,'Member added') + remove_member(id,rower) + return (1,'Member removed') else: return (0,'You are not the team manager') return 0 +def count_invites(manager): + ts = Team.objects.filter(manager=manager) + count = 0 + for t in ts: + count += TeamInvite.objects.filter(team=t).count() + + return count + def count_members(id): t = Team.objects.get(id=id) return Rower.objects.filter(team=t).count() @@ -65,9 +87,14 @@ def get_team_members(id): t = Team.objects.get(id=id) return Rower.objects.filter(team=t) +def get_team_workouts(id): + t = Team.objects.get(id=id) + return Workout.objects.filter(team=t).order_by("-date", "-starttime") + # Medium level functionality def create_invite(team,manager,user=None): + r = Rower.objects.get(user=manager) if team.manager != manager: return (0,'Not the team manager') if user: @@ -77,7 +104,7 @@ def create_invite(team,manager,user=None): return (0,'Rower does not exist') if r2 in Rower.objects.filter(team=team): return (0,'Already member of that team') - if count_club_members(team.manager) < r.clubsize: + if count_club_members(team.manager)+count_invites(team.manager) < r.clubsize: codes = [i.code for i in TeamInvite.objects.all()] code = uuid.uuid4().hex[:10].upper() # prevent duplicates @@ -86,10 +113,63 @@ def create_invite(team,manager,user=None): invite = TeamInvite(team=team,code=code,user=user) invite.save() - return code + return (invite.id,'Invitation created') else: return (0,'You are at your club size limit') return (0,'Nothing done') + +def revoke_invite(id): + invitation = TeamInvite.objects.get(id=id) + invitation.delete() + + return (1,'Invitation revoked') + +def send_invite_email(id): + invitation = TeamInvite.objects.get(id=id) + if invitation.user: + email = invitation.user.email + name = invitation.user.first_name + " " + invitation.user.last_name + else: + email = invitation.email + + code = invitation.code + teamname = invitation.team.name + manager = invitation.team.manager.first_name+' '+invitation.team.manager.last_name + + if settings.DEBUG: + res = handle_sendemail_invite(email,name,code,teamname,manager) + else: + res = queue.enqueue(handle_sendemail_invite(email,name,code, + teamname, + manager)) + + return (1,'Invitation email sent') + +def process_invite_code(user,code): + try: + invitation = TeamInvite.objects.get(code=code) + except TeamInvite.DoesNotExist: + return (0,'The invitation has expired or the code is invalid') + + r = Rower.objects.get(user=user) + nu = datetime.date(timezone.now()) + if nu > invitation.issuedate+timedelta(days=inviteduration): + revoke_invite(invitation.id) + return (0,'The invitation has expired') + + t = invitation.team + result = add_member(t.id,r) + invitation.delete() + return result + +def remove_expired_invites(): + issuedate = timezone.now()-timedelta(days=inviteduration) + issuedate = datetime.date(issuedate) + invitations = TeamInvite.objects.filter(issuedate__lt=issuedate) + for i in invitations: + revoke_invite(i.id) + + return (1,'Expired invitations deleted') diff --git a/rowers/templates/cumstats.html b/rowers/templates/cumstats.html index 564c160f..eb0b9654 100644 --- a/rowers/templates/cumstats.html +++ b/rowers/templates/cumstats.html @@ -98,7 +98,7 @@
{% if cordict %}

Correlation Matrix

-

This table indicates a positive (+) or negative (-) correlation between two parameters. The strong correlations are indicated with ++ and --. +

This matrix indicates a positive (+) or negative (-) correlation between two parameters. The Spearman correlation coefficient has values between +1 and -1. Positive correlation between two metrics means that if one metric increases, the other value is also likely to increase. Negative is the opposite. The further from zero, the higher the likelyhood.

@@ -116,13 +116,13 @@ {% for key2,value in thedict.items %}
{% if value > 0.5 %} -
++
+
{{ value|floatformat:-1 }}
{% elif value > 0.1 %} -
+
+
{{ value|floatformat:-1 }}
{% elif value < -0.5 %} -
--
+
{{ value|floatformat:-1 }}
{% elif value < -0.1 %} -
-
+
{{ value|floatformat:-1 }}
{% else %}   {% endif %} diff --git a/rowers/templates/rower_form.html b/rowers/templates/rower_form.html index 90c920e6..c17124ec 100644 --- a/rowers/templates/rower_form.html +++ b/rowers/templates/rower_form.html @@ -3,107 +3,124 @@ {% block title %}Change Rower {% endblock %} {% block content %} -
-

-

Heart Rate Zones

- {% if form.errors %} -

- Please correct the error{{ form.errors|pluralize }} below. -

- {% endif %} - -
- - {{ form.as_table }} -
- {% csrf_token %} -
- - -

-
-

-

Account Information

- {% if userform.errors %} -

- Please correct the error{{ form.errors|pluralize }} below. -

- {% endif %} - {% if accountform.errors %} -

- -

- {% endif %} -
- - {{ userform.as_table }} - {{ accountform.as_table }} - - - -
Plan{{ rower.rowerplan }}
- {% csrf_token %} -
- - -
-

-
-
-

-

Power Zones

+
+
+

+

Heart Rate Zones

+ {% if form.errors %} +

+ Please correct the error{{ form.errors|pluralize }} below. +

+ {% endif %} +
- {% if powerzonesform.errors %} -

- Please correct the error{{ powerzonesform.errors|pluralize }} below. - {{ powerzonesform.non_field_errors }} - -

- {% endif %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {{ form.as_table }}
IDZone NameLower Boundary (Watt)
1{{ powerzonesform.ut3name }}
2{{ powerzonesform.ut2name }}{{ powerzonesform.pw_ut2 }}
3{{ powerzonesform.ut1name }}{{ powerzonesform.pw_ut1 }}
4{{ powerzonesform.atname }}{{ powerzonesform.pw_at }}
5{{ powerzonesform.trname }}{{ powerzonesform.pw_tr }}
6{{ powerzonesform.anname }}{{ powerzonesform.pw_an }}
{% csrf_token %}
-
-

-

-

Functional Threshold Power

-

Use this form to quickly change your zones based on the power of a - recent - full out 60 minutes effort. It will update all zones defined above.

+

+
+
+
+

+

Power Zones

+
+ {% if powerzonesform.errors %} +

+ Please correct the error{{ powerzonesform.errors|pluralize }} below. + {{ powerzonesform.non_field_errors }} + +

+ {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDZone NameLower Boundary (Watt)
1{{ powerzonesform.ut3name }}
2{{ powerzonesform.ut2name }}{{ powerzonesform.pw_ut2 }}
3{{ powerzonesform.ut1name }}{{ powerzonesform.pw_ut1 }}
4{{ powerzonesform.atname }}{{ powerzonesform.pw_at }}
5{{ powerzonesform.trname }}{{ powerzonesform.pw_tr }}
6{{ powerzonesform.anname }}{{ powerzonesform.pw_an }}
+ {% csrf_token %} +
+ +
+
+

+
+
+
+
+

+

Account Information

+ {% if userform.errors %} +

+ Please correct the error{{ form.errors|pluralize }} below. +

+ {% endif %} + {% if accountform.errors %} +

+ +

+ {% endif %} +
+ + {{ userform.as_table }} + {{ accountform.as_table }} + + + + + + +
Plan{{ rower.rowerplan }}
Plan Expiry{{ rower.planexpires }}
+ {% csrf_token %} +
+ {% if rower.rowerplan == 'basic' %} + Upgrade + {% else %} +   + {% endif %} +
+
+ + +
+

+
+
+

+

Functional Threshold Power

+

Use this form to quickly change your zones based on the power of a + recent + full out 60 minutes effort. It will update all zones defined above.

{{ powerform.as_table }} @@ -113,43 +130,61 @@ -

- {% if grants %} -

-

Applications

-
- - - - - - - - - {% for grant in grants %} - - - - - - {% endfor %} - -
ApplicationScopeRevoke
{{ grant.application }}{{ grant.scope }} - Revoke -
-

- {% endif %} +
+
+
+
+

+

Teams

+ +

+
+ +
+

+

Favorite Charts

+ +

+
- -
-

-

Favorite Charts

- -

+
+
+ {% if grants %} +

+

Applications

+ + + + + + + + + + {% for grant in grants %} + + + + + + {% endfor %} + +
ApplicationScopeRevoke
{{ grant.application }}{{ grant.scope }} + Revoke +
+

+ {% else %} +

 

+ {% endif %}
+
+ + {% endblock %} diff --git a/rowers/templates/workoutstats.html b/rowers/templates/workoutstats.html index 3f470672..6d413dea 100644 --- a/rowers/templates/workoutstats.html +++ b/rowers/templates/workoutstats.html @@ -75,8 +75,8 @@
{% if cordict %} -

Correlation table

-

This table indicates a positive (+) or negative (-) correlation between two parameters. The strong correlations are indicated with ++ and --. +

Correlation matrix

+

This matrix indicates a positive (+) or negative (-) correlation between two parameters. The Spearman correlation coefficient has values between +1 and -1. Positive correlation between two metrics means that if one metric increases, the other value is also likely to increase. Negative is the opposite. The further from zero, the higher the likelyhood.

@@ -94,13 +94,13 @@ {% for key2,value in thedict.items %}
{% if value > 0.5 %} -
++
+
{{ value|floatformat:-1 }}
{% elif value > 0.1 %} -
+
+
{{ value|floatformat:-1 }}
{% elif value < -0.5 %} -
--
+
{{ value|floatformat:-1 }}
{% elif value < -0.1 %} -
-
+
{{ value|floatformat:-1 }}
{% else %}   {% endif %}