Private
Public Access
1
0

Merge branch 'release/v6.1'

This commit is contained in:
Sander Roosendaal
2018-03-07 22:12:34 +01:00
18 changed files with 810 additions and 93 deletions
+63 -4
View File
@@ -65,6 +65,8 @@ queue = django_rq.get_queue('default')
queuelow = django_rq.get_queue('low') queuelow = django_rq.get_queue('low')
queuehigh = django_rq.get_queue('default') queuehigh = django_rq.get_queue('default')
from rowsandall_app.settings import SITE_URL
user = settings.DATABASES['default']['USER'] user = settings.DATABASES['default']['USER']
password = settings.DATABASES['default']['PASSWORD'] password = settings.DATABASES['default']['PASSWORD']
@@ -131,6 +133,66 @@ def get_latlon(id):
return [pd.Series([]), pd.Series([])] 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): def get_workouts(ids, userid):
goodids = [] goodids = []
@@ -1510,13 +1572,9 @@ def repair_data(verbose=False):
res = data.to_csv(w.csvfilename + '.gz', res = data.to_csv(w.csvfilename + '.gz',
index_label='index', index_label='index',
compression='gzip') compression='gzip')
print 'adding csv file'
else: else:
print w.id, ' No stroke records anywhere'
w.delete() w.delete()
except: except:
print 'failed'
print str(sys.exc_info()[0])
pass pass
# A wrapper around the rowingdata class, with some error catching # 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): def getrowdata_db(id=0, doclean=False, convertnewtons=True):
data = read_df_sql(id) data = read_df_sql(id)
data['x_right'] = data['x_right'] / 1.0e6 data['x_right'] = data['x_right'] / 1.0e6
data['deltat'] = data['time'].diff()
if data.empty: if data.empty:
rowdata, row = getrowdata(id=id) rowdata, row = getrowdata(id=id)
+25
View File
@@ -71,5 +71,30 @@ class PowerTimeFitnessMetricMiddleWare(object):
result = do_update(request.user,mode='rower') result = do_update(request.user,mode='rower')
result = do_update(request.user,mode='water') 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
View File
@@ -173,6 +173,7 @@ def update_records(url=c2url):
df['Distance'] = df['Event'] df['Distance'] = df['Event']
df['Duration'] = 0 df['Duration'] = 0
print row.Duration
for nr,row in df.iterrows(): for nr,row in df.iterrows():
if 'm' in row['Record']: if 'm' in row['Record']:
df.ix[nr,'Distance'] = row['Record'][:-1] 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') 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 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(): for nr,row in df.iterrows():
try: try:
weightcategory = row.Weight.lower() weightcategory = row.Weight.lower()
@@ -468,6 +468,8 @@ class Rower(models.Model):
('Yoga','Yoga'), ('Yoga','Yoga'),
) )
user = models.OneToOneField(User) user = models.OneToOneField(User)
gdproptin = models.BooleanField(default=False)
gdproptindate = models.DateTimeField(blank=True,null=True)
# Heart Rate Zone data # Heart Rate Zone data
max = models.IntegerField(default=192,verbose_name="Max Heart Rate") max = models.IntegerField(default=192,verbose_name="Max Heart Rate")
@@ -606,6 +608,19 @@ class Rower(models.Model):
def clean_email(self): def clean_email(self):
return self.user.email.lower() 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) @receiver(models.signals.post_save,sender=Rower)
def auto_delete_teams_on_change(sender, instance, **kwargs): def auto_delete_teams_on_change(sender, instance, **kwargs):
if instance.rowerplan != 'coach': if instance.rowerplan != 'coach':
+4
View File
@@ -396,6 +396,10 @@ def get_workouts_session(r,ps):
def update_plannedsession(ps,cd): def update_plannedsession(ps,cd):
for attr, value in cd.items(): for attr, value in cd.items():
if attr == 'comment':
value.replace("\r\n", "&#10");
value.replace("\n", "&#10");
print value
setattr(ps, attr, value) setattr(ps, attr, value)
ps.save() ps.save()
+49
View File
@@ -385,6 +385,24 @@ def handle_sendemail_hard(workoutid, useremail,
return 1 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 # send email to me when an unrecognized file is uploaded
@app.task @app.task
def handle_sendemail_unrecognized(unrecognizedfile, useremail, def handle_sendemail_unrecognized(unrecognizedfile, useremail,
@@ -499,6 +517,37 @@ def handle_zip_file(emailfrom, subject, file,**kwargs):
# Send email with CSV attachment # 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 @app.task
def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs): def handle_sendemailcsv(first_name, last_name, email, csvfile,**kwargs):
-18
View File
@@ -204,24 +204,6 @@
</div> </div>
<!-- end container --> <!-- 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" /> <link rel="stylesheet" href="/static/debug_toolbar/css/print.css" type="text/css" media="print" />
+33
View File
@@ -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 %}
+222
View File
@@ -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 %}
+155 -35
View File
@@ -159,61 +159,181 @@
<h3>Credit</h3> <h3>Credit</h3>
<p>This document was created using a Contractology template available at <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: <h3>Personal information collection</h3>
<ul> <p>
<li>information about your use of this website rowsandall.com may collect and use the following kinds of information:
<li>information that you provide for the purpose of registering with the website <ul>
<li>information about transactions carried out over this website <li>information about your use of 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
<li>any other information that you send to rowsandall.com registering with the website
</ul></p> <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>
<h3>Using personal information</h3> <p>
The site is only accessible to user of 16 years and older.
</p>
<p>rowsandall.com may use your personal information to: <h3>Data Deletion</h3>
<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>
<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>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>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>Data deletion can be initiated by the user through the button on the user settings page.</p>
<h3>Securing your data</h3> <h3>Data Security</h3>
<p>rowsandall.com will take reasonable technical and organisational precautions to prevent the loss, misuse or alteration of your personal information. </p> <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>rowsandall.com will store all the personal information you provide</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>
<h3>Cross-border data transfers</h3> <p>rowsandall.com will take reasonable technical and organisational precautions to prevent the loss,
misuse or alteration of your personal information. </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>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>
<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>You agree to such cross-border transfers of personal information.</p> <h3>Data Sharing and access to data</h3>
<h3>Updating this statement</h3> <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>rowsandall.com may update this privacy policy by posting a new version on this website. </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>You should check this page occasionally to ensure you are familiar with any changes. </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>Other websites</h3>
<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>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>
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>
<p>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>
</div> </div>
{% endblock content %} {% endblock content %}
+9
View File
@@ -31,6 +31,15 @@
<div class="grid_6 omega"> <div class="grid_6 omega">
<p> To use rowsandall, you need to register and agree with the Terms of Service. </p> <p> To use rowsandall, you need to register and agree with the Terms of Service. </p>
<p> Registration is free. </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> </div>
{% endblock content %} {% endblock content %}
+45 -6
View File
@@ -1,11 +1,25 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Change Rower {% endblock %} {% block title %}Change Rower {% endblock %}
{% block content %} {% block content %}
<div class="grid_12 alpha"> <div class="grid_12 alpha">
<h1>User Settings</h1> <div class="grid_8 alpha">
<p><a href="http://analytics.rowsandall.com/2017/11/02/rowsandall-settings-page-tutorial/">Need help? Click to read the tutorial</a></p> <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"> <div class="grid_6 alpha">
<p> <p>
<h2>Heart Rate Zones</h2> <h2>Heart Rate Zones</h2>
@@ -88,7 +102,11 @@
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<div class="grid_2 suffix_4 alpha"> <div class="grid_2 suffix_4 alpha">
<p> <p>
{% if rower.user == user %}
<a class="button gray small" href="/password_change/">Password Change</a> <a class="button gray small" href="/password_change/">Password Change</a>
{% else %}
&nbsp;
{% endif %}
</p> </p>
</div> </div>
</div> </div>
@@ -116,7 +134,7 @@
</table> </table>
{% csrf_token %} {% csrf_token %}
<div class="grid_2 alpha"> <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> <a class="button blue" href="/rowers/promembership">Upgrade</a>
{% else %} {% else %}
&nbsp; &nbsp;
@@ -152,6 +170,7 @@
</div> </div>
</div> </div>
</div> </div>
{% if rower.user == user %}
<div class="grid_12 alpha"> <div class="grid_12 alpha">
<div class="grid_6 alpha"> <div class="grid_6 alpha">
<p> <p>
@@ -161,7 +180,7 @@
</div> </div>
</p> </p>
</div> </div>
<div class="grid_6 omega"> <div class="grid_6 omega">
<p> <p>
<h2>Favorite Charts</h2> <h2>Favorite Charts</h2>
@@ -193,7 +212,27 @@
</div> </div>
<div class="grid_12 alpha"> <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 %} {% if grants %}
<p> <p>
<h2>Applications</h2> <h2>Applications</h2>
@@ -223,7 +262,7 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %}
+1 -1
View File
@@ -54,7 +54,7 @@
<tbody> <tbody>
{% for member in members %} {% for member in members %}
<tr> <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 %} {% if team.manager == user %}
<td><a class="button red small" href="/rowers/me/team/{{ team.id }}/drop/{{ member.user.id }}">Drop</a></td> <td><a class="button red small" href="/rowers/me/team/{{ team.id }}/drop/{{ member.user.id }}">Drop</a></td>
{% else %} {% 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 %}
+22
View File
@@ -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 %}
+6
View File
@@ -121,6 +121,7 @@ urlpatterns = [
url(r'^400/$', TemplateView.as_view(template_name='400.html'),name='400'), 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'^403/$', TemplateView.as_view(template_name='403.html'),name='403'),
url(r'^imports/$', TemplateView.as_view(template_name='imports.html'), name='imports'), 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+)$',views.agegroupcpview),
url(r'^agegroupcp/(?P<age>\d+)/(?P<normalize>\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+)$', 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$',views.multiflex_view), url(r'^user-multiflex$',views.multiflex_view),
url(r'^user-multiflex-data$',views.multiflex_data), 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/teams/$',views.rower_teams_view),
url(r'^me/calcdps/$',views.rower_calcdps_view), url(r'^me/calcdps/$',views.rower_calcdps_view),
url(r'^me/exportsettings/$',views.rower_exportsettings_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/(\w+.*)/$',views.manager_requests_view),
url(r'^me/request/$',views.manager_requests_view), url(r'^me/request/$',views.manager_requests_view),
url(r'^me/edit/$',views.rower_edit_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/edit/(.+.*)/$',views.rower_edit_view),
url(r'^me/c2authorize/$',views.rower_c2_authorize), url(r'^me/c2authorize/$',views.rower_c2_authorize),
url(r'^me/revokeapp/(?P<id>\d+)$',views.rower_revokeapp_view), url(r'^me/revokeapp/(?P<id>\d+)$',views.rower_revokeapp_view),
+135 -24
View File
@@ -12,6 +12,7 @@ import yaml
from PIL import Image from PIL import Image
from numbers import Number from numbers import Number
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.contrib.auth import views as auth_views
from django.db.models import Q from django.db.models import Q
from django import template from django import template
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
@@ -54,7 +55,7 @@ from rowers.forms import (
) )
from rowers.models import ( from rowers.models import (
Workout, User, Rower, WorkoutForm,FavoriteChart, Workout, User, Rower, WorkoutForm,FavoriteChart,
PlannedSession PlannedSession, DeactivateUserForm,DeleteUserForm
) )
from rowers.models import ( from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, 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_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv
from rowers.tasks import ( from rowers.tasks import (
handle_sendemail_unrecognized,handle_sendemailnewcomment, handle_sendemail_unrecognized,handle_sendemailnewcomment,
handle_sendemailsummary,
handle_sendemailnewresponse, handle_updatedps, handle_sendemailnewresponse, handle_updatedps,
handle_updatecp,long_test_task,long_test_task2, handle_updatecp,long_test_task,long_test_task2,
handle_zip_file,handle_getagegrouprecords, handle_zip_file,handle_getagegrouprecords,
handle_updatefitnessmetric handle_updatefitnessmetric,
handle_sendemail_userdeleted,
) )
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
@@ -572,6 +575,82 @@ def get_thumbnails(request,id):
return JSONResponse(charts) 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() @login_required()
def get_testscript(request,id): def get_testscript(request,id):
row = get_workout_permitted(request.user,id) row = get_workout_permitted(request.user,id)
@@ -1704,6 +1783,37 @@ def workout_gpxemail_view(request,id=0):
return response 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 # Get Workout CSV file and send it to user's email address
@login_required() @login_required()
def workout_csvemail_view(request,id=0): def workout_csvemail_view(request,id=0):
@@ -1867,7 +1977,6 @@ def workout_strava_upload_view(request,id=0):
try: try:
os.remove(tcxfile) os.remove(tcxfile)
except WindowsError: except WindowsError:
print tcxfile
pass pass
url = "/rowers/workout/"+str(w.id)+"/edit" url = "/rowers/workout/"+str(w.id)+"/edit"
@@ -3720,7 +3829,6 @@ def rankings_view2(request,theuser=0,
sex = r.sex, sex = r.sex,
weightcategory = r.weightcategory) weightcategory = r.weightcategory)
print len(agerecords),'aap'
if len(agerecords) == 0: if len(agerecords) == 0:
recalc = True recalc = True
wcpower = [] wcpower = []
@@ -7461,7 +7569,7 @@ def workout_stats_view(request,id=0,message="",successmessage=""):
if datadf.empty: if datadf.empty:
return HttpResponse("CSV data file not found") 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] workoutstateswork = [1,4,5,8,9,6,7]
@@ -8134,8 +8242,6 @@ def workout_flexchart3_view(request,*args,**kwargs):
yparam1 = yparam1.replace('/','_slsh_') yparam1 = yparam1.replace('/','_slsh_')
yparam2 = yparam2.replace('/','_slsh_') yparam2 = yparam2.replace('/','_slsh_')
print xparam,yparam1,yparam2,'aap'
try: try:
extrametrics.pop('originalvelo') extrametrics.pop('originalvelo')
except KeyError: except KeyError:
@@ -10866,8 +10972,16 @@ def rower_exportsettings_view(request):
# Page where user can set his details # Page where user can set his details
# Add email address to form so user can change his email address # Add email address to form so user can change his email address
@login_required() @login_required()
def rower_edit_view(request,message=""): def rower_edit_view(request,rowerid=0,message=""):
r = getrower(request.user) 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: if request.method == 'POST' and "ut2" in request.POST:
form = RowerForm(request.POST) form = RowerForm(request.POST)
if form.is_valid(): if form.is_valid():
@@ -10881,7 +10995,6 @@ def rower_edit_view(request,message=""):
an = cd['an'] an = cd['an']
rest = cd['rest'] rest = cd['rest']
try: try:
r = getrower(request.user)
r.max = max(min(hrmax,250),10) r.max = max(min(hrmax,250),10)
r.ut2 = max(min(ut2,250),10) r.ut2 = max(min(ut2,250),10)
r.ut1 = max(min(ut1,250),10) r.ut1 = max(min(ut1,250),10)
@@ -10896,7 +11009,7 @@ def rower_edit_view(request,message=""):
powerform = RowerPowerForm(instance=r) powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r)
accountform = AccountRowerForm(instance=r) accountform = AccountRowerForm(instance=r)
userform = UserForm(instance=request.user) userform = UserForm(instance=r.user)
return render(request, 'rower_form.html', return render(request, 'rower_form.html',
{'form':form, {'form':form,
'powerzonesform':powerzonesform, 'powerzonesform':powerzonesform,
@@ -10916,7 +11029,7 @@ def rower_edit_view(request,message=""):
#form = RowerForm(instance=r) #form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r) powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r)
userform = UserForm(instance=request.user) userform = UserForm(instance=r.user)
accountform = AccountRowerForm(instance=r) accountform = AccountRowerForm(instance=r)
return render(request, 'rower_form.html', return render(request, 'rower_form.html',
{'form':form, {'form':form,
@@ -10938,7 +11051,6 @@ def rower_edit_view(request,message=""):
ftp = cd['ftp'] ftp = cd['ftp']
otwslack = cd['otwslack'] otwslack = cd['otwslack']
try: try:
r = getrower(request.user)
powerfrac = 100*np.array([r.pw_ut2, powerfrac = 100*np.array([r.pw_ut2,
r.pw_ut1, r.pw_ut1,
r.pw_at, r.pw_at,
@@ -10954,7 +11066,10 @@ def rower_edit_view(request,message=""):
r.save() r.save()
message = "FTP and/or OTW slack values changed." message = "FTP and/or OTW slack values changed."
messages.info(request,message) messages.info(request,message)
url = reverse(rower_edit_view) url = reverse(rower_edit_view,
kwargs = {
'rowerid':r.id,
})
response = HttpResponseRedirect(url) response = HttpResponseRedirect(url)
except Rower.DoesNotExist: except Rower.DoesNotExist:
message = "Funny. This user doesn't exist." message = "Funny. This user doesn't exist."
@@ -10966,7 +11081,7 @@ def rower_edit_view(request,message=""):
form = RowerForm(instance=r) form = RowerForm(instance=r)
#powerform = RowerPowerForm(instance=r) #powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r)
userform = UserForm(instance=request.user) userform = UserForm(instance=r.user)
accountform = AccountRowerForm(instance=r) accountform = AccountRowerForm(instance=r)
return render(request, 'rower_form.html', return render(request, 'rower_form.html',
{'form':form, {'form':form,
@@ -10996,7 +11111,6 @@ def rower_edit_view(request,message=""):
anname = cd['anname'] anname = cd['anname']
powerzones = [ut3name,ut2name,ut1name,atname,trname,anname] powerzones = [ut3name,ut2name,ut1name,atname,trname,anname]
try: try:
r = getrower(request.user)
r.pw_ut2 = pw_ut2 r.pw_ut2 = pw_ut2
r.pw_ut1 = pw_ut1 r.pw_ut1 = pw_ut1
r.pw_at = pw_at r.pw_at = pw_at
@@ -11008,7 +11122,7 @@ def rower_edit_view(request,message=""):
messages.info(request,successmessage) messages.info(request,successmessage)
form = RowerForm(instance=r) form = RowerForm(instance=r)
accountform = AccountRowerForm(instance=r) accountform = AccountRowerForm(instance=r)
userform = UserForm(instance=request.user) userform = UserForm(instance=r.user)
powerform = RowerPowerForm(instance=r) powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r)
return render(request, 'rower_form.html', return render(request, 'rower_form.html',
@@ -11030,7 +11144,7 @@ def rower_edit_view(request,message=""):
form = RowerForm(instance=r) form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r) powerform = RowerPowerForm(instance=r)
accountform = AccountRowerForm(instance=r) accountform = AccountRowerForm(instance=r)
userform = UserForm(instance=request.user) userform = UserForm(instance=r.user)
#powerzonesform = RowerPowerZonesForm(instance=r) #powerzonesform = RowerPowerZonesForm(instance=r)
message = HttpResponse("invalid form") message = HttpResponse("invalid form")
return render(request, 'rower_form.html', 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: elif request.method == 'POST' and "weightcategory" in request.POST:
accountform = AccountRowerForm(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(): if accountform.is_valid() and userform.is_valid():
# process # process
cd = accountform.cleaned_data cd = accountform.cleaned_data
@@ -11059,7 +11173,7 @@ def rower_edit_view(request,message=""):
showfavoritechartnotes = cd['showfavoritechartnotes'] showfavoritechartnotes = cd['showfavoritechartnotes']
getemailnotifications = cd['getemailnotifications'] getemailnotifications = cd['getemailnotifications']
defaulttimezone=cd['defaulttimezone'] defaulttimezone=cd['defaulttimezone']
u = request.user u = r.user
if len(first_name): if len(first_name):
u.first_name = first_name u.first_name = first_name
u.last_name = last_name u.last_name = last_name
@@ -11068,7 +11182,6 @@ def rower_edit_view(request,message=""):
u.save() u.save()
r = getrower(u)
r.defaulttimezone=defaulttimezone r.defaulttimezone=defaulttimezone
r.weightcategory = weightcategory r.weightcategory = weightcategory
r.getemailnotifications = getemailnotifications r.getemailnotifications = getemailnotifications
@@ -11110,13 +11223,11 @@ def rower_edit_view(request,message=""):
else: else:
try: try:
r = getrower(request.user)
form = RowerForm(instance=r) form = RowerForm(instance=r)
powerform = RowerPowerForm(instance=r) powerform = RowerPowerForm(instance=r)
powerzonesform = RowerPowerZonesForm(instance=r) powerzonesform = RowerPowerZonesForm(instance=r)
accountform = AccountRowerForm(instance=r) accountform = AccountRowerForm(instance=r)
userform = UserForm(instance=request.user) userform = UserForm(instance=r.user)
grants = AccessToken.objects.filter(user=request.user) grants = AccessToken.objects.filter(user=request.user)
return render(request, 'rower_form.html', return render(request, 'rower_form.html',
{ {
+2 -1
View File
@@ -94,6 +94,7 @@ MIDDLEWARE_CLASSES = [
'async_messages.middleware.AsyncMiddleware', 'async_messages.middleware.AsyncMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'tz_detect.middleware.TimezoneMiddleware', 'tz_detect.middleware.TimezoneMiddleware',
'rowers.middleware.GDPRMiddleWare',
'rowers.middleware.PowerTimeFitnessMetricMiddleWare', 'rowers.middleware.PowerTimeFitnessMetricMiddleWare',
] ]
@@ -118,7 +119,7 @@ TEMPLATES = [
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages', 'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n', 'django.template.context_processors.i18n',
'context_processors.google_analytics', # 'context_processors.google_analytics',
'context_processors.warning_message', 'context_processors.warning_message',
], ],
# 'loaders': [ # 'loaders': [
+1 -3
View File
@@ -26,9 +26,7 @@
<script type="text/javascript" src="/static/admin/js/inlines.js"></script> <script type="text/javascript" src="/static/admin/js/inlines.js"></script>
<script src="/static/cookielaw/js/cookielaw.js"></script> <script src="/static/cookielaw/js/cookielaw.js"></script>
{% analytical_head_top %} {% 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-0.12.3.min.css" type="text/css" />
<link rel="stylesheet" href="/static/css/bokeh-widgets-0.12.3.min.css" type="text/css" /> <link rel="stylesheet" href="/static/css/bokeh-widgets-0.12.3.min.css" type="text/css" />