From e517767641be5e5c7005d012fab03680fd384b9e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 4 Mar 2018 16:18:58 +0100 Subject: [PATCH 01/11] team manager can edit user --- rowers/models.py | 2 +- rowers/templates/rower_form.html | 29 ++++++++++++++++++++---- rowers/urls.py | 1 + rowers/views.py | 39 ++++++++++++++++++-------------- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index e860e264..4082994f 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -598,7 +598,7 @@ class Rower(models.Model): verbose_name='Show Notes for Favorite Charts') def __str__(self): - return self.user.firstname+' '+self.user.last_name + return self.user.first_name+' '+self.user.last_name def __unicode__(self): return self.user.first_name+' '+self.user.last_name diff --git a/rowers/templates/rower_form.html b/rowers/templates/rower_form.html index fc9a574b..f863df84 100644 --- a/rowers/templates/rower_form.html +++ b/rowers/templates/rower_form.html @@ -1,11 +1,25 @@ {% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} {% block title %}Change Rower {% endblock %} {% block content %}
-

User Settings

-

Need help? Click to read the tutorial

+
+

User Settings for {{ rower.user.first_name }} {{ rower.user.last_name }}

+

Need help? Click to read the tutorial

+
+

Heart Rate Zones

@@ -88,7 +102,11 @@

+ {% if rower.user == user %} Password Change + {% else %} +   + {% endif %}

@@ -116,7 +134,7 @@ {% csrf_token %}
- {% if rower.rowerplan == 'basic' %} + {% if rower.rowerplan == 'basic' and rower.user == user %} Upgrade {% else %}   @@ -152,6 +170,7 @@
+{% if rower.user == user %}

@@ -161,7 +180,7 @@

- +

Favorite Charts

@@ -223,7 +242,7 @@ {% endif %}
- +{% endif %} diff --git a/rowers/urls.py b/rowers/urls.py index c1947531..9fb25e63 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -357,6 +357,7 @@ urlpatterns = [ url(r'^me/request/(\w+.*)/$',views.manager_requests_view), url(r'^me/request/$',views.manager_requests_view), url(r'^me/edit/$',views.rower_edit_view), + url(r'^rower/edit/(?P\d+)$',views.rower_edit_view), url(r'^me/edit/(.+.*)/$',views.rower_edit_view), url(r'^me/c2authorize/$',views.rower_c2_authorize), url(r'^me/revokeapp/(?P\d+)$',views.rower_revokeapp_view), diff --git a/rowers/views.py b/rowers/views.py index 8da3803d..c27cb5cf 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -10866,8 +10866,16 @@ def rower_exportsettings_view(request): # Page where user can set his details # Add email address to form so user can change his email address @login_required() -def rower_edit_view(request,message=""): - r = getrower(request.user) +def rower_edit_view(request,rowerid=0,message=""): + if rowerid==0: + r = getrower(request.user) + else: + r = Rower.objects.get(id=rowerid) + if not checkaccessuser(request.user,r): + raise PermissionDenied("You have no access to these user settings") + + rowerid = r.id + if request.method == 'POST' and "ut2" in request.POST: form = RowerForm(request.POST) if form.is_valid(): @@ -10881,7 +10889,6 @@ def rower_edit_view(request,message=""): an = cd['an'] rest = cd['rest'] try: - r = getrower(request.user) r.max = max(min(hrmax,250),10) r.ut2 = max(min(ut2,250),10) r.ut1 = max(min(ut1,250),10) @@ -10896,7 +10903,7 @@ def rower_edit_view(request,message=""): powerform = RowerPowerForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r) accountform = AccountRowerForm(instance=r) - userform = UserForm(instance=request.user) + userform = UserForm(instance=r.user) return render(request, 'rower_form.html', {'form':form, 'powerzonesform':powerzonesform, @@ -10916,7 +10923,7 @@ def rower_edit_view(request,message=""): #form = RowerForm(instance=r) powerform = RowerPowerForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r) - userform = UserForm(instance=request.user) + userform = UserForm(instance=r.user) accountform = AccountRowerForm(instance=r) return render(request, 'rower_form.html', {'form':form, @@ -10938,7 +10945,6 @@ def rower_edit_view(request,message=""): ftp = cd['ftp'] otwslack = cd['otwslack'] try: - r = getrower(request.user) powerfrac = 100*np.array([r.pw_ut2, r.pw_ut1, r.pw_at, @@ -10954,7 +10960,10 @@ def rower_edit_view(request,message=""): r.save() message = "FTP and/or OTW slack values changed." messages.info(request,message) - url = reverse(rower_edit_view) + url = reverse(rower_edit_view, + kwargs = { + 'rowerid':r.id, + }) response = HttpResponseRedirect(url) except Rower.DoesNotExist: message = "Funny. This user doesn't exist." @@ -10966,7 +10975,7 @@ def rower_edit_view(request,message=""): form = RowerForm(instance=r) #powerform = RowerPowerForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r) - userform = UserForm(instance=request.user) + userform = UserForm(instance=r.user) accountform = AccountRowerForm(instance=r) return render(request, 'rower_form.html', {'form':form, @@ -10996,7 +11005,6 @@ def rower_edit_view(request,message=""): anname = cd['anname'] powerzones = [ut3name,ut2name,ut1name,atname,trname,anname] try: - r = getrower(request.user) r.pw_ut2 = pw_ut2 r.pw_ut1 = pw_ut1 r.pw_at = pw_at @@ -11008,7 +11016,7 @@ def rower_edit_view(request,message=""): messages.info(request,successmessage) form = RowerForm(instance=r) accountform = AccountRowerForm(instance=r) - userform = UserForm(instance=request.user) + userform = UserForm(instance=r.user) powerform = RowerPowerForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r) return render(request, 'rower_form.html', @@ -11030,7 +11038,7 @@ def rower_edit_view(request,message=""): form = RowerForm(instance=r) powerform = RowerPowerForm(instance=r) accountform = AccountRowerForm(instance=r) - userform = UserForm(instance=request.user) + userform = UserForm(instance=r.user) #powerzonesform = RowerPowerZonesForm(instance=r) message = HttpResponse("invalid form") return render(request, 'rower_form.html', @@ -11044,7 +11052,7 @@ def rower_edit_view(request,message=""): }) elif request.method == 'POST' and "weightcategory" in request.POST: accountform = AccountRowerForm(request.POST) - userform = UserForm(request.POST,instance=request.user) + userform = UserForm(request.POST,instance=r.user) if accountform.is_valid() and userform.is_valid(): # process cd = accountform.cleaned_data @@ -11059,7 +11067,7 @@ def rower_edit_view(request,message=""): showfavoritechartnotes = cd['showfavoritechartnotes'] getemailnotifications = cd['getemailnotifications'] defaulttimezone=cd['defaulttimezone'] - u = request.user + u = r.user if len(first_name): u.first_name = first_name u.last_name = last_name @@ -11068,7 +11076,6 @@ def rower_edit_view(request,message=""): u.save() - r = getrower(u) r.defaulttimezone=defaulttimezone r.weightcategory = weightcategory r.getemailnotifications = getemailnotifications @@ -11110,13 +11117,11 @@ def rower_edit_view(request,message=""): else: try: - r = getrower(request.user) - form = RowerForm(instance=r) powerform = RowerPowerForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r) accountform = AccountRowerForm(instance=r) - userform = UserForm(instance=request.user) + userform = UserForm(instance=r.user) grants = AccessToken.objects.filter(user=request.user) return render(request, 'rower_form.html', { From 18b58aa780382a3ddb4b4d9d5254281b44cf9fb7 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 4 Mar 2018 16:26:43 +0100 Subject: [PATCH 02/11] add link to user edit on Team page --- rowers/templates/team.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/templates/team.html b/rowers/templates/team.html index d112ea92..cc53418c 100644 --- a/rowers/templates/team.html +++ b/rowers/templates/team.html @@ -54,7 +54,7 @@ {% for member in members %} - {{ member.user.first_name }} {{ member.user.last_name }} + {{ member.user.first_name }} {{ member.user.last_name }} {% if team.manager == user %} Drop {% else %} From ea78242949c3d331f4a3eb774d2f049d8292fb7f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 5 Mar 2018 12:51:07 +0100 Subject: [PATCH 03/11] added data download button on user settings --- rowers/dataprep.py | 62 ++++++++++++++++++++++++++ rowers/tasks.py | 31 +++++++++++++ rowers/templates/export_workouts.html | 33 ++++++++++++++ rowers/templates/rower_form.html | 10 ++++- rowers/urls.py | 1 + rowers/views.py | 29 ++++++++++++ rowsandall_workouts_{first}_{last}.csv | 58 ++++++++++++++++++++++++ 7 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 rowers/templates/export_workouts.html create mode 100644 rowsandall_workouts_{first}_{last}.csv diff --git a/rowers/dataprep.py b/rowers/dataprep.py index f6eaaba1..e2387939 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -65,6 +65,8 @@ queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('default') +from rowsandall_app.settings import SITE_URL + user = settings.DATABASES['default']['USER'] password = settings.DATABASES['default']['PASSWORD'] @@ -131,6 +133,66 @@ def get_latlon(id): return [pd.Series([]), pd.Series([])] +def workout_summary_to_df( + rower, + startdate=datetime.datetime(1970,1,1), + enddate=timezone.now()+timezone.timedelta(days=1)): + + ws = Workout.objects.filter(user=rower).order_by("startdatetime") + + types = [] + names = [] + startdatetimes = [] + timezones = [] + distances = [] + durations = [] + weightcategories = [] + weightvalues = [] + notes = [] + tcx_links = [] + csv_links = [] + rscores = [] + trimps = [] + + for w in ws: + types.append(w.workouttype) + names.append(w.name) + startdatetimes.append(w.startdatetime) + timezones.append(w.timezone) + distances.append(w.distance) + durations.append(w.duration) + weightcategories.append(w.weightcategory) + weightvalues.append(w.weightvalue) + notes.append(w.notes) + tcx_link = SITE_URL+'/rowers/workout/{id}/emailtcx'.format( + id=w.id + ) + tcx_links.append(tcx_link) + csv_link = SITE_URL+'/rowers/workout/{id}/emailcsv'.format( + id=w.id + ) + csv_links.append(csv_link) + trimps.append(workout_trimp(w)) + rscore = workout_rscore(w) + rscores.append(int(rscore[0])) + + df = pd.DataFrame({ + 'name':names, + 'date':startdatetimes, + 'timezone':timezones, + 'type':types, + 'distance (m)':distances, + 'duration ':durations, + 'weight category':weightcategories, + 'weight (kg)':weightvalues, + 'notes':notes, + 'Stroke Data TCX':tcx_links, + 'Stroke Data CSV':csv_links, + 'TRIMP Training Load':trimps, + 'TSS Training Load':rscores, + }) + + return df def get_workouts(ids, userid): goodids = [] diff --git a/rowers/tasks.py b/rowers/tasks.py index 31afcdec..aed1bf59 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -499,6 +499,37 @@ def handle_zip_file(emailfrom, subject, file,**kwargs): # Send email with CSV attachment +@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]) + + if os.path.isfile(csvfile): + email.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) + os.remove(csvfile2) + + res = email.send() + try: + os.remove(csvfile) + except: + pass + + return 1 + @app.task def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs): diff --git a/rowers/templates/export_workouts.html b/rowers/templates/export_workouts.html new file mode 100644 index 00000000..61bec7b6 --- /dev/null +++ b/rowers/templates/export_workouts.html @@ -0,0 +1,33 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Rowsandall Workouts Summary Export{% endblock %} + +{% block content %} +
+
+
+ + {{ form.as_table }} +
+ {% csrf_token %} +
+
+ +
+
+
+

+ With this form, you can export a summary table for all workouts within a selected date range. + The table will be sent to you as a CSV file which can be opened in excel. The table contains + links to download stroke data per workout. +

+

+ By setting the start date to your registration date or earlier and the end date to today, + you will receive all workout data we are storing for you. +

+
+
+ +{% endblock %} diff --git a/rowers/templates/rower_form.html b/rowers/templates/rower_form.html index f863df84..d903392d 100644 --- a/rowers/templates/rower_form.html +++ b/rowers/templates/rower_form.html @@ -212,7 +212,15 @@
-
+
+

+

GDPR - Data Protection

+ +

+
+
{% if grants %}

Applications

diff --git a/rowers/urls.py b/rowers/urls.py index 9fb25e63..a7faa506 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -121,6 +121,7 @@ urlpatterns = [ url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'), url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'), url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'), + url(r'^exportallworkouts$',views.workouts_summaries_email_view), url(r'^agegroupcp/(?P\d+)$',views.agegroupcpview), url(r'^agegroupcp/(?P\d+)/(?P\d+)$',views.agegroupcpview), url(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)$', diff --git a/rowers/views.py b/rowers/views.py index c27cb5cf..9f9b5128 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -118,6 +118,7 @@ from rowers.plannedsessions import * from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv from rowers.tasks import ( handle_sendemail_unrecognized,handle_sendemailnewcomment, + handle_sendemailsummary, handle_sendemailnewresponse, handle_updatedps, handle_updatecp,long_test_task,long_test_task2, handle_zip_file,handle_getagegrouprecords, @@ -1704,6 +1705,34 @@ def workout_gpxemail_view(request,id=0): return response +# Get Workout summary CSV file +@login_required() +def workouts_summaries_email_view(request): + r = getrower(request.user) + + if request.method == 'POST': + form = DateRangeForm(request.POST) + if form.is_valid(): + startdate = form.cleaned_data['startdate'] + enddate = form.cleaned_data['enddate'] + filename = 'rowsandall_workouts_{first}_{last}.csv' + df = dataprep.workout_summary_to_df(r,startdate=startdate,enddate=enddate) + df.to_csv(filename,encoding='utf-8') + res = myqueue(queuehigh,handle_sendemailsummary, + r.user.first_name, + r.user.last_name, + r.user.email, + filename) + messages.info(request,'The summary CSV file was sent to you per email') + else: + form = DateRangeForm() + + return render(request,"export_workouts.html", + { + 'form':form + }) + + # Get Workout CSV file and send it to user's email address @login_required() def workout_csvemail_view(request,id=0): diff --git a/rowsandall_workouts_{first}_{last}.csv b/rowsandall_workouts_{first}_{last}.csv new file mode 100644 index 00000000..09f39ee8 --- /dev/null +++ b/rowsandall_workouts_{first}_{last}.csv @@ -0,0 +1,58 @@ +,Stroke Data CSV,Stroke Data TCX,TRIMP Training Load,TSS Training Load,date,distance (m),duration ,name,notes,timezone,type,weight (kg),weight category +0,https://rowsandall.com/rowers/workout/2301/emailcsv,https://rowsandall.com/rowers/workout/2301/emailtcx,-3,0,2012-09-06 19:52:16+00:00,3851,00:30:00.400000,Imported data,,Europe/Prague,other,80.0,lwt +1,https://rowsandall.com/rowers/workout/2302/emailcsv,https://rowsandall.com/rowers/workout/2302/emailtcx,-8,0,2012-09-11 21:18:12+00:00,12017,01:15:29.300000,Imported data,,Europe/Prague,other,80.0,lwt +2,https://rowsandall.com/rowers/workout/2303/emailcsv,https://rowsandall.com/rowers/workout/2303/emailtcx,-8,0,2012-09-11 21:18:12+00:00,12017,01:15:29.300000,Imported data,,Europe/Prague,other,80.0,lwt +3,https://rowsandall.com/rowers/workout/2379/emailcsv,https://rowsandall.com/rowers/workout/2379/emailtcx,0,4,2017-10-27 16:14:42+00:00,742,00:02:25,Test Weba,,Europe/Prague,water,80.0,lwt +4,https://rowsandall.com/rowers/workout/2380/emailcsv,https://rowsandall.com/rowers/workout/2380/emailtcx,0,4,2017-10-27 16:14:42+00:00,742,00:02:25,Test Weba,,Europe/Belgrade,water,80.0,lwt +5,https://rowsandall.com/rowers/workout/2334/emailcsv,https://rowsandall.com/rowers/workout/2334/emailtcx,54,0,2018-01-17 06:12:54+00:00,0,00:57:00,Swim, ,Europe/Prague,other,80.0,lwt +6,https://rowsandall.com/rowers/workout/2333/emailcsv,https://rowsandall.com/rowers/workout/2333/emailtcx,11,10,2018-01-18 19:01:48+00:00,2007,00:08:23,WU & CD & rest, ,Europe/Prague,other,80.0,lwt +7,https://rowsandall.com/rowers/workout/2332/emailcsv,https://rowsandall.com/rowers/workout/2332/emailtcx,7,3,2018-01-18 19:40:43+00:00,1217,00:06:06,WU & CD & rest, ,Europe/Prague,other,80.0,lwt +8,https://rowsandall.com/rowers/workout/2331/emailcsv,https://rowsandall.com/rowers/workout/2331/emailtcx,12,6,2018-01-18 20:18:01+00:00,1997,00:09:06,WU & CD & rest, ,Europe/Prague,other,80.0,lwt +9,https://rowsandall.com/rowers/workout/2330/emailcsv,https://rowsandall.com/rowers/workout/2330/emailtcx,172,99,2018-01-20 13:22:33+00:00,19381,01:20:36,Zwift Ride/Row, ,Europe/Prague,other,80.0,lwt +10,https://rowsandall.com/rowers/workout/2329/emailcsv,https://rowsandall.com/rowers/workout/2329/emailtcx,36,0,2018-01-21 13:23:11+00:00,0,00:45:15,Afternoon Swim, ,Europe/Prague,other,80.0,lwt +11,https://rowsandall.com/rowers/workout/2328/emailcsv,https://rowsandall.com/rowers/workout/2328/emailtcx,0,0,2018-01-24 18:55:25+00:00,210,00:01:40,Steady state, ,Europe/Prague,other,80.0,lwt +12,https://rowsandall.com/rowers/workout/2327/emailcsv,https://rowsandall.com/rowers/workout/2327/emailtcx,11,11,2018-01-24 18:59:08+00:00,2110,00:08:39,Steady state, ,Europe/Prague,other,80.0,lwt +13,https://rowsandall.com/rowers/workout/2326/emailcsv,https://rowsandall.com/rowers/workout/2326/emailtcx,33,22,2018-01-24 19:08:06+00:00,6081,00:16:08,Steady state, ,Europe/Prague,other,80.0,lwt +14,https://rowsandall.com/rowers/workout/2325/emailcsv,https://rowsandall.com/rowers/workout/2325/emailtcx,85,44,2018-01-24 19:24:20+00:00,14426,00:35:00,Steady state, ,Europe/Prague,other,80.0,lwt +15,https://rowsandall.com/rowers/workout/2324/emailcsv,https://rowsandall.com/rowers/workout/2324/emailtcx,143,83,2018-01-27 13:23:28+00:00,14981,01:01:36,You’ve unlocked colored socks, ,Europe/Prague,other,80.0,lwt +16,https://rowsandall.com/rowers/workout/2323/emailcsv,https://rowsandall.com/rowers/workout/2323/emailtcx,85,207,2018-01-28 14:43:45+00:00,6519,00:47:35,Afternoon Run, ,Europe/Prague,other,80.0,lwt +17,https://rowsandall.com/rowers/workout/2322/emailcsv,https://rowsandall.com/rowers/workout/2322/emailtcx,121,712,2018-01-29 08:32:25+00:00,12622,02:11:55,Morning Run, ,Europe/Prague,other,80.0,lwt +18,https://rowsandall.com/rowers/workout/2321/emailcsv,https://rowsandall.com/rowers/workout/2321/emailtcx,99,216,2018-01-29 14:02:59+00:00,8801,01:20:06,Afternoon Run, ,Europe/Prague,other,80.0,lwt +19,https://rowsandall.com/rowers/workout/2320/emailcsv,https://rowsandall.com/rowers/workout/2320/emailtcx,224,663,2018-01-30 08:07:27+00:00,24640,04:24:41,Morning Run, ,Europe/Prague,other,80.0,lwt +20,https://rowsandall.com/rowers/workout/2319/emailcsv,https://rowsandall.com/rowers/workout/2319/emailtcx,142,862,2018-01-31 08:34:52+00:00,13659,01:35:37,Morning Run, ,Europe/Prague,other,80.0,lwt +21,https://rowsandall.com/rowers/workout/2318/emailcsv,https://rowsandall.com/rowers/workout/2318/emailtcx,101,244,2018-01-31 14:31:30+00:00,8948,01:11:07,Afternoon Run, ,Europe/Prague,other,80.0,lwt +22,https://rowsandall.com/rowers/workout/2317/emailcsv,https://rowsandall.com/rowers/workout/2317/emailtcx,160,216,2018-02-01 08:46:14+00:00,10501,01:32:54,Morning Run, ,Europe/Prague,other,80.0,lwt +23,https://rowsandall.com/rowers/workout/2295/emailcsv,https://rowsandall.com/rowers/workout/2295/emailtcx,88,75,2018-02-03 14:20:20+00:00,12181,00:51:43,Zwift in the rain, ,Europe/Prague,water,80.0,lwt +24,https://rowsandall.com/rowers/workout/2316/emailcsv,https://rowsandall.com/rowers/workout/2316/emailtcx,-5,396,2018-02-04 12:56:40+00:00,9082,00:51:50,Afternoon Run, ,Europe/Prague,other,80.0,lwt +25,https://rowsandall.com/rowers/workout/2315/emailcsv,https://rowsandall.com/rowers/workout/2315/emailtcx,60,40,2018-02-06 17:54:04+00:00,7210,00:31:05,Aborted 2x(1min+2min+3min+4min+3min+2min+1min)/4min 26/24/22/20/22/24/26spm, ,Europe/Prague,other,80.0,lwt +26,https://rowsandall.com/rowers/workout/2314/emailcsv,https://rowsandall.com/rowers/workout/2314/emailtcx,56,0,2018-02-08 06:08:23+00:00,0,00:51:58,Morning Activity, ,Europe/Prague,other,80.0,lwt +27,https://rowsandall.com/rowers/workout/2313/emailcsv,https://rowsandall.com/rowers/workout/2313/emailtcx,16,15,2018-02-10 09:10:16+00:00,3040,00:12:44,WU and cd, ,Europe/Prague,other,80.0,lwt +28,https://rowsandall.com/rowers/workout/2312/emailcsv,https://rowsandall.com/rowers/workout/2312/emailtcx,85,66,2018-02-10 09:23:53+00:00,9305,00:40:48,7x(1min+1min+1min)/3min @ 24/26/28spm, ,Europe/Prague,other,80.0,lwt +29,https://rowsandall.com/rowers/workout/2311/emailcsv,https://rowsandall.com/rowers/workout/2311/emailtcx,14,6,2018-02-10 10:06:03+00:00,2009,00:08:59,WU and cd, ,Europe/Prague,other,80.0,lwt +30,https://rowsandall.com/rowers/workout/2310/emailcsv,https://rowsandall.com/rowers/workout/2310/emailtcx,133,68,2018-02-11 16:57:33+00:00,14450,01:00:00,Steady State (fused),,Europe/Prague,other,80.0,lwt +31,https://rowsandall.com/rowers/workout/2335/emailcsv,https://rowsandall.com/rowers/workout/2335/emailtcx,24,20,2018-02-12 05:02:06+00:00,3872,00:27:12.200000,SpdCoach 1,,America/Los_Angeles,water,80.0,lwt +32,https://rowsandall.com/rowers/workout/2338/emailcsv,https://rowsandall.com/rowers/workout/2338/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt +33,https://rowsandall.com/rowers/workout/2339/emailcsv,https://rowsandall.com/rowers/workout/2339/emailtcx,403,1502,2018-02-12 05:02:06+00:00,7522,09:41:48.200000,Joined Workout,,America/Los_Angeles,water,80.0,lwt +34,https://rowsandall.com/rowers/workout/2341/emailcsv,https://rowsandall.com/rowers/workout/2341/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt +35,https://rowsandall.com/rowers/workout/2342/emailcsv,https://rowsandall.com/rowers/workout/2342/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt +36,https://rowsandall.com/rowers/workout/2343/emailcsv,https://rowsandall.com/rowers/workout/2343/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt +37,https://rowsandall.com/rowers/workout/2344/emailcsv,https://rowsandall.com/rowers/workout/2344/emailtcx,24,20,2018-02-12 14:02:06+00:00,3872,00:27:12.200000,SpdCoach Sessions,imported through email,America/Los_Angeles,water,80.0,lwt +38,https://rowsandall.com/rowers/workout/2347/emailcsv,https://rowsandall.com/rowers/workout/2347/emailtcx,89,73,2018-02-12 14:02:06+00:00,9336,00:58:35,Joined Workout,imported through email,America/Los_Angeles,water,80.0,lwt +39,https://rowsandall.com/rowers/workout/2336/emailcsv,https://rowsandall.com/rowers/workout/2336/emailtcx,37,32,2018-02-12 14:29:01+00:00,3650,00:14:53.200000,SpdCoach 2," +Summary for your location at 2018-02-12T14:55:00Z: Temperature 9.0C/48.2F. Wind: 2.5 m/s (5.0 kt). Wind Bearing: 280.0 degrees",America/Los_Angeles,water,80.0,lwt +40,https://rowsandall.com/rowers/workout/2340/emailcsv,https://rowsandall.com/rowers/workout/2340/emailtcx,65,49,2018-02-12 14:29:01+00:00,5464,00:31:40,Joined Workout,,America/Los_Angeles,water,80.0,lwt +41,https://rowsandall.com/rowers/workout/2345/emailcsv,https://rowsandall.com/rowers/workout/2345/emailtcx,37,32,2018-02-12 14:29:01+00:00,3650,00:14:53.200000,SpdCoach Sessions (2),imported through email,America/Los_Angeles,water,80.0,lwt +42,https://rowsandall.com/rowers/workout/2337/emailcsv,https://rowsandall.com/rowers/workout/2337/emailtcx,11,5,2018-02-12 14:49:02+00:00,1814,00:11:39,SpdCoach 3,,America/Los_Angeles,water,80.0,lwt +43,https://rowsandall.com/rowers/workout/2346/emailcsv,https://rowsandall.com/rowers/workout/2346/emailtcx,11,5,2018-02-12 14:49:02+00:00,1814,00:11:39,SpdCoach Sessions (3),imported through email,America/Los_Angeles,water,80.0,lwt +44,https://rowsandall.com/rowers/workout/2309/emailcsv,https://rowsandall.com/rowers/workout/2309/emailtcx,8,9,2018-02-12 18:39:45+00:00,2014,00:08:33,WU and CD, ,Europe/Prague,other,80.0,lwt +45,https://rowsandall.com/rowers/workout/2308/emailcsv,https://rowsandall.com/rowers/workout/2308/emailtcx,90,73,2018-02-12 18:49:04+00:00,10562,00:46:10,4x2km,,Europe/Prague,rower,80.0,lwt +46,https://rowsandall.com/rowers/workout/2307/emailcsv,https://rowsandall.com/rowers/workout/2307/emailtcx,9,5,2018-02-12 19:36:46+00:00,1998,00:09:35,WU and CD, ,Europe/Prague,other,80.0,lwt +47,https://rowsandall.com/rowers/workout/2306/emailcsv,https://rowsandall.com/rowers/workout/2306/emailtcx,48,0,2018-02-14 05:55:22+00:00,0,00:58:33,Morning Activity, ,Europe/Prague,other,80.0,lwt +48,https://rowsandall.com/rowers/workout/2378/emailcsv,https://rowsandall.com/rowers/workout/2378/emailtcx,40,6,2018-02-17 08:17:50+00:00,4386,00:27:08,Morning Run, ,Europe/Prague,other,80.0,lwt +49,https://rowsandall.com/rowers/workout/2381/emailcsv,https://rowsandall.com/rowers/workout/2381/emailtcx,79,48,2018-02-21 06:26:01+00:00,6119,00:27:20.200000,3x2km Racice,,Europe/Prague,water,80.0,lwt +50,https://rowsandall.com/rowers/workout/2384/emailcsv,https://rowsandall.com/rowers/workout/2384/emailtcx,107,66,2018-02-22 09:32:01+00:00,8000,00:35:08.500000,8k trial,,Europe/Prague,water,80.0,lwt +51,https://rowsandall.com/rowers/workout/2383/emailcsv,https://rowsandall.com/rowers/workout/2383/emailtcx,-2,50,2018-02-23 15:17:03+00:00,5918,00:25:10.700000,Horin,,Europe/Prague,water,80.0,lwt +52,https://rowsandall.com/rowers/workout/2385/emailcsv,https://rowsandall.com/rowers/workout/2385/emailtcx,169,0,2018-02-24 11:58:09+00:00,17045,01:35:59,Washington,,America/New_York,water,80.0,lwt +53,https://rowsandall.com/rowers/workout/2386/emailcsv,https://rowsandall.com/rowers/workout/2386/emailtcx,-2,59,2018-02-25 12:24:59+00:00,6182,00:26:24.400000,9x500m/70sec,,Europe/Prague,rower,80.0,lwt +54,https://rowsandall.com/rowers/workout/2387/emailcsv,https://rowsandall.com/rowers/workout/2387/emailtcx,11,7,2018-02-27 13:19:44+00:00,2206,00:10:00,StatsError,,Europe/Prague,rower,80.0,lwt +55,https://rowsandall.com/rowers/workout/2388/emailcsv,https://rowsandall.com/rowers/workout/2388/emailtcx,87,410,2018-03-04 12:16:34+00:00,17817,01:29:08,Afternoon Activity, ,Europe/Prague,other,80.0,lwt From 4219725471cf577e489c3a0df6e5019ad418b067 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Mar 2018 13:12:47 +0100 Subject: [PATCH 04/11] removed some print statements --- rowers/dataprep.py | 4 ---- rowers/views.py | 4 ---- 2 files changed, 8 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index e2387939..70638f33 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1572,13 +1572,9 @@ def repair_data(verbose=False): res = data.to_csv(w.csvfilename + '.gz', index_label='index', compression='gzip') - print 'adding csv file' else: - print w.id, ' No stroke records anywhere' w.delete() except: - print 'failed' - print str(sys.exc_info()[0]) pass # A wrapper around the rowingdata class, with some error catching diff --git a/rowers/views.py b/rowers/views.py index 9f9b5128..707a7e27 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1896,7 +1896,6 @@ def workout_strava_upload_view(request,id=0): try: os.remove(tcxfile) except WindowsError: - print tcxfile pass url = "/rowers/workout/"+str(w.id)+"/edit" @@ -3749,7 +3748,6 @@ def rankings_view2(request,theuser=0, sex = r.sex, weightcategory = r.weightcategory) - print len(agerecords),'aap' if len(agerecords) == 0: recalc = True wcpower = [] @@ -8163,8 +8161,6 @@ def workout_flexchart3_view(request,*args,**kwargs): yparam1 = yparam1.replace('/','_slsh_') yparam2 = yparam2.replace('/','_slsh_') - print xparam,yparam1,yparam2,'aap' - try: extrametrics.pop('originalvelo') except KeyError: From e353b1cf1f9c6ecb9665a96d0992da3a792d5781 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Mar 2018 13:14:41 +0100 Subject: [PATCH 05/11] typo --- rowers/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/models.py b/rowers/models.py index 4082994f..73600946 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -173,6 +173,7 @@ def update_records(url=c2url): df['Distance'] = df['Event'] df['Duration'] = 0 + print row.Duration for nr,row in df.iterrows(): if 'm' in row['Record']: df.ix[nr,'Distance'] = row['Record'][:-1] @@ -185,7 +186,6 @@ def update_records(url=c2url): tobj = datetime.datetime.strptime(row['Record'],'%H:%M:%S.%f') df.ix[nr,'Duration'] = 3600.*tobj.hour+60.*tobj.minute+tobj.second+tobj.microsecond/1.e6 - print row.Duration for nr,row in df.iterrows(): try: weightcategory = row.Weight.lower() From d80e7d8f04f3a7e4f78c92c9bee001bbaa1ab59d Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Mar 2018 20:20:42 +0100 Subject: [PATCH 06/11] gdpr user delete and user deactivate functions --- rowers/models.py | 13 +++++ rowers/templates/registration_form.html | 9 +++ rowers/templates/rower_form.html | 14 ++++- rowers/templates/userprofile_deactivate.html | 22 ++++++++ rowers/templates/userprofile_delete.html | 22 ++++++++ rowers/urls.py | 2 + rowers/views.py | 51 ++++++++++++++++- rowsandall_workouts_{first}_{last}.csv | 58 -------------------- 8 files changed, 130 insertions(+), 61 deletions(-) create mode 100644 rowers/templates/userprofile_deactivate.html create mode 100644 rowers/templates/userprofile_delete.html delete mode 100644 rowsandall_workouts_{first}_{last}.csv diff --git a/rowers/models.py b/rowers/models.py index 73600946..99117e5a 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -606,6 +606,19 @@ class Rower(models.Model): def clean_email(self): return self.user.email.lower() +class DeactivateUserForm(forms.ModelForm): + class Meta: + model = User + fields = ['is_active'] + +class DeleteUserForm(forms.ModelForm): + delete_user = forms.BooleanField(initial=False, + label='Remove my account and all data') + + class Meta: + model = User + fields = [] + @receiver(models.signals.post_save,sender=Rower) def auto_delete_teams_on_change(sender, instance, **kwargs): if instance.rowerplan != 'coach': diff --git a/rowers/templates/registration_form.html b/rowers/templates/registration_form.html index 82f51fce..0e337e78 100644 --- a/rowers/templates/registration_form.html +++ b/rowers/templates/registration_form.html @@ -31,6 +31,15 @@

To use rowsandall, you need to register and agree with the Terms of Service.

Registration is free.

+ +

Some of our advanced services only work if you give us your + (approximate) birth date, sex and weight category, with 72.5 kg the + bounday between heavies and lighties for men, and 59 kg for women. +

+ +

Also, we are restricting access to the site to 16 years and older + because of EU data protection regulations.

+
{% endblock content %} diff --git a/rowers/templates/rower_form.html b/rowers/templates/rower_form.html index d903392d..91a0637c 100644 --- a/rowers/templates/rower_form.html +++ b/rowers/templates/rower_form.html @@ -216,7 +216,19 @@

GDPR - Data Protection

+ +

diff --git a/rowers/templates/userprofile_deactivate.html b/rowers/templates/userprofile_deactivate.html new file mode 100644 index 00000000..7f4b2c15 --- /dev/null +++ b/rowers/templates/userprofile_deactivate.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Deactivate your account{% endblock %} + +{% block content %} +
+

Deactivate your account

+
+

Account deactivation is reversible. After you logout, you will not be + able to access your data any more, but the site admin can restore your + account.

+
{% csrf_token %} + {{ user_form.as_p}} + {% csrf_token %} + +
+ +
+ +{% endblock %} diff --git a/rowers/templates/userprofile_delete.html b/rowers/templates/userprofile_delete.html new file mode 100644 index 00000000..04331c3f --- /dev/null +++ b/rowers/templates/userprofile_delete.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Delete your account{% endblock %} + +{% block content %} +
+

Delete your account

+
+

Warning: This will remove your account and all your data. + You will not be able to recover from this action. We cannot restore + your account

+
{% csrf_token %} + {{ user_form.as_p}} + {% csrf_token %} + +
+ +
+ +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index a7faa506..c642adc2 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -336,6 +336,8 @@ urlpatterns = [ url(r'^user-multiflex/$',views.multiflex_view), url(r'^user-multiflex$',views.multiflex_view), url(r'^user-multiflex-data$',views.multiflex_data), + url(r'^me/deactivate$',views.deactivate_user), + url(r'^me/delete$',views.remove_user), url(r'^me/teams/$',views.rower_teams_view), url(r'^me/calcdps/$',views.rower_calcdps_view), url(r'^me/exportsettings/$',views.rower_exportsettings_view), diff --git a/rowers/views.py b/rowers/views.py index 707a7e27..1857f2a0 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -12,6 +12,7 @@ import yaml from PIL import Image from numbers import Number from django.views.generic.base import TemplateView +from django.contrib.auth import views as auth_views from django.db.models import Q from django import template from django.db import IntegrityError, transaction @@ -54,7 +55,7 @@ from rowers.forms import ( ) from rowers.models import ( Workout, User, Rower, WorkoutForm,FavoriteChart, - PlannedSession + PlannedSession, DeactivateUserForm,DeleteUserForm ) from rowers.models import ( RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, @@ -573,6 +574,48 @@ def get_thumbnails(request,id): return JSONResponse(charts) +@login_required() +def deactivate_user(request): + pk = request.user.id + user = User.objects.get(pk=pk) + user_form = DeactivateUserForm(instance=user) + if request.user.is_authenticated() and request.user.id == user.id: + if request.method == "POST": + user_form = DeactivateUserForm(request.POST, instance=user) + if user_form.is_valid(): + deactivate_user = user_form.save(commit=False) + user.is_active = False + deactivate_user.save() + url = reverse(auth_views.logout_then_login) + return HttpResponseRedirect(url) + return render(request, "userprofile_deactivate.html", { + "user_form": user_form, + }) + else: + raise PermissionDenied + +@login_required() +def remove_user(request): + pk = request.user.id + user = User.objects.get(pk=pk) + user_form = DeleteUserForm(instance=user) + if request.user.is_authenticated() and request.user.id == user.id: + if request.method == "POST": + user_form = DeleteUserForm(request.POST,instance=user) + if user_form.is_valid(): + cd = user_form.cleaned_data + print cd + if cd['delete_user']: + user.delete() + url = reverse(auth_views.logout_then_login) + return HttpResponseRedirect(url) + return render(request, "userprofile_delete.html", { + "user_form": user_form, + }) + else: + raise PermissionDenied + + @login_required() def get_testscript(request,id): row = get_workout_permitted(request.user,id) @@ -1715,7 +1758,11 @@ def workouts_summaries_email_view(request): if form.is_valid(): startdate = form.cleaned_data['startdate'] enddate = form.cleaned_data['enddate'] - filename = 'rowsandall_workouts_{first}_{last}.csv' + filename = 'rowsandall_workouts_{first}_{last}.csv'.format( + first=startdate, + last=enddate + ) + print filename df = dataprep.workout_summary_to_df(r,startdate=startdate,enddate=enddate) df.to_csv(filename,encoding='utf-8') res = myqueue(queuehigh,handle_sendemailsummary, diff --git a/rowsandall_workouts_{first}_{last}.csv b/rowsandall_workouts_{first}_{last}.csv deleted file mode 100644 index 09f39ee8..00000000 --- a/rowsandall_workouts_{first}_{last}.csv +++ /dev/null @@ -1,58 +0,0 @@ -,Stroke Data CSV,Stroke Data TCX,TRIMP Training Load,TSS Training Load,date,distance (m),duration ,name,notes,timezone,type,weight (kg),weight category -0,https://rowsandall.com/rowers/workout/2301/emailcsv,https://rowsandall.com/rowers/workout/2301/emailtcx,-3,0,2012-09-06 19:52:16+00:00,3851,00:30:00.400000,Imported data,,Europe/Prague,other,80.0,lwt -1,https://rowsandall.com/rowers/workout/2302/emailcsv,https://rowsandall.com/rowers/workout/2302/emailtcx,-8,0,2012-09-11 21:18:12+00:00,12017,01:15:29.300000,Imported data,,Europe/Prague,other,80.0,lwt -2,https://rowsandall.com/rowers/workout/2303/emailcsv,https://rowsandall.com/rowers/workout/2303/emailtcx,-8,0,2012-09-11 21:18:12+00:00,12017,01:15:29.300000,Imported data,,Europe/Prague,other,80.0,lwt -3,https://rowsandall.com/rowers/workout/2379/emailcsv,https://rowsandall.com/rowers/workout/2379/emailtcx,0,4,2017-10-27 16:14:42+00:00,742,00:02:25,Test Weba,,Europe/Prague,water,80.0,lwt -4,https://rowsandall.com/rowers/workout/2380/emailcsv,https://rowsandall.com/rowers/workout/2380/emailtcx,0,4,2017-10-27 16:14:42+00:00,742,00:02:25,Test Weba,,Europe/Belgrade,water,80.0,lwt -5,https://rowsandall.com/rowers/workout/2334/emailcsv,https://rowsandall.com/rowers/workout/2334/emailtcx,54,0,2018-01-17 06:12:54+00:00,0,00:57:00,Swim, ,Europe/Prague,other,80.0,lwt -6,https://rowsandall.com/rowers/workout/2333/emailcsv,https://rowsandall.com/rowers/workout/2333/emailtcx,11,10,2018-01-18 19:01:48+00:00,2007,00:08:23,WU & CD & rest, ,Europe/Prague,other,80.0,lwt -7,https://rowsandall.com/rowers/workout/2332/emailcsv,https://rowsandall.com/rowers/workout/2332/emailtcx,7,3,2018-01-18 19:40:43+00:00,1217,00:06:06,WU & CD & rest, ,Europe/Prague,other,80.0,lwt -8,https://rowsandall.com/rowers/workout/2331/emailcsv,https://rowsandall.com/rowers/workout/2331/emailtcx,12,6,2018-01-18 20:18:01+00:00,1997,00:09:06,WU & CD & rest, ,Europe/Prague,other,80.0,lwt -9,https://rowsandall.com/rowers/workout/2330/emailcsv,https://rowsandall.com/rowers/workout/2330/emailtcx,172,99,2018-01-20 13:22:33+00:00,19381,01:20:36,Zwift Ride/Row, ,Europe/Prague,other,80.0,lwt -10,https://rowsandall.com/rowers/workout/2329/emailcsv,https://rowsandall.com/rowers/workout/2329/emailtcx,36,0,2018-01-21 13:23:11+00:00,0,00:45:15,Afternoon Swim, ,Europe/Prague,other,80.0,lwt -11,https://rowsandall.com/rowers/workout/2328/emailcsv,https://rowsandall.com/rowers/workout/2328/emailtcx,0,0,2018-01-24 18:55:25+00:00,210,00:01:40,Steady state, ,Europe/Prague,other,80.0,lwt -12,https://rowsandall.com/rowers/workout/2327/emailcsv,https://rowsandall.com/rowers/workout/2327/emailtcx,11,11,2018-01-24 18:59:08+00:00,2110,00:08:39,Steady state, ,Europe/Prague,other,80.0,lwt -13,https://rowsandall.com/rowers/workout/2326/emailcsv,https://rowsandall.com/rowers/workout/2326/emailtcx,33,22,2018-01-24 19:08:06+00:00,6081,00:16:08,Steady state, ,Europe/Prague,other,80.0,lwt -14,https://rowsandall.com/rowers/workout/2325/emailcsv,https://rowsandall.com/rowers/workout/2325/emailtcx,85,44,2018-01-24 19:24:20+00:00,14426,00:35:00,Steady state, ,Europe/Prague,other,80.0,lwt -15,https://rowsandall.com/rowers/workout/2324/emailcsv,https://rowsandall.com/rowers/workout/2324/emailtcx,143,83,2018-01-27 13:23:28+00:00,14981,01:01:36,You’ve unlocked colored socks, ,Europe/Prague,other,80.0,lwt -16,https://rowsandall.com/rowers/workout/2323/emailcsv,https://rowsandall.com/rowers/workout/2323/emailtcx,85,207,2018-01-28 14:43:45+00:00,6519,00:47:35,Afternoon Run, ,Europe/Prague,other,80.0,lwt -17,https://rowsandall.com/rowers/workout/2322/emailcsv,https://rowsandall.com/rowers/workout/2322/emailtcx,121,712,2018-01-29 08:32:25+00:00,12622,02:11:55,Morning Run, ,Europe/Prague,other,80.0,lwt -18,https://rowsandall.com/rowers/workout/2321/emailcsv,https://rowsandall.com/rowers/workout/2321/emailtcx,99,216,2018-01-29 14:02:59+00:00,8801,01:20:06,Afternoon Run, ,Europe/Prague,other,80.0,lwt -19,https://rowsandall.com/rowers/workout/2320/emailcsv,https://rowsandall.com/rowers/workout/2320/emailtcx,224,663,2018-01-30 08:07:27+00:00,24640,04:24:41,Morning Run, ,Europe/Prague,other,80.0,lwt -20,https://rowsandall.com/rowers/workout/2319/emailcsv,https://rowsandall.com/rowers/workout/2319/emailtcx,142,862,2018-01-31 08:34:52+00:00,13659,01:35:37,Morning Run, ,Europe/Prague,other,80.0,lwt -21,https://rowsandall.com/rowers/workout/2318/emailcsv,https://rowsandall.com/rowers/workout/2318/emailtcx,101,244,2018-01-31 14:31:30+00:00,8948,01:11:07,Afternoon Run, ,Europe/Prague,other,80.0,lwt -22,https://rowsandall.com/rowers/workout/2317/emailcsv,https://rowsandall.com/rowers/workout/2317/emailtcx,160,216,2018-02-01 08:46:14+00:00,10501,01:32:54,Morning Run, ,Europe/Prague,other,80.0,lwt -23,https://rowsandall.com/rowers/workout/2295/emailcsv,https://rowsandall.com/rowers/workout/2295/emailtcx,88,75,2018-02-03 14:20:20+00:00,12181,00:51:43,Zwift in the rain, ,Europe/Prague,water,80.0,lwt -24,https://rowsandall.com/rowers/workout/2316/emailcsv,https://rowsandall.com/rowers/workout/2316/emailtcx,-5,396,2018-02-04 12:56:40+00:00,9082,00:51:50,Afternoon Run, ,Europe/Prague,other,80.0,lwt -25,https://rowsandall.com/rowers/workout/2315/emailcsv,https://rowsandall.com/rowers/workout/2315/emailtcx,60,40,2018-02-06 17:54:04+00:00,7210,00:31:05,Aborted 2x(1min+2min+3min+4min+3min+2min+1min)/4min 26/24/22/20/22/24/26spm, ,Europe/Prague,other,80.0,lwt -26,https://rowsandall.com/rowers/workout/2314/emailcsv,https://rowsandall.com/rowers/workout/2314/emailtcx,56,0,2018-02-08 06:08:23+00:00,0,00:51:58,Morning Activity, ,Europe/Prague,other,80.0,lwt -27,https://rowsandall.com/rowers/workout/2313/emailcsv,https://rowsandall.com/rowers/workout/2313/emailtcx,16,15,2018-02-10 09:10:16+00:00,3040,00:12:44,WU and cd, ,Europe/Prague,other,80.0,lwt -28,https://rowsandall.com/rowers/workout/2312/emailcsv,https://rowsandall.com/rowers/workout/2312/emailtcx,85,66,2018-02-10 09:23:53+00:00,9305,00:40:48,7x(1min+1min+1min)/3min @ 24/26/28spm, ,Europe/Prague,other,80.0,lwt -29,https://rowsandall.com/rowers/workout/2311/emailcsv,https://rowsandall.com/rowers/workout/2311/emailtcx,14,6,2018-02-10 10:06:03+00:00,2009,00:08:59,WU and cd, ,Europe/Prague,other,80.0,lwt -30,https://rowsandall.com/rowers/workout/2310/emailcsv,https://rowsandall.com/rowers/workout/2310/emailtcx,133,68,2018-02-11 16:57:33+00:00,14450,01:00:00,Steady State (fused),,Europe/Prague,other,80.0,lwt -31,https://rowsandall.com/rowers/workout/2335/emailcsv,https://rowsandall.com/rowers/workout/2335/emailtcx,24,20,2018-02-12 05:02:06+00:00,3872,00:27:12.200000,SpdCoach 1,,America/Los_Angeles,water,80.0,lwt -32,https://rowsandall.com/rowers/workout/2338/emailcsv,https://rowsandall.com/rowers/workout/2338/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt -33,https://rowsandall.com/rowers/workout/2339/emailcsv,https://rowsandall.com/rowers/workout/2339/emailtcx,403,1502,2018-02-12 05:02:06+00:00,7522,09:41:48.200000,Joined Workout,,America/Los_Angeles,water,80.0,lwt -34,https://rowsandall.com/rowers/workout/2341/emailcsv,https://rowsandall.com/rowers/workout/2341/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt -35,https://rowsandall.com/rowers/workout/2342/emailcsv,https://rowsandall.com/rowers/workout/2342/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt -36,https://rowsandall.com/rowers/workout/2343/emailcsv,https://rowsandall.com/rowers/workout/2343/emailtcx,431,1524,2018-02-12 05:02:06+00:00,9336,09:58:35,Joined Workout,,America/Los_Angeles,water,80.0,lwt -37,https://rowsandall.com/rowers/workout/2344/emailcsv,https://rowsandall.com/rowers/workout/2344/emailtcx,24,20,2018-02-12 14:02:06+00:00,3872,00:27:12.200000,SpdCoach Sessions,imported through email,America/Los_Angeles,water,80.0,lwt -38,https://rowsandall.com/rowers/workout/2347/emailcsv,https://rowsandall.com/rowers/workout/2347/emailtcx,89,73,2018-02-12 14:02:06+00:00,9336,00:58:35,Joined Workout,imported through email,America/Los_Angeles,water,80.0,lwt -39,https://rowsandall.com/rowers/workout/2336/emailcsv,https://rowsandall.com/rowers/workout/2336/emailtcx,37,32,2018-02-12 14:29:01+00:00,3650,00:14:53.200000,SpdCoach 2," -Summary for your location at 2018-02-12T14:55:00Z: Temperature 9.0C/48.2F. Wind: 2.5 m/s (5.0 kt). Wind Bearing: 280.0 degrees",America/Los_Angeles,water,80.0,lwt -40,https://rowsandall.com/rowers/workout/2340/emailcsv,https://rowsandall.com/rowers/workout/2340/emailtcx,65,49,2018-02-12 14:29:01+00:00,5464,00:31:40,Joined Workout,,America/Los_Angeles,water,80.0,lwt -41,https://rowsandall.com/rowers/workout/2345/emailcsv,https://rowsandall.com/rowers/workout/2345/emailtcx,37,32,2018-02-12 14:29:01+00:00,3650,00:14:53.200000,SpdCoach Sessions (2),imported through email,America/Los_Angeles,water,80.0,lwt -42,https://rowsandall.com/rowers/workout/2337/emailcsv,https://rowsandall.com/rowers/workout/2337/emailtcx,11,5,2018-02-12 14:49:02+00:00,1814,00:11:39,SpdCoach 3,,America/Los_Angeles,water,80.0,lwt -43,https://rowsandall.com/rowers/workout/2346/emailcsv,https://rowsandall.com/rowers/workout/2346/emailtcx,11,5,2018-02-12 14:49:02+00:00,1814,00:11:39,SpdCoach Sessions (3),imported through email,America/Los_Angeles,water,80.0,lwt -44,https://rowsandall.com/rowers/workout/2309/emailcsv,https://rowsandall.com/rowers/workout/2309/emailtcx,8,9,2018-02-12 18:39:45+00:00,2014,00:08:33,WU and CD, ,Europe/Prague,other,80.0,lwt -45,https://rowsandall.com/rowers/workout/2308/emailcsv,https://rowsandall.com/rowers/workout/2308/emailtcx,90,73,2018-02-12 18:49:04+00:00,10562,00:46:10,4x2km,,Europe/Prague,rower,80.0,lwt -46,https://rowsandall.com/rowers/workout/2307/emailcsv,https://rowsandall.com/rowers/workout/2307/emailtcx,9,5,2018-02-12 19:36:46+00:00,1998,00:09:35,WU and CD, ,Europe/Prague,other,80.0,lwt -47,https://rowsandall.com/rowers/workout/2306/emailcsv,https://rowsandall.com/rowers/workout/2306/emailtcx,48,0,2018-02-14 05:55:22+00:00,0,00:58:33,Morning Activity, ,Europe/Prague,other,80.0,lwt -48,https://rowsandall.com/rowers/workout/2378/emailcsv,https://rowsandall.com/rowers/workout/2378/emailtcx,40,6,2018-02-17 08:17:50+00:00,4386,00:27:08,Morning Run, ,Europe/Prague,other,80.0,lwt -49,https://rowsandall.com/rowers/workout/2381/emailcsv,https://rowsandall.com/rowers/workout/2381/emailtcx,79,48,2018-02-21 06:26:01+00:00,6119,00:27:20.200000,3x2km Racice,,Europe/Prague,water,80.0,lwt -50,https://rowsandall.com/rowers/workout/2384/emailcsv,https://rowsandall.com/rowers/workout/2384/emailtcx,107,66,2018-02-22 09:32:01+00:00,8000,00:35:08.500000,8k trial,,Europe/Prague,water,80.0,lwt -51,https://rowsandall.com/rowers/workout/2383/emailcsv,https://rowsandall.com/rowers/workout/2383/emailtcx,-2,50,2018-02-23 15:17:03+00:00,5918,00:25:10.700000,Horin,,Europe/Prague,water,80.0,lwt -52,https://rowsandall.com/rowers/workout/2385/emailcsv,https://rowsandall.com/rowers/workout/2385/emailtcx,169,0,2018-02-24 11:58:09+00:00,17045,01:35:59,Washington,,America/New_York,water,80.0,lwt -53,https://rowsandall.com/rowers/workout/2386/emailcsv,https://rowsandall.com/rowers/workout/2386/emailtcx,-2,59,2018-02-25 12:24:59+00:00,6182,00:26:24.400000,9x500m/70sec,,Europe/Prague,rower,80.0,lwt -54,https://rowsandall.com/rowers/workout/2387/emailcsv,https://rowsandall.com/rowers/workout/2387/emailtcx,11,7,2018-02-27 13:19:44+00:00,2206,00:10:00,StatsError,,Europe/Prague,rower,80.0,lwt -55,https://rowsandall.com/rowers/workout/2388/emailcsv,https://rowsandall.com/rowers/workout/2388/emailtcx,87,410,2018-03-04 12:16:34+00:00,17817,01:29:08,Afternoon Activity, ,Europe/Prague,other,80.0,lwt From 6b5a76242d8828a3a1f6dd535b40d626e5691f46 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Mar 2018 20:34:48 +0100 Subject: [PATCH 07/11] fixed time weighted mean bug Please enter the commit message for your changes. Lines starting --- rowers/dataprep.py | 1 + rowers/views.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 70638f33..85bf4b22 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1634,6 +1634,7 @@ def testdata(time, distance, pace, spm): def getrowdata_db(id=0, doclean=False, convertnewtons=True): data = read_df_sql(id) data['x_right'] = data['x_right'] / 1.0e6 + data['deltat'] = data['time'].diff() if data.empty: rowdata, row = getrowdata(id=id) diff --git a/rowers/views.py b/rowers/views.py index 1857f2a0..cda3a8e2 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1762,7 +1762,6 @@ def workouts_summaries_email_view(request): first=startdate, last=enddate ) - print filename df = dataprep.workout_summary_to_df(r,startdate=startdate,enddate=enddate) df.to_csv(filename,encoding='utf-8') res = myqueue(queuehigh,handle_sendemailsummary, @@ -7535,7 +7534,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""): if datadf.empty: return HttpResponse("CSV data file not found") - datadf['deltat'] = datadf['time'].diff() + #datadf['deltat'] = datadf['time'].diff() workoutstateswork = [1,4,5,8,9,6,7] From 0808884c9c08f7736fe82e53ae02374afa525116 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 6 Mar 2018 21:40:28 +0100 Subject: [PATCH 08/11] formset --- rowers/plannedsessions.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 3aad0ecf..29018c24 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -396,6 +396,10 @@ def get_workouts_session(r,ps): def update_plannedsession(ps,cd): for attr, value in cd.items(): + if attr == 'comment': + value.replace("\r\n", " "); + value.replace("\n", " "); + print value setattr(ps, attr, value) ps.save() From ad71e8ff0d95bda5548e4bbdbe2a37ebc16ab7d9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 7 Mar 2018 13:55:25 +0100 Subject: [PATCH 09/11] implemented positive opt in for GDPR --- rowers/middleware.py | 25 ++++++++++++++++ rowers/models.py | 2 ++ rowers/templates/gdpr_optin.html | 49 ++++++++++++++++++++++++++++++++ rowers/urls.py | 4 ++- rowers/views.py | 27 ++++++++++++++++++ rowsandall_app/settings.py | 1 + 6 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 rowers/templates/gdpr_optin.html diff --git a/rowers/middleware.py b/rowers/middleware.py index e9c90956..c87c6c44 100644 --- a/rowers/middleware.py +++ b/rowers/middleware.py @@ -71,5 +71,30 @@ class PowerTimeFitnessMetricMiddleWare(object): result = do_update(request.user,mode='rower') result = do_update(request.user,mode='water') +from django.shortcuts import redirect +allowed_paths = [ + '/rowers/me/delete', + '/', + '/logout', + '/logout/', + '/rowers/me/gdpr-optin/', + '/rowers/me/gdpr-optin-confirm/', + '/rowers/me/gdpr-optin', + '/rowers/me/gdpr-optin-confirm' + '/rowers/exportallworkouts/', + '/rowers/exportallworkouts', +] + +class GDPRMiddleWare(object): + def process_request(self, request): + if request.user.is_authenticated() and request.path not in allowed_paths: + r = getrower(request.user) + nexturl = request.path + if 'optin' in nexturl: + nexturl = '/rowers/list-workouts' + if not r.gdproptin: + return redirect( + '/rowers/me/gdpr-optin/?next=%s' % nexturl + ) diff --git a/rowers/models.py b/rowers/models.py index 99117e5a..f9d89468 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -468,6 +468,8 @@ class Rower(models.Model): ('Yoga','Yoga'), ) user = models.OneToOneField(User) + gdproptin = models.BooleanField(default=False) + gdproptindate = models.DateTimeField(blank=True,null=True) # Heart Rate Zone data max = models.IntegerField(default=192,verbose_name="Max Heart Rate") diff --git a/rowers/templates/gdpr_optin.html b/rowers/templates/gdpr_optin.html new file mode 100644 index 00000000..532dd59f --- /dev/null +++ b/rowers/templates/gdpr_optin.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}GDPR Opt-In{% endblock %} + +{% block content %} +
+

GDPR Opt-In

+
+

+ + To comply with the European Union General Data Protection Regulation, + we need to record your consent to use personal data on this website. + Please take some time to review our data policies. If you agree and + opt in, click the green button at the bottom to be taken to the site. + If you do not agree, please use the red button to delete your + account. This will irreversibly delete all your data on rowsandall.com + and remove your account. + +

+ +

+ This paragraph will contain the data policy +

+ +

+

+

+ + +
+ {% csrf_token %} + +
+ + +
+
+ +
+ +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index c642adc2..71c4b6fd 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -121,7 +121,7 @@ urlpatterns = [ url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'), url(r'^403/$', TemplateView.as_view(template_name='403.html'),name='403'), url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'), - url(r'^exportallworkouts$',views.workouts_summaries_email_view), + url(r'^exportallworkouts/?$',views.workouts_summaries_email_view), url(r'^agegroupcp/(?P\d+)$',views.agegroupcpview), url(r'^agegroupcp/(?P\d+)/(?P\d+)$',views.agegroupcpview), url(r'^ajax_agegroup/(?P\d+)/(?P\w+.*)/(?P\w+.*)/(?P\d+)$', @@ -338,6 +338,8 @@ urlpatterns = [ url(r'^user-multiflex-data$',views.multiflex_data), url(r'^me/deactivate$',views.deactivate_user), url(r'^me/delete$',views.remove_user), + url(r'^me/gdpr-optin-confirm/?$',views.user_gdpr_confirm), + url(r'^me/gdpr-optin/?$',views.user_gdpr_optin), url(r'^me/teams/$',views.rower_teams_view), url(r'^me/calcdps/$',views.rower_calcdps_view), url(r'^me/exportsettings/$',views.rower_exportsettings_view), diff --git a/rowers/views.py b/rowers/views.py index cda3a8e2..c341e611 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -594,6 +594,33 @@ def deactivate_user(request): else: raise PermissionDenied +@login_required() +def user_gdpr_optin(request): + r = getrower(request.user) + r.gdproptin = False + r.gdproptindate = None + r.save() + nexturl = request.GET.get('next','/rowers/list-workouts/') + if r.gdproptin: + return HttpResponseRedirect(nexturl) + + return render(request,'gdpr_optin.html',{ + "next": nexturl + }) + +@login_required() +def user_gdpr_confirm(request): + r = getrower(request.user) + r.gdproptin = True + r.gdproptindate = timezone.now() + r.save() + + nexturl = request.GET.get('next','/rowers/list-workouts/') + + return HttpResponseRedirect(nexturl) + + + @login_required() def remove_user(request): pk = request.user.id diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index 052f2af4..e8d1f0b9 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -94,6 +94,7 @@ MIDDLEWARE_CLASSES = [ 'async_messages.middleware.AsyncMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'tz_detect.middleware.TimezoneMiddleware', + 'rowers.middleware.GDPRMiddleWare', 'rowers.middleware.PowerTimeFitnessMetricMiddleWare', ] From 1a87d72a8d5ac51755245a2707c7d6dc57d1611e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 7 Mar 2018 15:59:42 +0100 Subject: [PATCH 10/11] removing google analytics and clicky --- rowers/tasks.py | 18 ++++++++++++++++++ rowers/templates/502.html | 18 ------------------ rowers/views.py | 12 ++++++++++-- rowsandall_app/settings.py | 2 +- templates/basebase.html | 4 +--- 5 files changed, 30 insertions(+), 24 deletions(-) diff --git a/rowers/tasks.py b/rowers/tasks.py index aed1bf59..29619a05 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -385,6 +385,24 @@ def handle_sendemail_hard(workoutid, useremail, return 1 +# send email when user deletes account +@app.task +def handle_sendemail_userdeleted(name, email, debug=False, **kwargs): + fullemail = 'roosendaalsander@gmail.com' + subject = 'User account deleted' + message = 'Sander,\n\n' + message += 'The user {name} ({email}) has just deleted his account'.format( + name=name, + email=email + ) + email = EmailMessage(subject,message, + 'Rowsandall ', + [fullemail]) + + res = email.send() + + return 1 + # send email to me when an unrecognized file is uploaded @app.task def handle_sendemail_unrecognized(unrecognizedfile, useremail, diff --git a/rowers/templates/502.html b/rowers/templates/502.html index bbf6f82f..edd8094e 100644 --- a/rowers/templates/502.html +++ b/rowers/templates/502.html @@ -204,24 +204,6 @@
- diff --git a/rowers/views.py b/rowers/views.py index c341e611..74d4b9d4 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -123,7 +123,8 @@ from rowers.tasks import ( handle_sendemailnewresponse, handle_updatedps, handle_updatecp,long_test_task,long_test_task2, handle_zip_file,handle_getagegrouprecords, - handle_updatefitnessmetric + handle_updatefitnessmetric, + handle_sendemail_userdeleted, ) from scipy.signal import savgol_filter @@ -631,9 +632,16 @@ def remove_user(request): user_form = DeleteUserForm(request.POST,instance=user) if user_form.is_valid(): cd = user_form.cleaned_data - print cd + name = user.first_name+' '+user.last_name + email = user.email + + if cd['delete_user']: user.delete() + res = myqueue(queuehigh, + handle_sendemail_userdeleted, + name, email) + url = reverse(auth_views.logout_then_login) return HttpResponseRedirect(url) return render(request, "userprofile_delete.html", { diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index e8d1f0b9..86f3f1c4 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -119,7 +119,7 @@ TEMPLATES = [ 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.i18n', - 'context_processors.google_analytics', +# 'context_processors.google_analytics', 'context_processors.warning_message', ], # 'loaders': [ diff --git a/templates/basebase.html b/templates/basebase.html index 3ad39b0b..6aaaac8a 100644 --- a/templates/basebase.html +++ b/templates/basebase.html @@ -26,9 +26,7 @@ {% analytical_head_top %} - {% if GOOGLE_ANALYTICS_PROPERTY_ID %} - {% include "ga.html" %} - {% endif %} + From 100cd9a48f96bf3f0cd8f049e1815c83fa22a2d7 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 7 Mar 2018 22:06:27 +0100 Subject: [PATCH 11/11] updated legal text on legal page and GDPR opt in page --- rowers/templates/gdpr_optin.html | 177 +++++++++++++++++++++++++++- rowers/templates/legal.html | 190 +++++++++++++++++++++++++------ 2 files changed, 330 insertions(+), 37 deletions(-) diff --git a/rowers/templates/gdpr_optin.html b/rowers/templates/gdpr_optin.html index 532dd59f..939a307f 100644 --- a/rowers/templates/gdpr_optin.html +++ b/rowers/templates/gdpr_optin.html @@ -7,7 +7,6 @@ {% block content %}

GDPR Opt-In

-

To comply with the European Union General Data Protection Regulation, @@ -19,9 +18,183 @@ and remove your account.

+
+ +

Personal information collection

+

+ rowsandall.com may collect and use the following kinds of information: +

    +
  • information about your use of this website +
  • information that you provide for the purpose of + registering with the website +
  • information about transactions carried out over this website +
  • information that you provide for the purpose of + using this website, for instance heart rate band and weight information. +
  • any other information that you send to rowsandall.com +
+ Explicitly, the following information is collected: +
    +
  • User name, email address, encrypted password (PBKDF2 algorithm + with a SHA256 Hash and a password stretching mechanism recommended + by NIST). +
  • Your birth date. +
  • Your user consent to these GDPR compliance policies, and the + date at which you consented. Without this consent, the site cannot + be used. +
  • Weight category. With individual workouts, you may record your + actual weight during the workout. +
  • Your gender, if you decide to provide it. +
  • Heart rate zones you define. Only the actual values are stored. We do + not keep records of their evolution. +
  • Power zones and Functional Threshold power. Only the + actual values are stored. We do + not keep records of their evolution. +
  • Parameter values used to construct your Critical Power curve + (OTW and OTE). +
  • User preferences, such as the buttons and functionalities + defined in the Workflow left + panel and right panel. +
  • Tokens and their expiry dates used for sharing data with + other fitness sites. You can actually revoke these at any time. +
  • User preferences as shown on the user settings page +
  • Your favorite Flex Charts if defined +
  • The teams you are a member of. +
  • Estimated four minute, 2k and 1 hour ergometer and OTW power values, + based on the workouts you upload, and their evolution during your + usage of the site +
  • For members on the Coach plan, the names and purposes of teams. Names + of team members. (Members who delete their account will be erased from + existing teams.) +
  • Any rowing courses you uploaded +
  • Training targets and training plans +
  • Your uploaded workouts, their names, boat type, start time and date, + time zone information, total distance, duration, weight, average + and maximum heart rate, and references to their locations on third + party sites, rigging parameters (if provided), + summary information, any notes you made, privacy status and + ranking piece status. +
  • Stroke data, including, for each stroke, time, heart rate, + pace, stroke rate, work per stroke, power, average and peak + force, drive length, distance, drive speed, catch and finish angles, + slip, wash, peak force angle, effective angle, rhythm, + efficiency and distance per stroke, as well as any other + data in the data files you shared to rowsandall.com +
  • Images created on the site, from your rowing data, or uploaded + to the site. +
  • Comments you make to your and other people's workouts +
+

- This paragraph will contain the data policy + The site is only accessible to user of 16 years and older. +

+ +

Data Deletion

+ +

All the data mentioned in the previous section are stored in files + and in a database, hosted on our hosting provider's servers. Our + hosting provider is creating backups of those data. The database backups + are retained for 7 days. File backups are retained for 30 days. However, + the file names or content do not contain any links to the users. The + link to the file is stored under the user data in the database, so once + a database entry is removed, there is no way to link a file with data + to a particular user. +

+

+ When a user requests deletion of the data, his account and all data linked to his account + are removed from the database and the files are deleted. This includes all data mentioned in the + previous section. In backups, database entries will be removed after 7 days and files after + 30 days. +

+ +

Data deletion can be initiated by the user through the button on the user settings page.

+ +

Data Security

+ +

The site uses SSL to encrypt data transferred between the server and the client (web browers, + mobile apps, third party sites). Any forms are secured from Cross Site Request Forgery (CSRF) using Django's + CSRF middleware.

+ +

+ We have a double defense against reading or editing of personal data. First, we ensure that all "protected" views + are only visible to logged-in users. Only logged-in users have buttons leading to the private parts of the site. + As a second step, protecting against guessing of URL, before serving data from the database, we check explicitly that the data + is owned by the user in question, redirecting unauthorized requests to a "Permission Denied" page. Private data is collected + through POST requests to prevent them from being visible in URL data. +

+ +

rowsandall.com will take reasonable technical and organisational precautions to prevent the loss, + misuse or alteration of your personal information.

+ +

In case of loss, misuse or alteration of your personal information, we will inform you without undue delay and take measures + to prevent further misuse. In particular, we will deactivate your account, which will not delete the data but make them + inaccessible even for people who obtained the password (including yourself). We will await your instructions. If no + instructions are received within 7 days of contacting you, your account and all your data will be removed. +

+ + +

Data Sharing and access to data

+ +

+ Only the data owner can the site administrator can edit and/or delete the data. Per our data policy, the site administrator will not alter + or delete any data owned by users, unless requested so. As data are not stored on servers that are physically owner by us, or by + our hosting provider, but we use rented server space, we are technically sharing the information to agents or sub-contractors. +

+ +

+ Where rowsandall.com discloses your personal information to its agents or sub-contractors for these purposes, + the agent or sub-contractor in question will be obligated to use that personal information in accordance with the terms of this privacy statement. + Our hosting provider is based in the European Union and is bound by the same GDPR regulation as we are. +

+ +

In addition to the disclosures reasonably necessary for the purposes identified elsewhere above, rowsandall.com + may disclose your personal information to the extent that it is required to do so by law, in connection with + any legal proceedings or prospective legal proceedings, and in order to establish, exercise or defend its legal rights.

+ + +

+ Workout data and charts based on workout data can be shared to anyone by sharing the URL. Workouts have an option to be set to + "private", in which case the data are not visible to anyone except the owner. The site is not searchable for data other than + your own data, so there is no way for other people to track your workouts, unless you share them. +

+ +

+ Cross-border data transfers. Information that rowsandall.com collects may be stored and processed in and transferred + between any of the countries in which rowsandall.com operates to enable the use of the information in accordance with this privacy policy. + In addition, personal information that you submit for publication on the website will be published on the internet and + may be available around the world. + You agree to such cross-border transfers of personal information. +

+ +

+ By accepting an "invitation" to become a member of a team, or by requesting to become part of a team, you agree to automatically + share all your workout data (including workouts done prior to becoming a member of the team) to the team manager (coach) and, + depending to the team policy, to other members of the team. When you leave + a team, all your workout data will immediately become invisible to those who had access to it during your team membership, including + workouts that cover the period of time when you were member of the team. As a member of a team, you grant the team manager + permission to edit workout data + on your behalf, including the creation of charts and cross workout analysis. You also grant the team manager permission to + edit your heart rate and power settings, as well as functional threshold information and the account information accessible on your + settings page under the header "Account Information". The team manager is not able to access or change your passwords, team memberships, + favorite charts, export settings, workflow layout, or secret tokens. Also, the team manager is not able to download all your data, + not can he deactivate or delete your account. +

+ +

+ This site offers the possiblity to synchronize your data with other fitness sites. By clicking on the share or connect button (link, or + equivalent) you agree to share information between rowsandall.com and the other website. Rowsandall.com is not responsible for the privacy + policies or practices of any third party. +

+ +

Data portability

+ +

Through the "download your data" link on the user settings page, each user can download all workout data. Stroke data can be downloaded + through links in the downloaded workout data file.

+ +
+ +

+ To start or continue using the site, please give your consent by clicking on the green Opt In button below.

diff --git a/rowers/templates/legal.html b/rowers/templates/legal.html index 37951f1b..6a4b3d35 100644 --- a/rowers/templates/legal.html +++ b/rowers/templates/legal.html @@ -159,61 +159,181 @@

Credit

This document was created using a Contractology template available at -http://www.freenetlaw.com..

+http://www.freenetlaw.com.. It was modified to reflect the GDPR requirements.

-

Personal information collection

-

rowsandall.com may collect and use the following kinds of information: -

    -
  • information about your use of this website -
  • information that you provide for the purpose of registering with the website -
  • information about transactions carried out over this website -
  • information that you provide for the purpose of using this website, for instance heart rate band and weight information. -
  • any other information that you send to rowsandall.com -

+

Personal information collection

+

+ rowsandall.com may collect and use the following kinds of information: +

    +
  • information about your use of this website +
  • information that you provide for the purpose of + registering with the website +
  • information about transactions carried out over this website +
  • information that you provide for the purpose of + using this website, for instance heart rate band and weight information. +
  • any other information that you send to rowsandall.com +
+ Explicitly, the following information is collected: +
    +
  • User name, email address, encrypted password (PBKDF2 algorithm + with a SHA256 Hash and a password stretching mechanism recommended + by NIST). +
  • Your birth date. +
  • Your user consent to these GDPR compliance policies, and the + date at which you consented. Without this consent, the site cannot + be used. +
  • Weight category. With individual workouts, you may record your + actual weight during the workout. +
  • Your gender, if you decide to provide it. +
  • Heart rate zones you define. Only the actual values are stored. We do + not keep records of their evolution. +
  • Power zones and Functional Threshold power. Only the + actual values are stored. We do + not keep records of their evolution. +
  • Parameter values used to construct your Critical Power curve + (OTW and OTE). +
  • User preferences, such as the buttons and functionalities + defined in the Workflow left + panel and right panel. +
  • Tokens and their expiry dates used for sharing data with + other fitness sites. You can actually revoke these at any time. +
  • User preferences as shown on the user settings page +
  • Your favorite Flex Charts if defined +
  • The teams you are a member of. +
  • Estimated four minute, 2k and 1 hour ergometer and OTW power values, + based on the workouts you upload, and their evolution during your + usage of the site +
  • For members on the Coach plan, the names and purposes of teams. Names + of team members. (Members who delete their account will be erased from + existing teams.) +
  • Any rowing courses you uploaded +
  • Training targets and training plans +
  • Your uploaded workouts, their names, boat type, start time and date, + time zone information, total distance, duration, weight, average + and maximum heart rate, and references to their locations on third + party sites, rigging parameters (if provided), + summary information, any notes you made, privacy status and + ranking piece status. +
  • Stroke data, including, for each stroke, time, heart rate, + pace, stroke rate, work per stroke, power, average and peak + force, drive length, distance, drive speed, catch and finish angles, + slip, wash, peak force angle, effective angle, rhythm, + efficiency and distance per stroke, as well as any other + data in the data files you shared to rowsandall.com +
  • Images created on the site, from your rowing data, or uploaded + to the site. +
  • Comments you make to your and other people's workouts +
+

-

Using personal information

+

+ The site is only accessible to user of 16 years and older. +

-

rowsandall.com may use your personal information to: -

    -
  • administer this website -
  • personalize this website for you -
  • enable your access to and use of the website services -
  • publish information about you on the website -
  • supply to you services that you purchase -

+

Data Deletion

-

Where rowsandall.com discloses your personal information to its agents or sub-contractors for these purposes, the agent or sub-contractor in question will be obligated to use that personal information in accordance with the terms of this privacy statement.

+

All the data mentioned in the previous section are stored in files + and in a database, hosted on our hosting provider's servers. Our + hosting provider is creating backups of those data. The database backups + are retained for 7 days. File backups are retained for 30 days. However, + the file names or content do not contain any links to the users. The + link to the file is stored under the user data in the database, so once + a database entry is removed, there is no way to link a file with data + to a particular user. +

+

+ When a user requests deletion of the data, his account and all data linked to his account + are removed from the database and the files are deleted. This includes all data mentioned in the + previous section. In backups, database entries will be removed after 7 days and files after + 30 days. +

-

In addition to the disclosures reasonably necessary for the purposes identified elsewhere above, rowsandall.com may disclose your personal information to the extent that it is required to do so by law, in connection with any legal proceedings or prospective legal proceedings, and in order to establish, exercise or defend its legal rights.

+

Data deletion can be initiated by the user through the button on the user settings page.

-

Securing your data

+

Data Security

-

rowsandall.com will take reasonable technical and organisational precautions to prevent the loss, misuse or alteration of your personal information.

+

The site uses SSL to encrypt data transferred between the server and the client (web browers, + mobile apps, third party sites). Any forms are secured from Cross Site Request Forgery (CSRF) using Django's + CSRF middleware.

-

rowsandall.com will store all the personal information you provide

+

+ We have a double defense against reading or editing of personal data. First, we ensure that all "protected" views + are only visible to logged-in users. Only logged-in users have buttons leading to the private parts of the site. + As a second step, protecting against guessing of URL, before serving data from the database, we check explicitly that the data + is owned by the user in question, redirecting unauthorized requests to a "Permission Denied" page. Private data is collected + through POST requests to prevent them from being visible in URL data. +

-

Cross-border data transfers

+

rowsandall.com will take reasonable technical and organisational precautions to prevent the loss, + misuse or alteration of your personal information.

-

Information that rowsandall.com collects may be stored and processed in and transferred between any of the countries in which rowsandall.com operates to enable the use of the information in accordance with this privacy policy.

+

In case of loss, misuse or alteration of your personal information, we will inform you without undue delay and take measures + to prevent further misuse. In particular, we will deactivate your account, which will not delete the data but make them + inaccessible even for people who obtained the password (including yourself). We will await your instructions. If no + instructions are received within 7 days of contacting you, your account and all your data will be removed. +

-

In addition, personal information that you submit for publication on the website will be published on the internet and may be available around the world.

-

You agree to such cross-border transfers of personal information.

+

Data Sharing and access to data

-

Updating this statement

+

+ Only the data owner can the site administrator can edit and/or delete the data. Per our data policy, the site administrator will not alter + or delete any data owned by users, unless requested so. As data are not stored on servers that are physically owner by us, or by + our hosting provider, but we use rented server space, we are technically sharing the information to agents or sub-contractors. +

-

rowsandall.com may update this privacy policy by posting a new version on this website.

+

+ Where rowsandall.com discloses your personal information to its agents or sub-contractors for these purposes, + the agent or sub-contractor in question will be obligated to use that personal information in accordance with the terms of this privacy statement. + Our hosting provider is based in the European Union and is bound by the same GDPR regulation as we are. +

-

You should check this page occasionally to ensure you are familiar with any changes.

+

In addition to the disclosures reasonably necessary for the purposes identified elsewhere above, rowsandall.com + may disclose your personal information to the extent that it is required to do so by law, in connection with + any legal proceedings or prospective legal proceedings, and in order to establish, exercise or defend its legal rights.

-

Other websites

+ +

+ Workout data and charts based on workout data can be shared to anyone by sharing the URL. Workouts have an option to be set to + "private", in which case the data are not visible to anyone except the owner. The site is not searchable for data other than + your own data, so there is no way for other people to track your workouts, unless you share them. +

-

This website connects to other websites. By clicking the connect button (link, or equivalent) you agree to share information between rowsandall.com and the other website.

+

+ Cross-border data transfers. Information that rowsandall.com collects may be stored and processed in and transferred + between any of the countries in which rowsandall.com operates to enable the use of the information in accordance with this privacy policy. + In addition, personal information that you submit for publication on the website will be published on the internet and + may be available around the world. + You agree to such cross-border transfers of personal information. +

+ +

+ By accepting an "invitation" to become a member of a team, or by requesting to become part of a team, you agree to automatically + share all your workout data (including workouts done prior to becoming a member of the team) to the team manager (coach) and, + depending to the team policy, to other members of the team. When you leave + a team, all your workout data will immediately become invisible to those who had access to it during your team membership, including + workouts that cover the period of time when you were member of the team. As a member of a team, you grant the team manager + permission to edit workout data + on your behalf, including the creation of charts and cross workout analysis. You also grant the team manager permission to + edit your heart rate and power settings, as well as functional threshold information and the account information accessible on your + settings page under the header "Account Information". The team manager is not able to access or change your passwords, team memberships, + favorite charts, export settings, workflow layout, or secret tokens. Also, the team manager is not able to download all your data, + not can he deactivate or delete your account. +

+ +

+ This site offers the possiblity to synchronize your data with other fitness sites. By clicking on the share or connect button (link, or + equivalent) you agree to share information between rowsandall.com and the other website. Rowsandall.com is not responsible for the privacy + policies or practices of any third party. +

-

rowsandall.com is not responsible for the privacy policies or practices of any third party.

+

Data portability

+ +

Through the "download your data" link on the user settings page, each user can download all workout data. Stroke data can be downloaded + through links in the downloaded workout data file.

- {% endblock content %} \ No newline at end of file + {% endblock content %}