Merge branch 'release/v6.1'
This commit is contained in:
+63
-4
@@ -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 = []
|
||||
@@ -1510,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
|
||||
@@ -1576,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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
+16
-1
@@ -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()
|
||||
@@ -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")
|
||||
@@ -606,6 +608,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':
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 <info@rowsandall.com>',
|
||||
[fullemail])
|
||||
|
||||
res = email.send()
|
||||
|
||||
return 1
|
||||
|
||||
# send email to me when an unrecognized file is uploaded
|
||||
@app.task
|
||||
def handle_sendemail_unrecognized(unrecognizedfile, useremail,
|
||||
@@ -499,6 +517,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 <info@rowsandall.com>',
|
||||
[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):
|
||||
|
||||
@@ -204,24 +204,6 @@
|
||||
|
||||
</div>
|
||||
<!-- end container -->
|
||||
<!-- Clicky disabled on internal IP address
|
||||
|
||||
<script type="text/javascript">
|
||||
var clicky = { log: function(){ return; }, goal: function(){ return; }};
|
||||
var clicky_site_ids = clicky_site_ids || [];
|
||||
clicky_site_ids.push(101011008);
|
||||
var clicky_custom = {};
|
||||
(function() {
|
||||
var s = document.createElement('script');
|
||||
s.type = 'text/javascript';
|
||||
s.async = true;
|
||||
s.src = '//static.getclicky.com/js';
|
||||
( document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] ).appendChild( s );
|
||||
})();
|
||||
</script>
|
||||
<noscript><p><img alt="Clicky" width="1" height="1" src="//in.getclicky.com/101011008ns.gif" /></p></noscript>
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<link rel="stylesheet" href="/static/debug_toolbar/css/print.css" type="text/css" media="print" />
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Rowsandall Workouts Summary Export{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12">
|
||||
<form enctype="multipart/form-data" method="post">
|
||||
<div class="grid_4 alpha">
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
<div class="grid_2">
|
||||
<input class="button green" type="submit" value="Submit">
|
||||
</div>
|
||||
</form>
|
||||
<div class="grid_6 omega">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,222 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}GDPR Opt-In{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12">
|
||||
<h2>GDPR Opt-In</h2>
|
||||
<p>
|
||||
<b>
|
||||
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.
|
||||
</b>
|
||||
</p>
|
||||
<hr>
|
||||
|
||||
<h2>Personal information collection</h2>
|
||||
<p>
|
||||
rowsandall.com may collect and use the following kinds of information:
|
||||
<ul>
|
||||
<li>information about your use of this website
|
||||
<li>information that you provide for the purpose of
|
||||
registering with the website
|
||||
<li>information about transactions carried out over this website
|
||||
<li>information that you provide for the purpose of
|
||||
using this website, for instance heart rate band and weight information.
|
||||
<li>any other information that you send to rowsandall.com
|
||||
</ul>
|
||||
Explicitly, the following information is collected:
|
||||
<ul>
|
||||
<li>User name, email address, encrypted password (PBKDF2 algorithm
|
||||
with a SHA256 Hash and a password stretching mechanism recommended
|
||||
by NIST).
|
||||
<li>Your birth date.
|
||||
<li>Your user consent to these GDPR compliance policies, and the
|
||||
date at which you consented. Without this consent, the site cannot
|
||||
be used.
|
||||
<li>Weight category. With individual workouts, you may record your
|
||||
actual weight during the workout.
|
||||
<li>Your gender, if you decide to provide it.
|
||||
<li>Heart rate zones you define. Only the actual values are stored. We do
|
||||
not keep records of their evolution.
|
||||
<li>Power zones and Functional Threshold power. Only the
|
||||
actual values are stored. We do
|
||||
not keep records of their evolution.
|
||||
<li>Parameter values used to construct your Critical Power curve
|
||||
(OTW and OTE).
|
||||
<li>User preferences, such as the buttons and functionalities
|
||||
defined in the Workflow left
|
||||
panel and right panel.
|
||||
<li>Tokens and their expiry dates used for sharing data with
|
||||
other fitness sites. You can actually revoke these at any time.
|
||||
<li>User preferences as shown on the user settings page
|
||||
<li>Your favorite Flex Charts if defined
|
||||
<li>The teams you are a member of.
|
||||
<li>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
|
||||
<li>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.)
|
||||
<li>Any rowing courses you uploaded
|
||||
<li>Training targets and training plans
|
||||
<li>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.
|
||||
<li>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
|
||||
<li>Images created on the site, from your rowing data, or uploaded
|
||||
to the site.
|
||||
<li>Comments you make to your and other people's workouts
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The site is only accessible to user of 16 years and older.
|
||||
</p>
|
||||
|
||||
<h2>Data Deletion</h2>
|
||||
|
||||
<p>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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>Data deletion can be initiated by the user through the button on the user settings page.</p>
|
||||
|
||||
<h2>Data Security</h2>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>rowsandall.com will take reasonable technical and organisational precautions to prevent the loss,
|
||||
misuse or alteration of your personal information. </p>
|
||||
|
||||
<p>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.
|
||||
</p>
|
||||
|
||||
|
||||
<h2>Data Sharing and access to data</h2>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h2>Data portability</h2>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
To start or continue using the site, please give your consent by clicking on the green Opt In button below.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<div class="grid_2 suffix_10 alpha">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/exportallworkouts">Download your data</a>
|
||||
</p>
|
||||
</div>
|
||||
</p>
|
||||
|
||||
<div class="grid_2 alpha">
|
||||
<a href="/rowers/me/gdpr-optin-confirm/?next={{ next }}" class="button green small">Opt in and continue</a>
|
||||
</div>
|
||||
<form method="POST" action="/rowers/me/delete" class="padding">
|
||||
{% csrf_token %}
|
||||
<input id="id_delete_user" type="hidden" name="delete_user" value="True">
|
||||
<div class="grid_2 prefix_2">
|
||||
<input class="button red small" type="submit" name="action" value="DELETE ACCOUNT">
|
||||
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
+151
-31
@@ -159,59 +159,179 @@
|
||||
<h3>Credit</h3>
|
||||
|
||||
<p>This document was created using a Contractology template available at
|
||||
<a href="http://www.freenetlaw.com">http://www.freenetlaw.com.</a>.</p>
|
||||
<a href="http://www.freenetlaw.com">http://www.freenetlaw.com.</a>. It was modified to reflect the GDPR requirements.</p>
|
||||
|
||||
|
||||
<h3>Personal information collection</h3>
|
||||
|
||||
<p>rowsandall.com may collect and use the following kinds of information:
|
||||
<p>
|
||||
rowsandall.com may collect and use the following kinds of information:
|
||||
<ul>
|
||||
<li>information about your use of this website
|
||||
<li>information that you provide for the purpose of registering with the website
|
||||
<li>information that you provide for the purpose of
|
||||
registering with the website
|
||||
<li>information about transactions carried out over this website
|
||||
<li>information that you provide for the purpose of using this website, for instance heart rate band and weight information.
|
||||
<li>information that you provide for the purpose of
|
||||
using this website, for instance heart rate band and weight information.
|
||||
<li>any other information that you send to rowsandall.com
|
||||
</ul></p>
|
||||
|
||||
<h3>Using personal information</h3>
|
||||
|
||||
<p>rowsandall.com may use your personal information to:
|
||||
</ul>
|
||||
Explicitly, the following information is collected:
|
||||
<ul>
|
||||
<li>administer this website
|
||||
<li>personalize this website for you
|
||||
<li>enable your access to and use of the website services
|
||||
<li>publish information about you on the website
|
||||
<li>supply to you services that you purchase
|
||||
</ul></p>
|
||||
<li>User name, email address, encrypted password (PBKDF2 algorithm
|
||||
with a SHA256 Hash and a password stretching mechanism recommended
|
||||
by NIST).
|
||||
<li>Your birth date.
|
||||
<li>Your user consent to these GDPR compliance policies, and the
|
||||
date at which you consented. Without this consent, the site cannot
|
||||
be used.
|
||||
<li>Weight category. With individual workouts, you may record your
|
||||
actual weight during the workout.
|
||||
<li>Your gender, if you decide to provide it.
|
||||
<li>Heart rate zones you define. Only the actual values are stored. We do
|
||||
not keep records of their evolution.
|
||||
<li>Power zones and Functional Threshold power. Only the
|
||||
actual values are stored. We do
|
||||
not keep records of their evolution.
|
||||
<li>Parameter values used to construct your Critical Power curve
|
||||
(OTW and OTE).
|
||||
<li>User preferences, such as the buttons and functionalities
|
||||
defined in the Workflow left
|
||||
panel and right panel.
|
||||
<li>Tokens and their expiry dates used for sharing data with
|
||||
other fitness sites. You can actually revoke these at any time.
|
||||
<li>User preferences as shown on the user settings page
|
||||
<li>Your favorite Flex Charts if defined
|
||||
<li>The teams you are a member of.
|
||||
<li>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
|
||||
<li>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.)
|
||||
<li>Any rowing courses you uploaded
|
||||
<li>Training targets and training plans
|
||||
<li>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.
|
||||
<li>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
|
||||
<li>Images created on the site, from your rowing data, or uploaded
|
||||
to the site.
|
||||
<li>Comments you make to your and other people's workouts
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>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. </p>
|
||||
<p>
|
||||
The site is only accessible to user of 16 years and older.
|
||||
</p>
|
||||
|
||||
<p>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.</p>
|
||||
<h3>Data Deletion</h3>
|
||||
|
||||
<h3>Securing your data</h3>
|
||||
<p>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.
|
||||
</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>rowsandall.com will take reasonable technical and organisational precautions to prevent the loss, misuse or alteration of your personal information. </p>
|
||||
<p>Data deletion can be initiated by the user through the button on the user settings page.</p>
|
||||
|
||||
<p>rowsandall.com will store all the personal information you provide</p>
|
||||
<h3>Data Security</h3>
|
||||
|
||||
<h3>Cross-border data transfers</h3>
|
||||
<p>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.</p>
|
||||
|
||||
<p>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.</p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>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.</p>
|
||||
<p>rowsandall.com will take reasonable technical and organisational precautions to prevent the loss,
|
||||
misuse or alteration of your personal information. </p>
|
||||
|
||||
<p>You agree to such cross-border transfers of personal information.</p>
|
||||
<p>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.
|
||||
</p>
|
||||
|
||||
<h3>Updating this statement</h3>
|
||||
|
||||
<p>rowsandall.com may update this privacy policy by posting a new version on this website. </p>
|
||||
<h3>Data Sharing and access to data</h3>
|
||||
|
||||
<p>You should check this page occasionally to ensure you are familiar with any changes. </p>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h3>Other websites</h3>
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>This website connects to other websites. By clicking the <q>connect</q> button (link, or equivalent) you agree to share information between rowsandall.com and the other website.</p>
|
||||
<p>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.</p>
|
||||
|
||||
<p>rowsandall.com is not responsible for the privacy policies or practices of any third party.</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h3>Data portability</h3>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,15 @@
|
||||
<div class="grid_6 omega">
|
||||
<p> To use rowsandall, you need to register and agree with the Terms of Service. </p>
|
||||
<p> Registration is free. </p>
|
||||
|
||||
<p>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.
|
||||
</p>
|
||||
|
||||
<p>Also, we are restricting access to the site to 16 years and older
|
||||
because of EU data protection regulations.</p>
|
||||
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
|
||||
@@ -1,11 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Change Rower {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12 alpha">
|
||||
<h1>User Settings</h1>
|
||||
<div class="grid_8 alpha">
|
||||
<h1>User Settings for {{ rower.user.first_name }} {{ rower.user.last_name }}</h1>
|
||||
<p><a href="http://analytics.rowsandall.com/2017/11/02/rowsandall-settings-page-tutorial/">Need help? Click to read the tutorial</a></p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_2 omega dropdown">
|
||||
<button class="grid_2 alpha button green small dropbtn">
|
||||
Change Rower
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
{% for rower in user|team_rowers %}
|
||||
<a class="button green small" href="/rowers/rower/edit/{{ rower.id }}">{{ rower.user.first_name }} {{ rower.user.last_name }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
<h2>Heart Rate Zones</h2>
|
||||
@@ -88,7 +102,11 @@
|
||||
<div class="grid_6 alpha">
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<p>
|
||||
{% if rower.user == user %}
|
||||
<a class="button gray small" href="/password_change/">Password Change</a>
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,7 +134,7 @@
|
||||
</table>
|
||||
{% csrf_token %}
|
||||
<div class="grid_2 alpha">
|
||||
{% if rower.rowerplan == 'basic' %}
|
||||
{% if rower.rowerplan == 'basic' and rower.user == user %}
|
||||
<a class="button blue" href="/rowers/promembership">Upgrade</a>
|
||||
{% else %}
|
||||
|
||||
@@ -152,6 +170,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if rower.user == user %}
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
@@ -193,7 +212,27 @@
|
||||
</div>
|
||||
|
||||
<div class="grid_12 alpha">
|
||||
<div class="grid_6 suffix_6 alpha">
|
||||
<div class="grid_6 alpha">
|
||||
<p>
|
||||
<h2>GDPR - Data Protection</h2>
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/exportallworkouts">Download your data</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<p>
|
||||
<a class="button gray small" href="/rowers/me/deactivate">Deactivate Account</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_2 suffix_4 alpha">
|
||||
<p>
|
||||
<a class="button red small" href="/rowers/me/delete">Delete Account</a>
|
||||
</p>
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
<div class="grid_6 omega">
|
||||
{% if grants %}
|
||||
<p>
|
||||
<h2>Applications</h2>
|
||||
@@ -223,7 +262,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td> {{ member.user.first_name }} {{ member.user.last_name }}</td>
|
||||
<td><a href="/rowers/rower/edit/{{ member.id }}"> {{ member.user.first_name }} {{ member.user.last_name }}</a></td>
|
||||
{% if team.manager == user %}
|
||||
<td><a class="button red small" href="/rowers/me/team/{{ team.id }}/drop/{{ member.user.id }}">Drop</a></td>
|
||||
{% else %}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Deactivate your account{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12">
|
||||
<h2>Deactivate your account</h2>
|
||||
<hr>
|
||||
<p>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.</p>
|
||||
<form method="POST" class="padding">{% csrf_token %}
|
||||
{{ user_form.as_p}}
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-primary" type="submit" name="action">Confirm</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,22 @@
|
||||
{% extends "base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load rowerfilters %}
|
||||
|
||||
{% block title %}Delete your account{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid_12">
|
||||
<h2>Delete your account</h2>
|
||||
<hr>
|
||||
<p><b>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</b></p>
|
||||
<form method="POST" class="padding">{% csrf_token %}
|
||||
{{ user_form.as_p}}
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-primary" type="submit" name="action">Confirm</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -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<age>\d+)$',views.agegroupcpview),
|
||||
url(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\d+)$',views.agegroupcpview),
|
||||
url(r'^ajax_agegroup/(?P<age>\d+)/(?P<weightcategory>\w+.*)/(?P<sex>\w+.*)/(?P<userid>\d+)$',
|
||||
@@ -335,6 +336,10 @@ 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/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),
|
||||
@@ -357,6 +362,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<rowerid>\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<id>\d+)$',views.rower_revokeapp_view),
|
||||
|
||||
+134
-23
@@ -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,
|
||||
@@ -118,10 +119,12 @@ 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,
|
||||
handle_updatefitnessmetric
|
||||
handle_updatefitnessmetric,
|
||||
handle_sendemail_userdeleted,
|
||||
)
|
||||
|
||||
from scipy.signal import savgol_filter
|
||||
@@ -572,6 +575,82 @@ 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 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
|
||||
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
|
||||
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", {
|
||||
"user_form": user_form,
|
||||
})
|
||||
else:
|
||||
raise PermissionDenied
|
||||
|
||||
|
||||
@login_required()
|
||||
def get_testscript(request,id):
|
||||
row = get_workout_permitted(request.user,id)
|
||||
@@ -1704,6 +1783,37 @@ 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'.format(
|
||||
first=startdate,
|
||||
last=enddate
|
||||
)
|
||||
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):
|
||||
@@ -1867,7 +1977,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"
|
||||
|
||||
@@ -3720,7 +3829,6 @@ def rankings_view2(request,theuser=0,
|
||||
sex = r.sex,
|
||||
weightcategory = r.weightcategory)
|
||||
|
||||
print len(agerecords),'aap'
|
||||
if len(agerecords) == 0:
|
||||
recalc = True
|
||||
wcpower = []
|
||||
@@ -7461,7 +7569,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]
|
||||
@@ -8134,8 +8242,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:
|
||||
@@ -10866,8 +10972,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=""):
|
||||
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 +10995,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 +11009,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 +11029,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 +11051,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 +11066,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 +11081,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 +11111,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 +11122,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 +11144,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 +11158,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 +11173,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 +11182,6 @@ def rower_edit_view(request,message=""):
|
||||
|
||||
|
||||
u.save()
|
||||
r = getrower(u)
|
||||
r.defaulttimezone=defaulttimezone
|
||||
r.weightcategory = weightcategory
|
||||
r.getemailnotifications = getemailnotifications
|
||||
@@ -11110,13 +11223,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',
|
||||
{
|
||||
|
||||
@@ -94,6 +94,7 @@ MIDDLEWARE_CLASSES = [
|
||||
'async_messages.middleware.AsyncMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'tz_detect.middleware.TimezoneMiddleware',
|
||||
'rowers.middleware.GDPRMiddleWare',
|
||||
'rowers.middleware.PowerTimeFitnessMetricMiddleWare',
|
||||
]
|
||||
|
||||
@@ -118,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': [
|
||||
|
||||
@@ -26,9 +26,7 @@
|
||||
<script type="text/javascript" src="/static/admin/js/inlines.js"></script>
|
||||
<script src="/static/cookielaw/js/cookielaw.js"></script>
|
||||
{% analytical_head_top %}
|
||||
{% if GOOGLE_ANALYTICS_PROPERTY_ID %}
|
||||
{% include "ga.html" %}
|
||||
{% endif %}
|
||||
|
||||
<link rel="stylesheet" href="/static/css/bokeh-0.12.3.min.css" type="text/css" />
|
||||
<link rel="stylesheet" href="/static/css/bokeh-widgets-0.12.3.min.css" type="text/css" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user