Private
Public Access
1
0

Merge branch 'release/v16.6.2'

This commit is contained in:
Sander Roosendaal
2021-10-14 13:27:40 +02:00
14 changed files with 372 additions and 21 deletions

View File

@@ -9,6 +9,7 @@ import zipfile
from zipfile import BadZipFile from zipfile import BadZipFile
import re import re
import time import time
import traceback
from time import strftime from time import strftime
import requests import requests
@@ -192,26 +193,48 @@ class Command(BaseCommand):
failedmailbox = Mailbox.objects.get(name='Failed') failedmailbox = Mailbox.objects.get(name='Failed')
# Polar # Polar
try:
polar_available = polarstuff.get_polar_notifications() polar_available = polarstuff.get_polar_notifications()
res = polarstuff.get_all_new_workouts(polar_available) res = polarstuff.get_all_new_workouts(polar_available)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
dologging('processemail.log',''.join('!! ' + line for line in lines))
# Concept2 # Concept2
try:
rowers = Rower.objects.filter(c2_auto_import=True) rowers = Rower.objects.filter(c2_auto_import=True)
for r in rowers: # pragma: no cover for r in rowers: # pragma: no cover
if user_is_not_basic(r.user): if user_is_not_basic(r.user):
c2stuff.get_c2_workouts(r) c2stuff.get_c2_workouts(r)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
dologging('processemail.log',''.join('!! ' + line for line in lines))
try:
rowers = Rower.objects.filter(rp3_auto_import=True) rowers = Rower.objects.filter(rp3_auto_import=True)
for r in rowers: # pragma: no cover for r in rowers: # pragma: no cover
if user_is_not_basic(r.user): if user_is_not_basic(r.user):
res = rp3stuff.get_rp3_workouts(r) res = rp3stuff.get_rp3_workouts(r)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
dologging('processemail.log',''.join('!! ' + line for line in lines))
try:
rowers = Rower.objects.filter(nk_auto_import=True) rowers = Rower.objects.filter(nk_auto_import=True)
for r in rowers: # pragma: no cover for r in rowers: # pragma: no cover
if user_is_not_basic(r.user): if user_is_not_basic(r.user):
s = 'Starting NK Auto Import for user {id}'.format(id=r.user.id) s = 'Starting NK Auto Import for user {id}'.format(id=r.user.id)
dologging('nklog.log',s) dologging('nklog.log',s)
res = nkstuff.get_nk_workouts(r) res = nkstuff.get_nk_workouts(r)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
dologging('processemail.log',''.join('!! ' + line for line in lines))
messages = Message.objects.filter(mailbox_id = workoutmailbox.id) messages = Message.objects.filter(mailbox_id = workoutmailbox.id)
message_ids = [m.id for m in messages] message_ids = [m.id for m in messages]

View File

@@ -904,6 +904,9 @@ class Rower(models.Model):
getimportantemails = models.BooleanField(default=True, getimportantemails = models.BooleanField(default=True,
verbose_name='Get Important Emails') verbose_name='Get Important Emails')
share_course_results = models.BooleanField(default=True,
verbose_name = 'Share Course Results')
sex = models.CharField(default="not specified", sex = models.CharField(default="not specified",
max_length=30, max_length=30,
@@ -4259,6 +4262,7 @@ class AccountRowerForm(ModelForm):
'adaptiveclass', 'adaptiveclass',
'getemailnotifications', 'getemailnotifications',
'getimportantemails', 'getimportantemails',
'share_course_results',
'defaulttimezone','showfavoritechartnotes', 'defaulttimezone','showfavoritechartnotes',
'fav_analysis', 'fav_analysis',
'usersmooth', 'usersmooth',

View File

@@ -137,7 +137,8 @@ def get_rp3_workouts(rower,do_async=True): # pragma: no cover
if (res.status_code != 200): if (res.status_code != 200):
return 0 return 0
dologging('rp3_import.log',res.json()) s = '{d}'.format(d=res.json())
dologging('rp3_import.log',s)
workouts_list = pd.json_normalize(res.json()['data']['workouts']) workouts_list = pd.json_normalize(res.json()['data']['workouts'])
try: try:
rp3ids = workouts_list['id'].values rp3ids = workouts_list['id'].values

View File

@@ -515,7 +515,6 @@ def handle_check_race_course(self,
logfile = 'courselog_{workoutid}_{courseid}.log'.format(workoutid=workoutid,courseid=courseid) logfile = 'courselog_{workoutid}_{courseid}.log'.format(workoutid=workoutid,courseid=courseid)
if 'debug' in kwargs: # pragma: no cover if 'debug' in kwargs: # pragma: no cover
debug = kwargs['debug'] debug = kwargs['debug']
else: else:
@@ -544,6 +543,10 @@ def handle_check_race_course(self,
if 'summary' in kwargs: if 'summary' in kwargs:
summary = kwargs['summary'] summary = kwargs['summary']
successemail = False
if 'successemail' in kwargs:
successemail = kwargs['successemail']
columns = ['time',' latitude',' longitude','cum_dist'] columns = ['time',' latitude',' longitude','cum_dist']
try: try:
@@ -742,7 +745,6 @@ def handle_check_race_course(self,
result = conn.execute(query) result = conn.execute(query)
if summary: if summary:
try: try:
row = rdata(csvfile=f1) row = rdata(csvfile=f1)
except IOError: # pragma: no cover except IOError: # pragma: no cover
@@ -771,6 +773,11 @@ def handle_check_race_course(self,
conn.close() conn.close()
engine.dispose() engine.dispose()
if successemail:
handle_sendemail_coursesucceed(
useremail, userfirstname, logfile, workoutid
)
os.remove(logfile) os.remove(logfile)
return 1 return 1
@@ -825,6 +832,8 @@ def handle_check_race_course(self,
return 2 return 2
return 0 # pragma: no cover return 0 # pragma: no cover
@@ -1463,6 +1472,32 @@ def handle_sendemail_raceregistration(
return 1 return 1
def handle_sendemail_coursesucceed(
useremail, username, logfile, workoutid, **kwargs
):
if 'debug' in kwargs: # pragma: no cover
debug = kwargs['debug']
else:
debug = True
subject = "The validation of your course has succeeded"
from_email = 'Rowsandall <info@rowsandall.com>'
d = {
'username':username,
'workoutid':encoder.encode_hex(workoutid),
}
res = send_template_email(from_email,[useremail],
subject,
'trajectorysuccessemail.html',
d,
attach_file=logfile,
**kwargs)
return 1
def handle_sendemail_coursefail( def handle_sendemail_coursefail(
useremail, username, logfile, **kwargs): useremail, username, logfile, **kwargs):

View File

@@ -95,6 +95,13 @@
<p> <p>
<h2>Filter Results</h2> <h2>Filter Results</h2>
</p> </p>
<p>
{% if onlyme %}
<a href="/rowers/courses/{{ course.id }}/">All Results</a>
{% else %}
<a href="/rowers/courses/{{ course.id}}/?onlyme=true">Only My Results</a>
{% endif %}
</p>
<p> <p>
<form id="result_filter_form", method="post"> <form id="result_filter_form", method="post">

View File

@@ -26,6 +26,18 @@
<i class="fas fa-pause fa-fw"></i>&nbsp;Intervals <i class="fas fa-pause fa-fw"></i>&nbsp;Intervals
</a> </a>
</li> </li>
{% if workout|water %}
<li id="workout-courses">
<a href="/rowers/workout/{{ workout.id|encode }}/courses/">
<i class="fas fa-route fa-fw"></i>&nbsp;Measured Courses
</a>
</li>
{% endif %}
<li>
<a href="/rowers/workout/{{ workout.id|encode }}/editintervals/">
<i class="fas fa-pause fa-fw"></i>&nbsp;Intervals
</a>
</li>
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<li id="workout-comments"> <li id="workout-comments">

View File

@@ -190,6 +190,15 @@
The summary will be updated to show time on course, and you can compare this with other The summary will be updated to show time on course, and you can compare this with other
attempts. attempts.
</p> </p>
<p>
{% if rower.share_course_results %}
You are currently sharing your course results with all Rowsandall users.
Click <a href="/rowers/me/edit/?courseshare=false">here</a> to hide your course results.
{% else %}
You are currently hiding your course results (except for your participation in online challenges).
Click <a href="/rowers/me/edit/?courseshare=true">here</a> to hide your course results.
{% endif %}
</p>
<form ecntype="multipart/form-data" method="post"> <form ecntype="multipart/form-data" method="post">
<table> <table>
{{ courseselectform.as_table }} {{ courseselectform.as_table }}

View File

@@ -0,0 +1,18 @@
{% extends "emailbase.html" %}
{% block body %}
<p>Dear <strong>{{ username }}</strong>,</p>
<p>
We have successfully validated the course for your
<a href="https://rowsandall.com/rowers/workout/{{ workoutid }}/">workout</a>.
</p>
<p>
The attachment contains the validation log.
</p>
<p>
Best Regards, the Rowsandall Team
</p>
{% endblock %}

View File

@@ -0,0 +1,106 @@
{% extends "newbase.html" %}
{% load static %}
{% load rowerfilters %}
{% load leaflet_tags %}
{% block meta %}
{% leaflet_js %}
{% leaflet_css %}
{% endblock %}
{% block scripts %}
{% endblock %}
{% block title %}{{ workout.name }} {% endblock %}
{% block og_title %}{{ workout.name }} {% endblock %}
{% block description %}{{ workout.name }}
{{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %}
{% block og_description %}{{ workout.name }}
{{ workout.date }} - {{ workout.distance }}m - {{ workout.duration |durationprint:"%H:%M:%S.%f" }}{% endblock %}
{% block main %}
<h1>{{ workout.name }}</h1>
<ul class="main-content">
{% if records %}
<li class="grid_4">
<h2>Course Results</h2>
<table class="listtable shortpadded">
<thead>
<tr>
<th>Course</th>
<th>Time</th>
<th>Distance</th>
</tr>
</thead>
<tbody>
{% for record in records %}
<tr>
<td><a href="/rowers/courses/{{ record.course.id }}">{{ record.course.name }}</a></td>
<td>{{ record.duration |durationprint:"%H:%M:%S.%f" }}</td>
<td>{{ record.distance }} m</td>
{% endfor %}
</tbody>
</table>
</li>
{% endif %}
{% if courses %}
<li class="grid_4">
<h2>Add a course</h2>
<p>
This functionality allows you to record a time on a set course that you've rowed during the workout.
The summary will be updated to show time on course, and you can compare this with other
attempts.
</p>
<p>
{% if rower.share_course_results %}
You are currently sharing your course results with all Rowsandall users.
Click <a href="/rowers/me/edit/?courseshare=false">here</a> to hide your course results.
{% else %}
You are currently hiding your course results (except for your participation in online challenges).
Click <a href="/rowers/me/edit/?courseshare=true">here</a> to hide your course results.
{% endif %}
</p>
<form ecntype="multipart/form-data" method="post">
<table>
{{ courseselectform.as_table }}
</table>
{% csrf_token %}
<input class="button" type="submit" value="Select Course">
</form>
</li>
{% endif %}
<li class="grid_2">
<h2>Workout Summary</h2>
<table width=100%>
<tr>
<th>Rower:</th><td>{{ first_name }} {{ last_name }}</td>
</tr><tr>
<tr>
<th>Name:</th><td>{{ workout.name }}</td>
</tr><tr>
<tr>
<th>Date:</th><td>{{ workout.date }}</td>
</tr><tr>
<th>Time:</th><td>{{ workout.starttime }}</td>
</tr><tr>
<th>Distance:</th><td>{{ workout.distance }}m</td>
</tr><tr>
<th>Duration:</th><td>{{ workout.duration |durationprint:"%H:%M:%S.%f" }}</td>
</tr><tr>
<th>Type:</th><td>{{ workout.workouttype }}</td>
</tr><tr>
<th>Weight Category:</th><td>{{ workout.weightcategory }}</td>
</tr>
</table>
</li>
</ul>
{% endblock %}
{% block sidebar %}
{% include 'menu_workout.html' %}
{% endblock %}

View File

@@ -650,6 +650,8 @@ urlpatterns = [
re_path(r'^register/thankyou/$', TemplateView.as_view(template_name='registerthankyou.html'), name='registerthankyou'), re_path(r'^register/thankyou/$', TemplateView.as_view(template_name='registerthankyou.html'), name='registerthankyou'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/workflow/$',views.workout_workflow_view, re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/workflow/$',views.workout_workflow_view,
name='workout_workflow_view'), name='workout_workflow_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/courses/$',views.workout_course_view,
name='workout_course_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>[\w\ ]+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/(?P<plottype>\w+)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>[\w\ ]+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/(?P<plottype>\w+)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/(?P<plottype>\w+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/(?P<plottype>\w+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),
re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'), re_path(r'^workout/(?P<id>\b[0-9A-Fa-f]+\b)/flexchart/(?P<xparam>\w+.*)/(?P<yparam1>[\w\ ]+.*)/(?P<yparam2>[\w\ ]+.*)/$',views.workout_flexchart3_view,name='workout_flexchart3_view'),

View File

@@ -232,6 +232,22 @@ def course_view(request,id=0):
workoutid__isnull=False, workoutid__isnull=False,
coursecompleted=True).order_by("duration","-distance") coursecompleted=True).order_by("duration","-distance")
notsharing = Rower.objects.filter(share_course_results=False).exclude(id=r.id)
notsharing_ids = [o.user.id for o in notsharing]
records = records.exclude(userid__in=notsharing_ids)
if 'onlyme' in request.GET:
onlyme = request.GET.get('onlyme',False)
if onlyme == 'true':
onlyme = True
if onlyme:
records = records.filter(userid=r.user.id)
else:
onlyme=False
form = RaceResultFilterForm(records=records,groups=False) form = RaceResultFilterForm(records=records,groups=False)
if request.method == 'POST': if request.method == 'POST':
form = RaceResultFilterForm(request.POST,records=records,groups=False) form = RaceResultFilterForm(request.POST,records=records,groups=False)
@@ -305,6 +321,7 @@ def course_view(request,id=0):
'records':records, 'records':records,
'rower':r, 'rower':r,
'form':form, 'form':form,
'onlyme':onlyme,
} }
) )

View File

@@ -765,6 +765,7 @@ verbose_job_status = {
'long_test_task2': 'Long Test Task 2', 'long_test_task2': 'Long Test Task 2',
'update_empower': 'Correct Empower Inflated Power Bug', 'update_empower': 'Correct Empower Inflated Power Bug',
'submit_race': 'Checking Race Course Result', 'submit_race': 'Checking Race Course Result',
'check_race_course': 'Checking Course Result',
} }
def get_job_status(jobid): # pragma: no cover def get_job_status(jobid): # pragma: no cover

View File

@@ -396,6 +396,15 @@ def rower_exportsettings_view(request,userid=0):
def rower_edit_view(request,rowerid=0,userid=0,message=""): def rower_edit_view(request,rowerid=0,userid=0,message=""):
r = getrequestrowercoachee(request,rowerid=rowerid,userid=userid,notpermanent=True) r = getrequestrowercoachee(request,rowerid=rowerid,userid=userid,notpermanent=True)
if 'courseshare' in request.GET:
courseshare = request.GET.get('courseshare',"ok")
if courseshare == 'true':
r.share_course_results = True
r.save()
elif courseshare == 'false':
r.share_course_results = False
r.save()
rowerid = r.id rowerid = r.id
breadcrumbs = [ breadcrumbs = [
@@ -435,6 +444,7 @@ def rower_edit_view(request,rowerid=0,userid=0,message=""):
showfavoritechartnotes = cd['showfavoritechartnotes'] showfavoritechartnotes = cd['showfavoritechartnotes']
getemailnotifications = cd['getemailnotifications'] getemailnotifications = cd['getemailnotifications']
getimportantemails = cd['getimportantemails'] getimportantemails = cd['getimportantemails']
share_course_results = cd['share_course_results']
defaulttimezone=cd['defaulttimezone'] defaulttimezone=cd['defaulttimezone']
fav_analysis = cd['fav_analysis'] fav_analysis = cd['fav_analysis']
usersmooth = cd['usersmooth'] usersmooth = cd['usersmooth']
@@ -461,6 +471,7 @@ def rower_edit_view(request,rowerid=0,userid=0,message=""):
r.offercoaching = offercoaching r.offercoaching = offercoaching
r.defaultlandingpage = defaultlandingpage r.defaultlandingpage = defaultlandingpage
r.showfavoritechartnotes = showfavoritechartnotes r.showfavoritechartnotes = showfavoritechartnotes
r.share_course_results = share_course_results
r.sex = sex r.sex = sex
r.birthdate = birthdate r.birthdate = birthdate
r.autojoin = autojoin r.autojoin = autojoin

View File

@@ -6058,6 +6058,104 @@ def workout_fusion_view(request,id1=0,id2=1):
'workout2':w2, 'workout2':w2,
}) })
# See attached courses
@login_required()
@permission_required('workout.change_workout',fn=get_workout_by_opaqueid, raise_exception=True)
def workout_course_view(request, id):
row = get_workout_by_opaqueid(request,id)
r = getrower(request.user)
breadcrumbs = [
{
'url':'/rowers/list-workouts/',
'name':'Workouts'
},
{
'url':get_workout_default_page(request,encoder.encode_hex(row.id)),
'name': row.name
},
{
'url':reverse('workout_course_view',kwargs={'id':id}),
'name': 'Measured Courses'
}
]
courses = []
courseselectform = CourseSelectForm()
has_latlon,lat_mean,lon_mean = dataprep.workout_has_latlon(row.id)
if has_latlon:
courses = getnearestcourses([lat_mean,lon_mean],GeoCourse.objects.all(),whatisnear=25,
strict=True)
courseselectform = CourseSelectForm(choices=courses)
if request.method == 'POST':
courseselectform = CourseSelectForm(request.POST,choices=courses)
if courseselectform.is_valid():
course = courseselectform.cleaned_data['course']
# get or create a record
records = VirtualRaceResult.objects.filter(
userid=r.id,
course=course,
workoutid=row.id
)
if records:
record = records[0]
else:
# create record
record = VirtualRaceResult(
userid = r.id,
username = r.user.first_name+' '+r.user.last_name,
workoutid = row.id,
weightcategory = r.weightcategory,
adaptiveclass = r.adaptiveclass,
course = course,
distance = course.distance,
boatclass = row.workouttype,
boattype = row.boattype,
sex = r.sex,
age = calculate_age(r.birthdate),
)
record.save()
job = myqueue(
queuehigh,
handle_check_race_course,
row.csvfilename,
row.id,
course.id,
record.id,
r.user.email,
r.user.first_name,
summary=True,
successemail=True,
)
try:
request.session['async_tasks'] += [(job.id,'check_race_course')]
except KeyError:
request.session['async_tasks'] = [(job.id,'check_race_course')]
messages.info(request,'We are checking your time on the course in the background. You will receive an email when the check is complete. You can check the status <a href="/rowers/jobs-status/" target="_blank">here</a>')
# get results
records = VirtualRaceResult.objects.filter(
course__isnull=False,
workoutid=row.id,
coursecompleted=True).order_by("duration","-distance")
return render(request, 'workout_courses.html',
{'workout':row,
'rower':r,
'breadcrumbs':breadcrumbs,
'active':'nav-workouts',
'teams':get_my_teams(request.user),
'courses':courses,
'courseselectform':courseselectform,
'records':records,
})
# Edit the splits/summary # Edit the splits/summary
@login_required() @login_required()
@@ -6218,7 +6316,7 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
record.save() record.save()
job = myqueue( job = myqueue(
queue, queuehigh,
handle_check_race_course, handle_check_race_course,
row.csvfilename, row.csvfilename,
row.id, row.id,
@@ -6227,8 +6325,15 @@ def workout_summary_edit_view(request,id,message="",successmessage=""
r.user.email, r.user.email,
r.user.first_name, r.user.first_name,
summary=True, summary=True,
successemail=True,
) )
messages.info(request,'We are checking your time on the course in the background')
try:
request.session['async_tasks'] += [(job.id,'check_race_course')]
except KeyError:
request.session['async_tasks'] = [(job.id,'check_race_course')]
messages.info(request,'We are checking your time on the course in the background. You will receive an email when the check is complete. You can check the status <a href="/rowers/jobs-status/" target="_blank">here</a>')
vals = None vals = None
# feeling lucky / ruptures # feeling lucky / ruptures