From c3504bd86e2baf72a435117b995d0ab00c1452f3 Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Thu, 31 Oct 2024 10:30:23 +0100
Subject: [PATCH] adding functionality to make it easier to do timed courses
---
rowers/templates/course_view.html | 19 ++++++
rowers/templates/workout_form.html | 12 ++++
rowers/templatetags/rowerfilters.py | 12 ++++
rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes
rowers/urls.py | 4 ++
rowers/views/racesviews.py | 3 +-
rowers/views/workoutviews.py | 89 +++++++++++++++++++++-----
7 files changed, 123 insertions(+), 16 deletions(-)
diff --git a/rowers/templates/course_view.html b/rowers/templates/course_view.html
index decc0089..4a610e83 100644
--- a/rowers/templates/course_view.html
+++ b/rowers/templates/course_view.html
@@ -111,6 +111,15 @@
+ {% if workoutid %}
+
+
+ Suggested Workout: {{ workoutid|getworkoutname }}
+
Measure time on course
+
+
+
+ {% endif %}
{% if form %}
@@ -136,6 +145,16 @@
{% endif %}
+ {% else %}
+ {% if workoutid %}
+
+
+ Suggested Workout: {{ workoutid|getworkoutname }}
+
Measure time on course
+
+
+
+ {% endif %}
{% endif %}
{% if ownrecords %}
diff --git a/rowers/templates/workout_form.html b/rowers/templates/workout_form.html
index ac019062..98034e21 100644
--- a/rowers/templates/workout_form.html
+++ b/rowers/templates/workout_form.html
@@ -213,6 +213,18 @@
{% endif %}
+ {% if suggested_courses %}
+
+ Suggested Measured Courses
+ {% for course in suggested_courses %}
+
+ {{ course }}
+
See course
+
Measure time on course
+
+ {% endfor %}
+
+{% endif %}
{% for graph in graphs %}
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index 2d4af95e..60a97c29 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -48,6 +48,16 @@ from six import string_types
register = template.Library()
+@register.filter
+def getworkoutname(id):
+ try:
+ w = Workout.objects.get(id=encoder.decode_hex(id))
+ return w
+ except Workout.DoesNotExist:
+ return ''
+
+ return ''
+
@register.filter
def workoutdate(id): # pragma: no cover
try:
@@ -56,6 +66,8 @@ def workoutdate(id): # pragma: no cover
except Workout.DoesNotExist:
return 'unknown'
+ return 'unknown'
+
@register.filter
def usermessages(rower):
try:
diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz
index 96de089261eaba1ffcf28004d7b41f4b18171ba8..5cf8d9a455ab06f2eb612d9828f15a01404b6104 100644
GIT binary patch
delta 3914
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
Y<;BDFG+v}jAKv*N+UkM11jK*=0GY7rX#fBK
delta 3914
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
YFCL|*@khG!;hp~hzSd-b1jK*=0Ke$+@&Et;
diff --git a/rowers/urls.py b/rowers/urls.py
index afe89f63..18b28d66 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -366,6 +366,8 @@ urlpatterns = [
views.course_follow_view, name='course_follow_view'),
re_path(r'^courses/(?P\d+)/unfollow/',
views.course_unfollow_view, name='course_unfollow_view'),
+ re_path(r'^courses/(?P\d+)/workout/(?P\b[0-9A-Fa-f]+\b)',
+ views.course_view, name='course_view'),
re_path(r'^standards/upload/$', views.standards_upload_view,
name='standards_upload_view'),
re_path(r'^standards/upload/(?P\d+)/$',
@@ -385,6 +387,8 @@ urlpatterns = [
re_path(
r'^user-analysis-select/(?P\w.*)/team/(?P\d+)/workout/(?P\b[0-9A-Fa-f]+\b)/$',
views.analysis_new, name='analysis_new'),
+ re_path(r'workout/(?P\b[0-9A-Fa-f]+\b)/submit/(?P\d+)/$',
+ views.workout_submit_course_view, name='workout_submit_course_view'),
re_path(r'^workouts-dupes-select/user/(?P\d+)/$',
views.workouts_duplicates_select_view, name='workouts_duplicates_select_view'),
re_path(r'^workouts-dupes-select/$',
diff --git a/rowers/views/racesviews.py b/rowers/views/racesviews.py
index 006de323..c233a985 100644
--- a/rowers/views/racesviews.py
+++ b/rowers/views/racesviews.py
@@ -356,7 +356,7 @@ def course_unfollow_view(request, id=0):
url = reverse("courses_view")
return HttpResponseRedirect(url)
-def course_view(request, id=0):
+def course_view(request, id=0, workoutid=0):
try:
course = GeoCourse.objects.get(id=id)
except GeoCourse.DoesNotExist: # pragma: no cover
@@ -477,6 +477,7 @@ def course_view(request, id=0):
'rower': r,
'form': form,
'onlyme': onlyme,
+ 'workoutid': workoutid,
}
)
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 73719594..fe0724b3 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -4540,24 +4540,13 @@ def workout_edit_view(request, id=0, message="", successmessage=""):
hascoordinates = 1
courses = []
- if rowdata != 0:
- try:
- latitude = rowdata.df[' latitude']
- longitude = rowdata.df[' longitude']
- if not latitude.std(): # pragma: no cover
- hascoordinates = 0
- if not longitude.std():
- hascoordinates = 0
- except (KeyError, AttributeError):
- hascoordinates = 0
-
- else: # pragma: no cover
- hascoordinates = 0
+ suggested_courses = []
+ has_latlon, lat_mean, lon_mean = dataprep.workout_has_latlon(row.id)
mapscript = ""
mapdiv = ""
- if hascoordinates:
+ if has_latlon:
try:
mapscript, mapdiv = leaflet_chart(
rowdata.df[' latitude'],
@@ -4570,6 +4559,12 @@ def workout_edit_view(request, id=0, message="", successmessage=""):
workoutid=row.id, userid=row.user.user.id, coursecompleted=True)
if records.count() > 0: # pragma: no cover
courses = list(set([record.course for record in records]))
+ suggested_courses = getnearestcourses([lat_mean, lon_mean], GeoCourse.objects.all(), whatisnear=25,
+ strict=True)
+
+ suggested_courses = list(set(courses) ^ set(suggested_courses))
+
+
breadcrumbs = [
{
@@ -4608,6 +4603,7 @@ def workout_edit_view(request, id=0, message="", successmessage=""):
'mapdiv': mapdiv,
'rower': r,
'courses': courses,
+ 'suggested_courses': suggested_courses,
})
@@ -6102,8 +6098,71 @@ def workout_fusion_view(request, id1=0, id2=1):
'workout2': w2,
})
-# See attached courses
+# See attached courses / attaching courses
+@login_required()
+@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)
+def workout_submit_course_view(request, id, courseid):
+ row = get_workout_by_opaqueid(request, id)
+ r = getrower(request.user)
+ try:
+ course = GeoCourse.objects.get(id=courseid)
+ except GeoCourse.DoesNotExist:
+ url = reverse('workout_edit_view', kwargs={'id': encoder.encode_hex(row.id)})
+ return HttpResponseRedirect(url)
+
+ # got a course
+ 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: # pragma: no cover
+ 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 here')
+
+ url = reverse('workout_edit_view', kwargs={'id': encoder.encode_hex(row.id)})
+ return HttpResponseRedirect(url)
+
@login_required()
@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True)