From 240210d43e990d90828e905968f68e6eabf4e656 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sun, 18 Mar 2018 20:15:48 +0100
Subject: [PATCH 1/9] first use of email templates
---
rowers/tasks.py | 37 ++++++++++++++++++++++------------
rowers/templates/csvemail.html | 14 +++++++++++++
rowers/templates/csvemail.txt | 5 +++++
rowers/utils.py | 1 +
4 files changed, 44 insertions(+), 13 deletions(-)
create mode 100644 rowers/templates/csvemail.html
create mode 100644 rowers/templates/csvemail.txt
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 42568a6e..98e3c3d5 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -6,6 +6,7 @@ import gzip
import shutil
import numpy as np
+
from scipy import optimize
import rowingdata
@@ -40,7 +41,13 @@ from rowers.dataprepnodjango import (
create_c2_stroke_data_db
)
-from django.core.mail import send_mail, EmailMessage
+
+from django.core.mail import (
+ send_mail,
+ EmailMessage,EmailMultiAlternatives,
+ )
+from django.template.loader import get_template
+from django.template import Context
from django.db.utils import OperationalError
import datautils
@@ -51,7 +58,7 @@ import arrow
# testing task
-
+from django.contrib.staticfiles import finders
@app.task
def add(x, y):
@@ -625,24 +632,28 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
# send email with attachment
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
subject = "File from Rowsandall.com"
- message = "Dear " + first_name + ",\n\n"
- message += "Please find attached the requested file for your workout.\n\n"
- message += "Best Regards, the Rowsandall Team"
-
-
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ plaintext = get_template('csvemail.txt')
+ htmly = get_template('csvemail.html')
+
+ d = {'first_name':first_name}
+
+ from_email = 'Rowsandall '
+
+ text_content = plaintext.render(d)
+ html_content = htmly.render(d)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
+ msg.attach_alternative(html_content, "text/html")
if os.path.isfile(csvfile):
- email.attach_file(csvfile)
+ msg.attach_file(csvfile)
else:
csvfile2 = csvfile
with gzip.open(csvfile + '.gz', 'rb') as f_in, open(csvfile2, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
- email.attach_file(csvfile2)
+ msg.attach_file(csvfile2)
os.remove(csvfile2)
@@ -652,7 +663,7 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
return 1
diff --git a/rowers/templates/csvemail.html b/rowers/templates/csvemail.html
new file mode 100644
index 00000000..95d44006
--- /dev/null
+++ b/rowers/templates/csvemail.html
@@ -0,0 +1,14 @@
+
+
+
+ Dear {{ first_name }},
+
+
+ Please find attached the requested file for your workout.
+
+
+
+ Best Regards, the Rowsandall Team
+
+
+
diff --git a/rowers/templates/csvemail.txt b/rowers/templates/csvemail.txt
new file mode 100644
index 00000000..33071ef7
--- /dev/null
+++ b/rowers/templates/csvemail.txt
@@ -0,0 +1,5 @@
+Dear {{ first_name }},
+
+Please find attached the requested file for your workout.
+
+Best Regards, the Rowsandall Team
diff --git a/rowers/utils.py b/rowers/utils.py
index e1588ffe..fa159aba 100644
--- a/rowers/utils.py
+++ b/rowers/utils.py
@@ -7,6 +7,7 @@ from django.conf import settings
import uuid
import datetime
+
lbstoN = 4.44822
landingpages = (
From 073533f1a924d7bcc5f1c9821e7a3e3f449a4dc1 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 10:23:39 +0100
Subject: [PATCH 2/9] fixed nan values in updatefitnessmetric
---
rowers/dataprepnodjango.py | 16 +++++++---------
rowers/views.py | 3 ++-
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/rowers/dataprepnodjango.py b/rowers/dataprepnodjango.py
index e9e3cd10..49a3e6dc 100644
--- a/rowers/dataprepnodjango.py
+++ b/rowers/dataprepnodjango.py
@@ -677,14 +677,11 @@ def getsmallrowdata_db(columns,ids=[],debug=False):
def fitnessmetric_to_sql(m,table='powertimefitnessmetric',debug=False,
doclean=False):
# test if nan among values
- if np.nan in m.values() or np.inf in m.values():
- for key in m.keys():
- if np.isnan([m[key]]):
- m[key] = -1
- if m[key] == np.inf:
- m[key] = -1
- if -m[key] == np.inf:
- m[key] = -1
+ for key in m.keys():
+ if str(m[key]) == 'nan':
+ m[key] = -1
+ if 'inf' in str(m[key]):
+ m[key] = -1
if debug:
engine = create_engine(database_url_debug, echo=False)
@@ -702,9 +699,10 @@ def fitnessmetric_to_sql(m,table='powertimefitnessmetric',debug=False,
values = tuple(m[key] for key in m.keys())
with engine.connect() as conn, conn.begin():
- result = conn.execute(query,values)
if doclean:
result2 = conn.execute(query2)
+ result = conn.execute(query,values)
+
conn.close()
engine.dispose()
diff --git a/rowers/views.py b/rowers/views.py
index fb69cd79..9a5588a5 100644
--- a/rowers/views.py
+++ b/rowers/views.py
@@ -13031,7 +13031,8 @@ def plannedsession_view(request,id=0,rowerid=0,
{
'psdict': psdict,
'attrs':[
- 'name','startdate','enddate','sessiontype',
+ 'name','startdate','enddate','preferreddate',
+ 'sessiontype',
'sessionmode','criterium',
'sessionvalue','sessionunit','comment',
],
From 5afb2477cf16b6a442ed15cc6d62033a0b649850 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 12:44:41 +0100
Subject: [PATCH 3/9] cleaning up email templates
---
rowers/tasks.py | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 98e3c3d5..3f5be55a 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -5,7 +5,7 @@ import gc
import gzip
import shutil
import numpy as np
-
+import re
from scipy import optimize
@@ -30,6 +30,13 @@ import pandas as pd
from django_rq import job
from django.utils import timezone
+from django.utils.html import strip_tags
+
+def textify(html):
+ # Remove html tags and continuous whitespaces
+ text_only = re.sub('[ \t]+', ' ', strip_tags(html))
+ # Strip single spaces in the beginning of each line
+ return text_only.replace('\n ', '\n').strip()
from utils import deserialize_list
@@ -640,9 +647,9 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
from_email = 'Rowsandall '
- text_content = plaintext.render(d)
html_content = htmly.render(d)
-
+ text_content = textify(html_content)
+
msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
msg.attach_alternative(html_content, "text/html")
From b744eff7f692a58a1c209749e2fbff39ee0b443c Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 14:47:40 +0100
Subject: [PATCH 4/9] added breakthrough notification
---
rowers/tasks.py | 66 ++++++++++-------------
rowers/templates/breakthroughemail.html | 70 +++++++++++++++++++++++++
rowers/templates/csvemail.html | 2 +-
3 files changed, 98 insertions(+), 40 deletions(-)
create mode 100644 rowers/templates/breakthroughemail.html
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 3f5be55a..2349eade 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -311,55 +311,43 @@ def handle_sendemail_breakthrough(workoutid, useremail,
btvalues=pd.DataFrame().to_json(),
**kwargs):
+ if 'debug' in kwargs:
+ debug = kwargs['debug']
+ else:
+ debug = False
+
siteurl = SITE_URL
if debug:
siteurl = SITE_URL_DEV
- # send email with attachment
- subject = "A breakthrough workout on rowsandall.com"
- message = "Dear " + userfirstname + ",\n"
- message += "Congratulations! Your recent workout has been analyzed"
- message += " by Rowsandall.com and it appears your fitness,"
- message += " as measured by Critical Power, has improved!"
- message += " Critical Power (CP) is the power that you can "
- message += "sustain for a given duration. For more, see this "
- message += " article in the analytics blog:\n\n"
- message += " http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/ \n\n"
- message += "Link to the workout "+siteurl+"/rowers/workout/"
- message += str(workoutid)
- message += "/edit\n\n"
- message += "To add the workout to your Ranking workouts and see the updated CP plot, click the following link:\n"
- message += siteurl+"/rowers/workout/"
- message += str(workoutid)
- message += "/updatecp\n\n"
-
btvalues = pd.read_json(btvalues)
btvalues.sort_values('delta', axis=0, inplace=True)
- if not btvalues.empty:
- message += "These were the breakthrough values:\n"
- for t in btvalues.itertuples():
- delta = t.delta
- cpvalue = t.cpvalues
- pwr = t.pwr
+ tablevalues = [
+ {'delta': t.delta,
+ 'cpvalue': t.cpvalues,
+ 'pwr': t.pwr
+ } for t in btvalues.itertuples()
+ ]
- message += "Time: {delta} seconds\n".format(
- delta=delta
- )
- message += "New: {cpvalue:.0f} Watt\n".format(
- cpvalue=cpvalue
- )
- message += "Old: {pwr:.0f} Watt\n\n".format(
- pwr=pwr
- )
+ # send email with attachment
+ subject = "A breakthrough workout on rowsandall.com"
+ from_email = 'Rowsandall '
- message += "To opt out of these email notifications, deselect the checkbox on your Profile page under Account Information.\n\n"
+ htmly = get_template('breakthroughemail.html')
- message += "Best Regards, the Rowsandall Team"
+ d = {
+ 'first_name':userfirstname,
+ 'siteurl':siteurl,
+ 'workoutid':workoutid,
+ 'btvalues':tablevalues,
+ }
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [useremail])
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [useremail])
+ msg.attach_alternative(html_content, "text/html")
if 'emailbounced' in kwargs:
emailbounced = kwargs['emailbounced']
@@ -367,7 +355,7 @@ def handle_sendemail_breakthrough(workoutid, useremail,
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
diff --git a/rowers/templates/breakthroughemail.html b/rowers/templates/breakthroughemail.html
new file mode 100644
index 00000000..0c59145c
--- /dev/null
+++ b/rowers/templates/breakthroughemail.html
@@ -0,0 +1,70 @@
+
+
+{% load staticfiles %}
+{% load rowerfilters %}
+
+
+
+Dear {{ first_name }},
+
+
+ Congratulations! Your recent workout has been analyzed
+ by Rowsandall.com and it appears your fitness,
+ as measured by Critical Power, has improved!
+ Critical Power (CP) is the power that you can
+ sustain for a given duration. For more, see this
+ article in the analytics blog:
+
+
+
+
+ http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/
+
+
+
+ Link to the workout:
+ {{ siteurl }}/rowers/workout/{{ workoutid }}
+
+
+
+ To add the workout to your Ranking workouts and see the updated CP plot,
+ click the following link:
+
+
+
+
+ {{ siteurl }}/rowers/workout/{{ workoutid }}/updatecp
+
+
+{% if btvalues %}
+
+ These were the breakthrough values:
+
+
+
+
+
+ | Time (sec) | New Power (W) | Old Power |
+
+ {% for set in btvalues %}
+
+ | {{ set|lookup:"delta" }} |
+ {{ set|lookup:"cpvalue" }} |
+ {{ set|lookup:"pwr" }} |
+
+ {% endfor %}
+
+
+{% endif %}
+
+
+
+ To opt out of these email notifications, deselect the checkbox on your Profile
+ page under Account Information.
+
+
+
+ Best Regards, the Rowsandall Team
+
+
+
diff --git a/rowers/templates/csvemail.html b/rowers/templates/csvemail.html
index 95d44006..89b9b267 100644
--- a/rowers/templates/csvemail.html
+++ b/rowers/templates/csvemail.html
@@ -1,6 +1,6 @@
-
+
Dear {{ first_name }},
From e7e2ced5f516af7a68fe8fa4a124f64cb6ed8295 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 15:49:57 +0100
Subject: [PATCH 5/9] a few new email templates
---
rowers/tasks.py | 158 +++++++++++++-----------
rowers/templates/breakthroughemail.html | 10 +-
rowers/templates/csvemail.html | 11 +-
rowers/templates/csvemail.txt | 5 -
rowers/templates/emailbase.html | 16 +++
rowers/templates/gpxemail.html | 15 +++
rowers/templates/hardemail.html | 36 ++++++
rowers/templates/otwpoweremail.html | 35 ++++++
rowers/templates/summarymail.html | 15 +++
rowers/templates/tcxemail.html | 15 +++
rowers/templates/unrecognizedemail.html | 26 ++++
11 files changed, 254 insertions(+), 88 deletions(-)
delete mode 100644 rowers/templates/csvemail.txt
create mode 100644 rowers/templates/emailbase.html
create mode 100644 rowers/templates/gpxemail.html
create mode 100644 rowers/templates/hardemail.html
create mode 100644 rowers/templates/otwpoweremail.html
create mode 100644 rowers/templates/summarymail.html
create mode 100644 rowers/templates/tcxemail.html
create mode 100644 rowers/templates/unrecognizedemail.html
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 2349eade..6b0d06a9 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -371,31 +371,34 @@ def handle_sendemail_hard(workoutid, useremail,
btvalues=pd.DataFrame().to_json(),
debug=False,**kwargs):
+ if 'debug' in kwargs:
+ debug = kwargs['debug']
+ else:
+ debug = False
+
siteurl = SITE_URL
if debug:
siteurl = SITE_URL_DEV
# send email with attachment
subject = "That was a pretty hard workout on rowsandall.com"
- message = "Dear " + userfirstname + ",\n"
- message += "Congratulations! Your recent workout has been analyzed"
- message += " by Rowsandall.com and it appears that it was pretty hard work."
- message += " You were working pretty close to your Critical Power\n\n"
- message += " Critical Power (CP) is the power that you can "
- message += "sustain for a given duration. For more, see this "
- message += " article in the analytics blog:\n\n"
- message += " http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/ \n\n"
- message += "Link to the workout: "+siteurl+"/rowers/workout/"
- message += str(workoutid)
- message += "/edit\n\n"
+ from_email = 'Rowsandall '
- message += "To opt out of these email notifications, deselect the checkbox on your Profile page under Account Information.\n\n"
+ htmly = get_template('hardemail.html')
- message += "Best Regards, the Rowsandall Team"
+ d = {
+ 'first_name':userfirstname,
+ 'siteurl':siteurl,
+ 'workoutid':workoutid,
+ 'btvalues':tablevalues,
+ }
+
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [useremail])
+ msg.attach_alternative(html_content, "text/html")
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [useremail])
if 'emailbounced' in kwargs:
emailbounced = kwargs['emailbounced']
@@ -403,7 +406,7 @@ def handle_sendemail_hard(workoutid, useremail,
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
@@ -484,24 +487,19 @@ def handle_sendemail_unrecognizedowner(useremail, userfirstname,
# send email with attachment
fullemail = useremail
subject = "Unrecognized file from Rowsandall.com"
- message = "Dear " + userfirstname + ",\n\n"
- message += """
- The file you tried to send to rowsandall.com was not recognized by
- our email processing system. You may have sent a file in a format
- that is not supported. Sometimes, rowing apps make file format changes.
- When that happens, it takes some time for rowsandall.comm to make
- the necessary changes on our side and support the app again.
- The file has been sent to the developer at rowsandall.com for evaluation.
+ plaintext = get_template('csvemail.txt')
+ htmly = get_template('csvemail.html')
+ d = {'first_name':first_name}
- """
+ from_email = 'Rowsandall '
- message += "Best Regards, the Rowsandall Team"
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
+ msg.attach_alternative(html_content, "text/html")
if 'emailbounced' in kwargs:
emailbounced = kwargs['emailbounced']
@@ -509,7 +507,7 @@ def handle_sendemail_unrecognizedowner(useremail, userfirstname,
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
return 1
@@ -522,15 +520,20 @@ def handle_sendemailtcx(first_name, last_name, email, tcxfile,**kwargs):
# send email with attachment
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
subject = "File from Rowsandall.com"
- message = "Dear " + first_name + ",\n\n"
- message += "Please find attached the requested file for your workout.\n\n"
- message += "Best Regards, the Rowsandall Team"
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ htmly = get_template('tcxemail.html')
- email.attach_file(tcxfile)
+ d = {'first_name':first_name}
+
+ from_email = 'Rowsandall '
+
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
+ msg.attach_alternative(html_content, "text/html")
+
+ msg.attach_file(tcxfile)
if 'emailbounced' in kwargs:
emailbounced = kwargs['emailbounced']
@@ -538,7 +541,7 @@ def handle_sendemailtcx(first_name, last_name, email, tcxfile,**kwargs):
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
# remove tcx file
@@ -564,13 +567,8 @@ def handle_zip_file(emailfrom, subject, file,**kwargs):
if debug:
print "attaching"
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
- if not emailbounced:
- res = email.send()
+ res = email.send()
if debug:
@@ -582,25 +580,30 @@ def handle_zip_file(emailfrom, subject, file,**kwargs):
@app.task
def handle_sendemailsummary(first_name, last_name, email, csvfile, **kwargs):
- # send email with attachment
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
subject = "File from Rowsandall.com"
- message = "Dear " + first_name + ",\n\n"
- message += "Please find attached the requested summary file.\n\n"
- message += "Best Regards, the Rowsandall Team"
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ htmly = get_template('summarymail.html')
+
+ d = {'first_name':first_name}
+
+ from_email = 'Rowsandall '
+
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
+ msg.attach_alternative(html_content, "text/html")
+
if os.path.isfile(csvfile):
- email.attach_file(csvfile)
+ msg.attach_file(csvfile)
else:
csvfile2 = csvfile
with gzip.open(csvfile + '.gz', 'rb') as f_in, open(csvfile2, 'wb') as f_out:
shutil.copyfileobj(f_in, f_out)
- email.attach_file(csvfile2)
+ msg.attach_file(csvfile2)
os.remove(csvfile2)
if 'emailbounced' in kwargs:
@@ -609,7 +612,7 @@ def handle_sendemailsummary(first_name, last_name, email, csvfile, **kwargs):
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
try:
os.remove(csvfile)
@@ -781,24 +784,35 @@ def handle_otwsetpower(self,f1, boattype, weightvalue,
first_name,
last_name, btvalues=btvalues.to_json())
- # send email
- fullemail = first_name + " " + last_name + " " + "<" + email + ">"
- subject = "Your Rowsandall OTW calculations are ready"
- message = "Dear " + first_name + ",\n\n"
- message += "Your Rowsandall OTW calculations are ready.\n"
- message += "Thank you for using rowsandall.com.\n\n"
- message += "Rowsandall OTW calculations have not been fully implemented yet.\n"
- message += "We are now running an experimental version for debugging purposes. \n"
- message += "Your wind/stream corrected plot is available here: "
- message += siteurl+"/rowers/workout/"
- message += str(workoutid)
- message += "/interactiveotwplot\n\n"
- message += "Please report any bugs/inconsistencies/unexpected results at rowsandall.slack.com or by reply to this email.\n\n"
- message += "Best Regards, The Rowsandall Physics Department."
- send_mail(subject, message,
- 'Rowsandall Physics Department ',
- [fullemail])
+ subject = "Your OTW Physics Calculations are ready"
+ from_email = 'Rowsandall '
+ fullemail = first_name + " " + last_name + " " + "<" + email + ">"
+
+ htmly = get_template('otwpoweremail.html')
+
+ d = {
+ 'first_name':first_name,
+ 'siteurl':siteurl,
+ 'workoutid':workoutid,
+ }
+
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
+ msg.attach_alternative(html_content, "text/html")
+
+
+ if 'emailbounced' in kwargs:
+ emailbounced = kwargs['emailbounced']
+ else:
+ emailbounced = False
+
+ if not emailbounced:
+ res = msg.send()
+
+
return 1
diff --git a/rowers/templates/breakthroughemail.html b/rowers/templates/breakthroughemail.html
index 0c59145c..bd1107ab 100644
--- a/rowers/templates/breakthroughemail.html
+++ b/rowers/templates/breakthroughemail.html
@@ -1,10 +1,8 @@
-
-
+{% extends "emailbase.html" %}
{% load staticfiles %}
{% load rowerfilters %}
-
-
+{% block body %}
Dear {{ first_name }},
@@ -66,5 +64,5 @@
Best Regards, the Rowsandall Team
-
-
+{% endblock %}
+
diff --git a/rowers/templates/csvemail.html b/rowers/templates/csvemail.html
index 89b9b267..edb5ec7e 100644
--- a/rowers/templates/csvemail.html
+++ b/rowers/templates/csvemail.html
@@ -1,6 +1,8 @@
-
-
-
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
Dear {{ first_name }},
@@ -10,5 +12,4 @@
Best Regards, the Rowsandall Team
-
-
+{% endblock %}
diff --git a/rowers/templates/csvemail.txt b/rowers/templates/csvemail.txt
deleted file mode 100644
index 33071ef7..00000000
--- a/rowers/templates/csvemail.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-Dear {{ first_name }},
-
-Please find attached the requested file for your workout.
-
-Best Regards, the Rowsandall Team
diff --git a/rowers/templates/emailbase.html b/rowers/templates/emailbase.html
new file mode 100644
index 00000000..58c339eb
--- /dev/null
+++ b/rowers/templates/emailbase.html
@@ -0,0 +1,16 @@
+
+
+
+{% load staticfiles %}
+{% load rowerfilters %}
+
+
+
+
+ {% block body %}
+
+ {% endblock %}
+
+
+
+
diff --git a/rowers/templates/gpxemail.html b/rowers/templates/gpxemail.html
new file mode 100644
index 00000000..edb5ec7e
--- /dev/null
+++ b/rowers/templates/gpxemail.html
@@ -0,0 +1,15 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+ Dear {{ first_name }},
+
+
+ Please find attached the requested file for your workout.
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/hardemail.html b/rowers/templates/hardemail.html
new file mode 100644
index 00000000..0ca5cf10
--- /dev/null
+++ b/rowers/templates/hardemail.html
@@ -0,0 +1,36 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+ Dear {{ first_name }},
+
+
+ Congratulations! Your recent workout has been analyzed
+ by Rowsandall.com and it appears it was pretty hard work.
+ You were working pretty close to your Critical Power.
+ Critical Power (CP) is the power that you can
+ sustain for a given duration. For more, see this
+ article in the analytics blog:
+
+
+
+
+ http://analytics.rowsandall.com/2017/06/17/how-do-we-calculate-critical-power/
+
+
+
+ Link to the workout:
+ {{ siteurl }}/rowers/workout/{{ workoutid }}
+
+
+
+
+ To opt out of these email notifications, deselect the checkbox on your Profile
+ page under Account Information.
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/otwpoweremail.html b/rowers/templates/otwpoweremail.html
new file mode 100644
index 00000000..8b90a830
--- /dev/null
+++ b/rowers/templates/otwpoweremail.html
@@ -0,0 +1,35 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ first_name }},
+
+
+ Your Rowsandall OTW calculations are ready.
+
+
+ Thank you for using rowsandall.com.
+
+
+ Rowsandall OTW calculations have not been fully implemented yet.
+
+
+ We are now running an experimental version for debugging purposes.
+
+
+ Your wind/stream corrected plot is available here:
+
+ {{ siteurl }}/rowers/workout/{{ workoutid }}/interactiveotwplot
+
+
+ Please report any bugs/inconsistencies/unexpected results
+ by reply to this email.
+
+
+
+
+
+ Best Regards, the Rowsandall Physics Department
+
+{% endblock %}
diff --git a/rowers/templates/summarymail.html b/rowers/templates/summarymail.html
new file mode 100644
index 00000000..918f8443
--- /dev/null
+++ b/rowers/templates/summarymail.html
@@ -0,0 +1,15 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+ Dear {{ first_name }},
+
+
+ Please find attached the requested summary file.
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/tcxemail.html b/rowers/templates/tcxemail.html
new file mode 100644
index 00000000..edb5ec7e
--- /dev/null
+++ b/rowers/templates/tcxemail.html
@@ -0,0 +1,15 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+ Dear {{ first_name }},
+
+
+ Please find attached the requested file for your workout.
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/unrecognizedemail.html b/rowers/templates/unrecognizedemail.html
new file mode 100644
index 00000000..bc6706ab
--- /dev/null
+++ b/rowers/templates/unrecognizedemail.html
@@ -0,0 +1,26 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+ Dear {{ first_name }},
+
+
+ The file you tried to send to rowsandall.com was not recognized by
+ our email processing system. You may have sent a file in a format
+ that is not supported. Sometimes, rowing apps make file format changes.
+ When that happens, it takes some time for rowsandall.comm to make
+ the necessary changes on our side and support the app again.
+
+
+
+ The file has been sent to the developer at rowsandall.com for evaluation.
+
+
+
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
From 0dadcf32a62ffc00cc98dde6c82f0efd51e48315 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 17:48:47 +0100
Subject: [PATCH 6/9] a few more templates
---
rowers/tasks.py | 90 ++++++++++++-------------
rowers/templates/teaminviteemail.html | 58 ++++++++++++++++
rowers/templates/teamresponseemail.html | 26 +++++++
3 files changed, 128 insertions(+), 46 deletions(-)
create mode 100644 rowers/templates/teaminviteemail.html
create mode 100644 rowers/templates/teamresponseemail.html
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 6b0d06a9..6e882bcd 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -631,7 +631,6 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
subject = "File from Rowsandall.com"
- plaintext = get_template('csvemail.txt')
htmly = get_template('csvemail.html')
d = {'first_name':first_name}
@@ -1035,34 +1034,25 @@ def handle_sendemail_invite(email, name, code, teamname, manager,
debug=False,**kwargs):
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 += 'By accepting the invite, you will have access to your'
- message += " team's workouts on rowsandall.com and your workouts will "
- message += " be visible to "
- message += "the members of the team.\n\n"
- message += "By accepting the invite, you are agreeing with the sharing "
- message += "of personal data according to our privacy policy.\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 different 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"
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ htmly = get_template('teaminviteemail.html')
+
+ d = {
+ 'name':name,
+ 'manage':manager,
+ 'code':code,
+ 'teamname':teamname,
+ }
+
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
+ msg.attach_alternative(html_content, "text/html")
if 'emailbounced' in kwargs:
emailbounced = kwargs['emailbounced']
@@ -1070,7 +1060,7 @@ def handle_sendemail_invite(email, name, code, teamname, manager,
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
return 1
@@ -1085,25 +1075,33 @@ def handle_sendemailnewresponse(first_name, last_name,
workoutname, workoutid, commentid,
debug=False,**kwargs):
fullemail = first_name + ' ' + last_name + ' <' + email + '>'
+ from_email = 'Rowsandall '
subject = 'New comment on workout ' + workoutname
- message = 'Dear ' + first_name + ',\n\n'
- message += commenter_first_name + ' ' + commenter_last_name
- message += ' has written a new comment on the workout '
- message += workoutname + '\n\n'
- message += comment
- message += '\n\n'
- message += 'You can read the comment here:\n'
- message += 'https://rowsandall.com/rowers/workout/' + \
- str(workoutid) + '/comment'
- message += '\n\n'
- message += 'You are receiving this email because you are subscribed '
- message += 'to comments on this workout. To unsubscribe, follow this link:\n'
- message += 'https://rowsandall.com/rowers/workout/' + \
- str(workoutid) + '/unsubscribe'
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
+
+
+ htmly = get_template('teamresponseemail.html')
+
+ d = {
+ 'first_name':first_name,
+ 'commenter_first_name':commenter_first_name,
+ 'commenter_last_name':commenter_last_name,
+ 'comment':comment,
+ 'workoutname':workoutname,
+ 'siteurl':siteurl,
+ 'workoutid':workoutid,
+ 'commentid':commentid
+ }
+
+ html_content = htmly.render(d)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
+ msg.attach_alternative(html_content, "text/html")
+
if 'emailbounced' in kwargs:
emailbounced = kwargs['emailbounced']
@@ -1111,7 +1109,7 @@ def handle_sendemailnewresponse(first_name, last_name,
emailbounced = False
if not emailbounced:
- res = email.send()
+ res = msg.send()
return 1
diff --git a/rowers/templates/teaminviteemail.html b/rowers/templates/teaminviteemail.html
new file mode 100644
index 00000000..f0895252
--- /dev/null
+++ b/rowers/templates/teaminviteemail.html
@@ -0,0 +1,58 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ name }},
+
+
+ {{ manager }} is inviting you to join his team {{ teamname }}
+ on rowsandall.com
+
+
+ By accepting the invite, you will have access to your
+ team's workouts on rowsandall.com and your workouts will
+ be visible to
+ the members of the team.
+
+
+ By accepting the invite, you are agreeing with the sharing
+ of personal data according to our privacy policy.
+
+
+ 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:
+ {{ siteurl }}/rowers/me/teams
+
+
+ You can also click the direct link:
+
+ {{ siteurl }}/rowers/me/invitation/{{ code }}
+
+
+ If you are not yet registered to rowsandall.com,
+ you can register for free at
+ {{ siteurl }}/rowers/register
+
+
+ After you set up your account, you can use the direct link:
+
+ {{ siteurl }}/rowers/me/invitation/{{ code }}
+
+
+ You can also manually accept your team membership with the code.
+ You will need to do this if you registered under a different email address than this one.
+
+
+ Code: {{ code }}
+
+
+ Link to manually accept your team membership:
+
+ {{ siteurl }}/rowers/me/invitation
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/teamresponseemail.html b/rowers/templates/teamresponseemail.html
new file mode 100644
index 00000000..3bb04e19
--- /dev/null
+++ b/rowers/templates/teamresponseemail.html
@@ -0,0 +1,26 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ name }},
+
+
+ {{ commenter_first_name }} {{ commenter_last_name }} has written
+ a new comment on your workout {{ workoutname }}
+
+
+ {{ comment }}
+
+
+ You can read the comment here:
+
+
+
+ {{ siteurl }}/rowers/workout/{{ workoutid }}/comment
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
From ffed5c694b09905b64b1822a4a6c68a882207e55 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 18:05:30 +0100
Subject: [PATCH 7/9] bug fix in email templates
---
rowers/tasks.py | 39 ++++++++++++++++++++++++---------------
1 file changed, 24 insertions(+), 15 deletions(-)
diff --git a/rowers/tasks.py b/rowers/tasks.py
index 6e882bcd..b3fed727 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -38,6 +38,29 @@ def textify(html):
# Strip single spaces in the beginning of each line
return text_only.replace('\n ', '\n').strip()
+def send_template_email(from_email,to_email,subject,template,context,*args,**kwargs):
+
+ htmly = get_template(template)
+
+ html_content = htmly.render(context)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, to_email)
+ msg.attach_alternative(html_content, "text/html")
+
+
+ if 'emailbounced' in kwargs:
+ emailbounced = kwargs['emailbounced']
+ else:
+ emailbounced = False
+
+ if not emailbounced:
+ res = msg.send()
+ else:
+ return 0
+
+ return res
+
from utils import deserialize_list
from rowers.dataprepnodjango import (
@@ -1096,21 +1119,7 @@ def handle_sendemailnewresponse(first_name, last_name,
'commentid':commentid
}
- html_content = htmly.render(d)
- text_content = textify(html_content)
-
- msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
- msg.attach_alternative(html_content, "text/html")
-
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
+ res = send_template_email(from_email,[fullemail],subject,'teamresponseemail.html',d,**kwargs)
return 1
From ea30428f6ea4186f0db88e77ea37428f4b86df0f Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 21:08:45 +0100
Subject: [PATCH 8/9] a few more templates
---
rowers/tasks.py | 511 ++++++++------------
rowers/teams.py | 1 +
rowers/templates/teamdropemail.html | 16 +
rowers/templates/teaminviteacceptemail.html | 16 +
rowers/templates/teaminviterejectemail.html | 17 +
rowers/templates/teamrejectemail.html | 16 +
rowers/templates/teamremoveemail.html | 19 +
rowers/templates/teamrequestemail.html | 29 ++
rowers/templates/teamresponseemail.html | 2 +-
rowers/templates/teamwelcomeemail.html | 15 +
temp.txt | 5 -
11 files changed, 321 insertions(+), 326 deletions(-)
create mode 100644 rowers/templates/teamdropemail.html
create mode 100644 rowers/templates/teaminviteacceptemail.html
create mode 100644 rowers/templates/teaminviterejectemail.html
create mode 100644 rowers/templates/teamrejectemail.html
create mode 100644 rowers/templates/teamremoveemail.html
create mode 100644 rowers/templates/teamrequestemail.html
create mode 100644 rowers/templates/teamwelcomeemail.html
delete mode 100644 temp.txt
diff --git a/rowers/tasks.py b/rowers/tasks.py
index b3fed727..d802a565 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -32,35 +32,6 @@ from django_rq import job
from django.utils import timezone
from django.utils.html import strip_tags
-def textify(html):
- # Remove html tags and continuous whitespaces
- text_only = re.sub('[ \t]+', ' ', strip_tags(html))
- # Strip single spaces in the beginning of each line
- return text_only.replace('\n ', '\n').strip()
-
-def send_template_email(from_email,to_email,subject,template,context,*args,**kwargs):
-
- htmly = get_template(template)
-
- html_content = htmly.render(context)
- text_content = textify(html_content)
-
- msg = EmailMultiAlternatives(subject, text_content, from_email, to_email)
- msg.attach_alternative(html_content, "text/html")
-
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
- else:
- return 0
-
- return res
-
from utils import deserialize_list
from rowers.dataprepnodjango import (
@@ -90,6 +61,51 @@ import arrow
from django.contrib.staticfiles import finders
+def textify(html):
+ # Remove html tags and continuous whitespaces
+ text_only = re.sub('[ \t]+', ' ', strip_tags(html))
+ # Strip single spaces in the beginning of each line
+ return text_only.replace('\n ', '\n').strip()
+
+def send_template_email(from_email,to_email,subject,
+ template,context,
+ *args,**kwargs):
+
+ htmly = get_template(template)
+
+ html_content = htmly.render(context)
+ text_content = textify(html_content)
+
+ msg = EmailMultiAlternatives(subject, text_content, from_email, to_email)
+ msg.attach_alternative(html_content, "text/html")
+
+ if 'attach_file' in kwargs:
+ fileobj = kwargs['attach_file']
+ if os.path.isfile(fileobj):
+ msg.attach_file(fileobj)
+ else:
+ try:
+ fileobj2 = fileobj
+ with gzip.open(fileobj+'.gz','rb') as f_in, open(fileobj2,'wb') as f_out:
+ shutil.copyfileobj(f_in,f_out)
+ msg.attach_file(fileobj2)
+ os.remove(fileobj2)
+ except IOError:
+ pass
+
+ if 'emailbounced' in kwargs:
+ emailbounced = kwargs['emailbounced']
+ else:
+ emailbounced = False
+
+ if not emailbounced:
+ res = msg.send()
+ else:
+ return 0
+
+ return res
+
+
@app.task
def add(x, y):
return x + y
@@ -357,8 +373,6 @@ def handle_sendemail_breakthrough(workoutid, useremail,
subject = "A breakthrough workout on rowsandall.com"
from_email = 'Rowsandall '
- htmly = get_template('breakthroughemail.html')
-
d = {
'first_name':userfirstname,
'siteurl':siteurl,
@@ -366,23 +380,13 @@ def handle_sendemail_breakthrough(workoutid, useremail,
'btvalues':tablevalues,
}
- html_content = htmly.render(d)
- text_content = textify(html_content)
-
- msg = EmailMultiAlternatives(subject, text_content, from_email, [useremail])
- msg.attach_alternative(html_content, "text/html")
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
- if not emailbounced:
- res = msg.send()
+ res = send_template_email(from_email,[useremail],
+ subject,'breakthroughemail.html',
+ d,**kwargs)
-
- # remove tcx file
return 1
# send email when a breakthrough workout is uploaded
@@ -403,12 +407,20 @@ def handle_sendemail_hard(workoutid, useremail,
if debug:
siteurl = SITE_URL_DEV
+ btvalues = pd.read_json(btvalues)
+ btvalues.sort_values('delta', axis=0, inplace=True)
+
+ tablevalues = [
+ {'delta': t.delta,
+ 'cpvalue': t.cpvalues,
+ 'pwr': t.pwr
+ } for t in btvalues.itertuples()
+ ]
+
# send email with attachment
subject = "That was a pretty hard workout on rowsandall.com"
from_email = 'Rowsandall '
- htmly = get_template('hardemail.html')
-
d = {
'first_name':userfirstname,
'siteurl':siteurl,
@@ -416,24 +428,10 @@ def handle_sendemail_hard(workoutid, useremail,
'btvalues':tablevalues,
}
- html_content = htmly.render(d)
- text_content = textify(html_content)
-
- msg = EmailMultiAlternatives(subject, text_content, from_email, [useremail])
- msg.attach_alternative(html_content, "text/html")
+ res = send_template_email(from_email,[useremail],
+ subject,'hardemail.html',d,**kwargs)
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
-
-
- # remove tcx file
return 1
@@ -511,27 +509,15 @@ def handle_sendemail_unrecognizedowner(useremail, userfirstname,
fullemail = useremail
subject = "Unrecognized file from Rowsandall.com"
- plaintext = get_template('csvemail.txt')
- htmly = get_template('csvemail.html')
+ htmly = get_template('unrecognizedemail.html')
- d = {'first_name':first_name}
+ d = {'first_name':userfirstname}
from_email = 'Rowsandall '
- html_content = htmly.render(d)
- text_content = textify(html_content)
-
- msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
- msg.attach_alternative(html_content, "text/html")
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
+ res = send_template_email(from_email,[fullemail],
+ subject,'csvemail.html',d,
+ **kwargs)
return 1
@@ -550,24 +536,11 @@ def handle_sendemailtcx(first_name, last_name, email, tcxfile,**kwargs):
from_email = 'Rowsandall '
- html_content = htmly.render(d)
- text_content = textify(html_content)
- msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
- msg.attach_alternative(html_content, "text/html")
+ res = send_template_email(from_email,[fullemail],
+ subject,'tcxemail.html',d,
+ attach_file=tcxfile,**kwargs)
- msg.attach_file(tcxfile)
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
-
- # remove tcx file
os.remove(tcxfile)
return 1
@@ -612,31 +585,11 @@ def handle_sendemailsummary(first_name, last_name, email, csvfile, **kwargs):
from_email = 'Rowsandall '
- html_content = htmly.render(d)
- text_content = textify(html_content)
-
- msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
- msg.attach_alternative(html_content, "text/html")
-
-
- if os.path.isfile(csvfile):
- msg.attach_file(csvfile)
- else:
- csvfile2 = csvfile
- with gzip.open(csvfile + '.gz', 'rb') as f_in, open(csvfile2, 'wb') as f_out:
- shutil.copyfileobj(f_in, f_out)
-
- msg.attach_file(csvfile2)
- os.remove(csvfile2)
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
+ res = send_template_email(from_email,[fullemail],
+ subject,'summarymail.html',d,
+ attach_file=csvfile,
+ **kwargs)
+
try:
os.remove(csvfile)
except:
@@ -654,37 +607,15 @@ def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
fullemail = first_name + " " + last_name + " " + "<" + email + ">"
subject = "File from Rowsandall.com"
- htmly = get_template('csvemail.html')
-
d = {'first_name':first_name}
from_email = 'Rowsandall '
- html_content = htmly.render(d)
- text_content = textify(html_content)
- msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
- msg.attach_alternative(html_content, "text/html")
-
- if os.path.isfile(csvfile):
- msg.attach_file(csvfile)
- else:
- csvfile2 = csvfile
- with gzip.open(csvfile + '.gz', 'rb') as f_in, open(csvfile2, 'wb') as f_out:
- shutil.copyfileobj(f_in, f_out)
-
- msg.attach_file(csvfile2)
- os.remove(csvfile2)
-
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
+ res = send_template_email(from_email,[fullemail],
+ subject,'csvemail.html',d,
+ attach_file=csvfile,**kwargs)
+
return 1
@@ -819,22 +750,9 @@ def handle_otwsetpower(self,f1, boattype, weightvalue,
'workoutid':workoutid,
}
- html_content = htmly.render(d)
- text_content = textify(html_content)
-
- msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
- msg.attach_alternative(html_content, "text/html")
-
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
-
+ res = handle_template_email(from_email,[fullemail],
+ subject,'otwpoweremail.html',d,
+ **kwargs)
return 1
@@ -1062,30 +980,20 @@ def handle_sendemail_invite(email, name, code, teamname, manager,
if debug:
siteurl = SITE_URL_DEV
- htmly = get_template('teaminviteemail.html')
-
d = {
'name':name,
- 'manage':manager,
+ 'manager':manager,
'code':code,
'teamname':teamname,
+ 'siteurl':siteurl
}
- html_content = htmly.render(d)
- text_content = textify(html_content)
+ from_email = 'Rowsandall '
+
+ res = send_template_email(from_email,[fullemail],
+ subject,'teaminviteemail.html',d,
+ **kwargs)
- msg = EmailMultiAlternatives(subject, text_content, from_email, [fullemail])
- msg.attach_alternative(html_content, "text/html")
-
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = msg.send()
-
-
return 1
@@ -1106,8 +1014,6 @@ def handle_sendemailnewresponse(first_name, last_name,
siteurl = SITE_URL_DEV
- htmly = get_template('teamresponseemail.html')
-
d = {
'first_name':first_name,
'commenter_first_name':commenter_first_name,
@@ -1133,30 +1039,32 @@ def handle_sendemailnewcomment(first_name,
comment, workoutname,
workoutid,
debug=False,**kwargs):
+
+
+
fullemail = first_name + ' ' + last_name + ' <' + email + '>'
+ from_email = 'Rowsandall '
subject = 'New comment on workout ' + workoutname
- message = 'Dear ' + first_name + ',\n\n'
- message += commenter_first_name + ' ' + commenter_last_name
- message += ' has written a new comment on your workout '
- message += workoutname + '\n\n'
- message += comment
- message += '\n\n'
- message += 'You can read the comment here:\n'
- message += 'https://rowsandall.com/rowers/workout/' + \
- str(workoutid) + '/comment'
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
+
+ d = {
+ 'first_name':first_name,
+ 'commenter_first_name':commenter_first_name,
+ 'commenter_last_name':commenter_last_name,
+ 'comment':comment,
+ 'workoutname':workoutname,
+ 'siteurl':siteurl,
+ 'workoutid':workoutid,
+ 'commentid':commentid
+ }
- if not emailbounced:
- res = email.send()
-
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teamresponseemail.html',d,**kwargs)
+
return 1
@@ -1166,29 +1074,23 @@ def handle_sendemail_request(email, name, code, teamname, requestor, id,
debug=False,**kwargs):
fullemail = name + ' <' + email + '>'
subject = 'Request to join team ' + teamname
- message = 'Dear ' + name + ',\n\n'
- message += requestor + ' is requesting admission to your team ' + teamname
- message += ' on rowsandall.com\n\n'
- message += 'Click the direct link to accept: \n'
- message += 'https://rowsandall.com/rowers/me/request/' + code + ' \n\n'
- message += 'Click the following link to reject the request: \n'
- message += 'https://rowsandall.com/rowers/me/request/' + str(id) + ' \n\n'
- message += 'You can find all pending requests on your team management page:\n'
- message += 'https://rowsandall.com/rowers/me/teams\n\n'
- message += "Best Regards, the Rowsandall Team"
+ from_email = 'Rowsandall '
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
+ d = {
+ 'requestor':requestor,
+ 'teamname':teamname,
+ 'siteurl':siteurl,
+ 'id':id,
+ 'first_name':name,
+ }
+
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teamrequestemail.html',d,**kwargs)
- if not emailbounced:
- res = email.send()
-
return 1
@@ -1198,25 +1100,20 @@ def handle_sendemail_request_accept(email, name, teamname, managername,
debug=False,**kwargs):
fullemail = name + ' <' + email + '>'
subject = 'Welcome to ' + teamname
- message = 'Dear ' + name + ',\n\n'
- message += managername
- message += ' has accepted your request to be part of the team '
- message += teamname
- message += '\n\n'
- message += "Best Regards, the Rowsandall Team"
+ from_email = 'Rowsandall '
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
+ d = {
+ 'first_name':name,
+ 'managername':managername,
+ 'teamname':teamname,
+ }
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teamwelcomeemail.html',d,**kwargs)
- if not emailbounced:
- res = email.send()
-
return 1
@@ -1226,26 +1123,19 @@ def handle_sendemail_request_reject(email, name, teamname, managername,
debug=False,**kwargs):
fullemail = name + ' <' + email + '>'
subject = 'Your application to ' + teamname + ' was rejected'
- message = 'Dear ' + name + ',\n\n'
- message += 'Unfortunately, '
- message += managername
- message += ' has rejected your request to be part of the team '
- message += teamname
- message += '\n\n'
- message += "Best Regards, the Rowsandall Team"
+ from_email = 'Rowsandall '
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
-
- if not emailbounced:
- res = email.send()
-
+ d = {
+ 'first_name':name,
+ 'managername':managername,
+ 'teamname':teamname,
+ }
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teamrejectemail.html',d,**kwargs)
return 1
@@ -1255,26 +1145,20 @@ def handle_sendemail_member_dropped(email, name, teamname, managername,
debug=False,**kwargs):
fullemail = name + ' <' + email + '>'
subject = 'You were removed from ' + teamname
- message = 'Dear ' + name + ',\n\n'
- message += 'Unfortunately, '
- message += managername
- message += ' has removed you from the team '
- message += teamname
- message += '\n\n'
- message += "Best Regards, the Rowsandall Team"
+ from_email = 'Rowsandall '
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
+ d = {
+ 'first_name':name,
+ 'managername':managername,
+ 'teamname':teamname,
+ }
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teamdropemail.html',d,**kwargs)
- if not emailbounced:
- res = email.send()
-
return 1
@@ -1282,29 +1166,23 @@ def handle_sendemail_member_dropped(email, name, teamname, managername,
@app.task
def handle_sendemail_team_removed(email, name, teamname, managername,
debug=False,**kwargs):
+
fullemail = name + ' <' + email + '>'
- subject = 'Team ' + teamname + ' was deleted'
- message = 'Dear ' + name + ',\n\n'
- message += managername
- message += ' has decided to delete the team '
- message += teamname
- message += '\n\n'
- message += 'The ' + teamname + ' tag has been removed from all your '
- message += 'workouts on rowsandall.com.\n\n'
- message += "Best Regards, the Rowsandall Team"
+ subject = 'You were removed from ' + teamname
+ from_email = 'Rowsandall '
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
+ d = {
+ 'first_name':name,
+ 'managername':managername,
+ 'teamname':teamname,
+ }
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teamremoveemail.html',d,**kwargs)
- if not emailbounced:
- res = email.send()
-
return 1
@@ -1314,26 +1192,20 @@ def handle_sendemail_invite_reject(email, name, teamname, managername,
debug=False,**kwargs):
fullemail = managername + ' <' + email + '>'
subject = 'Your invitation to ' + name + ' was rejected'
- message = 'Dear ' + managername + ',\n\n'
- message += 'Unfortunately, '
- message += name
- message += ' has rejected your invitation to be part of the team '
- message += teamname
- message += '\n\n'
- message += "Best Regards, the Rowsandall Team"
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ from_email = 'Rowsandall '
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
- if not emailbounced:
- res = email.send()
-
+ d = {
+ 'first_name':name,
+ 'managername':managername,
+ 'teamname':teamname,
+ }
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teaminviterejectemail.html',d,**kwargs)
return 1
@@ -1343,22 +1215,21 @@ def handle_sendemail_invite_accept(email, name, teamname, managername,
debug=False,**kwargs):
fullemail = managername + ' <' + email + '>'
subject = 'Your invitation to ' + name + ' was accepted'
- message = 'Dear ' + managername + ',\n\n'
- message += name + ' has accepted your invitation to be part of the team ' + teamname + '\n\n'
- message += "Best Regards, the Rowsandall Team"
- email = EmailMessage(subject, message,
- 'Rowsandall ',
- [fullemail])
+ from_email = 'Rowsandall '
- if 'emailbounced' in kwargs:
- emailbounced = kwargs['emailbounced']
- else:
- emailbounced = False
+ siteurl = SITE_URL
+ if debug:
+ siteurl = SITE_URL_DEV
+
+ d = {
+ 'first_name':name,
+ 'managername':managername,
+ 'teamname':teamname,
+ }
+ res = send_template_email(from_email,[fullemail],subject,
+ 'teaminviteacceptemail.html',d,**kwargs)
- if not emailbounced:
- res = email.send()
-
return 1
diff --git a/rowers/teams.py b/rowers/teams.py
index ba802fd2..7ddbfcad 100644
--- a/rowers/teams.py
+++ b/rowers/teams.py
@@ -393,6 +393,7 @@ def send_request_email(rekwest):
code = rekwest.code
teamname = rekwest.team.name
requestor = rekwest.user.first_name+' '+rekwest.user.last_name
+ print requestor,'aap'
res = myqueue(queuehigh,
handle_sendemail_request,
diff --git a/rowers/templates/teamdropemail.html b/rowers/templates/teamdropemail.html
new file mode 100644
index 00000000..d8255b15
--- /dev/null
+++ b/rowers/templates/teamdropemail.html
@@ -0,0 +1,16 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ first_name }},
+
+
+ Unfortunately,
+ {{ managername }} has removed you from
+ the team {{ teamname }}.
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/teaminviteacceptemail.html b/rowers/templates/teaminviteacceptemail.html
new file mode 100644
index 00000000..86754ee1
--- /dev/null
+++ b/rowers/templates/teaminviteacceptemail.html
@@ -0,0 +1,16 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ managername }},
+
+
+ {{ first_name }} has accepted your invitation to be
+ part of the team {{ teamname }}.
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/teaminviterejectemail.html b/rowers/templates/teaminviterejectemail.html
new file mode 100644
index 00000000..30ab689a
--- /dev/null
+++ b/rowers/templates/teaminviterejectemail.html
@@ -0,0 +1,17 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ managername }},
+
+
+ Unfortunately,
+ {{ first_name }} has rejected your invitation to be
+ part of the team {{ teamname }}.
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/teamrejectemail.html b/rowers/templates/teamrejectemail.html
new file mode 100644
index 00000000..34422898
--- /dev/null
+++ b/rowers/templates/teamrejectemail.html
@@ -0,0 +1,16 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ first_name }},
+
+
+ Unfortunately,
+ {{ managername }} has rejected your request to be part
+ of the team {{ teamname }}.
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/teamremoveemail.html b/rowers/templates/teamremoveemail.html
new file mode 100644
index 00000000..64c92d62
--- /dev/null
+++ b/rowers/templates/teamremoveemail.html
@@ -0,0 +1,19 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ first_name }},
+
+
+ {{ managername }} has decided to delete the team {{ teamname }}.
+
+
+ The {{ teamname }} tag has been removed from all your
+ workouts on rowsandall.com
+
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/teamrequestemail.html b/rowers/templates/teamrequestemail.html
new file mode 100644
index 00000000..dd436837
--- /dev/null
+++ b/rowers/templates/teamrequestemail.html
@@ -0,0 +1,29 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+Dear {{ first_name }},
+
+
+ {{ requestor }} is requesting access to your team {{ teamname }}
+ on rowsandall.com
+
+
+ Click the direct link to accept:
+
+ {{ siteurl }}/rowers/me/request/{{ code }}
+
+
+ Click the following link to reject the request:
+
+ {{ siteurl }}/rowers/me/request/{{ id }}
+
+
+ You can find all pending requests on your team management page:
+ {{ siteurl }}/rowers/me/teams
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/rowers/templates/teamresponseemail.html b/rowers/templates/teamresponseemail.html
index 3bb04e19..d0def71a 100644
--- a/rowers/templates/teamresponseemail.html
+++ b/rowers/templates/teamresponseemail.html
@@ -7,7 +7,7 @@
{{ commenter_first_name }} {{ commenter_last_name }} has written
- a new comment on your workout {{ workoutname }}
+ a new comment on workout {{ workoutname }}
{{ comment }}
diff --git a/rowers/templates/teamwelcomeemail.html b/rowers/templates/teamwelcomeemail.html
new file mode 100644
index 00000000..d255002c
--- /dev/null
+++ b/rowers/templates/teamwelcomeemail.html
@@ -0,0 +1,15 @@
+{% extends "emailbase.html" %}
+{% load staticfiles %}
+{% load rowerfilters %}
+
+{% block body %}
+
Dear {{ first_name }},
+
+
+ {{ managername }} has accepted your request to be part
+ of the team {{ teamname }}.
+
+
+ Best Regards, the Rowsandall Team
+
+{% endblock %}
diff --git a/temp.txt b/temp.txt
deleted file mode 100644
index 5fdc2a13..00000000
--- a/temp.txt
+++ /dev/null
@@ -1,5 +0,0 @@
----
-aap: noot
-mies: jet
-jet
-...
\ No newline at end of file
From a466990b5cdbbee9bc688524b4e1c97a78fe0c3b Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Mon, 19 Mar 2018 21:16:02 +0100
Subject: [PATCH 9/9] smaller logo in emails
---
rowers/templates/emailbase.html | 2 +-
static/img/logoemail.png | Bin 0 -> 5143 bytes
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 static/img/logoemail.png
diff --git a/rowers/templates/emailbase.html b/rowers/templates/emailbase.html
index 58c339eb..4f457f2b 100644
--- a/rowers/templates/emailbase.html
+++ b/rowers/templates/emailbase.html
@@ -5,7 +5,7 @@
{% load rowerfilters %}
-
+
{% block body %}
diff --git a/static/img/logoemail.png b/static/img/logoemail.png
new file mode 100644
index 0000000000000000000000000000000000000000..aacc2e3f43561ecc85985db7bf41c2420c6c270f
GIT binary patch
literal 5143
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