From 53cbd91e8db77a66e03fb2f92e30ae3edfde07bf Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 1 Jun 2024 15:44:03 +0200
Subject: [PATCH 1/2] adding bulk action assign rowers
---
rowers/forms.py | 10 +++++
rowers/tasks.py | 44 ++++++++++++++++++++++
rowers/templates/workout_bulk_actions.html | 5 +++
rowers/templatetags/rowerfilters.py | 2 +-
rowers/views/statements.py | 4 +-
rowers/views/workoutviews.py | 13 +++++++
6 files changed, 76 insertions(+), 2 deletions(-)
diff --git a/rowers/forms.py b/rowers/forms.py
index 4d6740c3..311f1fa5 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -1213,6 +1213,7 @@ class ForceCurveMultipleCompareForm(forms.Form):
bulkactions = (
('remove','remove'),
('export','export'),
+ ('rower assign','rower assign'),
)
destinations = (
('C2','C2'),
@@ -1230,6 +1231,15 @@ class ExportChoices(forms.Form):
choices=destinations, label='Destination', required=False, initial='strava'
)
+class AssignChoices(forms.Form):
+ remove_workout = forms.BooleanField(initial=False, required=False)
+ destination = forms.ModelMultipleChoiceField(label='Rowers', required=False,
+ queryset=Rower.objects.filter(), widget=forms.CheckboxSelectMultiple())
+
+ def __init__(self, *args, **kwargs):
+ super(AssignChoices, self).__init__(*args, **kwargs)
+ self.fields['destination'].queryset = Rower.objects.filter()
+
class WorkoutMultipleCompareForm(forms.Form):
workouts = forms.ModelMultipleChoiceField(
queryset=Workout.objects.filter(),
diff --git a/rowers/tasks.py b/rowers/tasks.py
index dbf562d4..bb4f04bd 100644
--- a/rowers/tasks.py
+++ b/rowers/tasks.py
@@ -324,6 +324,50 @@ def summaryfromsplitdata(splitdata, data, filename, sep='|', workouttype='rower'
return sums, sa, results
+@app.task
+def handle_assignworkouts(workouts, rowers, remove_workout, debug=False, **kwargs):
+ for workout in workouts:
+ uploadoptions = {
+ 'secret': UPLOAD_SERVICE_SECRET,
+ 'title': workout.name,
+ 'boattype': workout.boattype,
+ 'workouttype': workout.workouttype,
+ 'inboard': workout.inboard,
+ 'oarlength': workout.oarlength,
+ 'summary': workout.summary,
+ 'elapsedTime': 3600.*workout.duration.hour+60*workout.duration.minute+workout.duration.second,
+ 'totalDistance': workout.distance,
+ 'useImpeller': workout.impeller,
+ 'seatNumber': workout.seatnumber,
+ 'boatName': workout.boatname,
+ 'portStarboard': workout.empowerside,
+ }
+ for rower in rowers:
+ failed = False
+ csvfilename = 'media/{code}.csv'.format(code=uuid4().hex[:16])
+ try:
+ with open(csvfilename,'wb') as f:
+ shutil.copy(workout.csvfilename,csvfilename)
+ except FileNotFoundError:
+ try:
+ with open(csvfilename,'wb') as f:
+ csvfilename = csvfilename+'.gz'
+ shutil.copy(workout.csvfilename+'.gz', csvfilename)
+ except:
+ failed = True
+ if not failed:
+ uploadoptions['user'] = rower.user.id
+ uploadoptions['file'] = csvfilename
+ session = requests.session()
+ newHeaders = {'Content-type': 'application/json', 'Accept': 'text/plain'}
+ session.headers.update(newHeaders)
+ response = session.post(UPLOAD_SERVICE_URL, json=uploadoptions)
+ print(response.text)
+ if remove_workout:
+ workout.delete()
+
+ return 1
+
@app.task
def handle_post_workout_api(uploadoptions, debug=False, **kwargs): # pragma: no cover
session = requests.session()
diff --git a/rowers/templates/workout_bulk_actions.html b/rowers/templates/workout_bulk_actions.html
index 4fcb356b..0c50413c 100644
--- a/rowers/templates/workout_bulk_actions.html
+++ b/rowers/templates/workout_bulk_actions.html
@@ -23,6 +23,11 @@
{{ exportchoice.as_p }}
{% endif %}
+ {% if action == 'rower assign' %}
+
+ {{ assignchoices.as_p }}
+
+ {% endif %}
{% csrf_token %}
diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py
index 77eb7a12..2d4af95e 100644
--- a/rowers/templatetags/rowerfilters.py
+++ b/rowers/templatetags/rowerfilters.py
@@ -770,7 +770,7 @@ def team_rowers(user): # pragma: no cover
teams = Team.objects.filter(manager=user)
members = Rower.objects.filter(
team__in=teams).distinct().order_by(
- "user__last_name", "user__last_name").exclude(
+ "user__last_name", "user__first_name").exclude(
rowerplan='freecoach')
return members
except TypeError:
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index fd7c2d99..d134013d 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -116,7 +116,8 @@ from rowers.forms import (
StravaChartForm, FitnessFitForm, PerformanceManagerForm,
TrainingPlanBillingForm, InstantPlanSelectForm,
TrainingZonesForm, InstrokeForm, InStrokeMultipleCompareForm,
- ForceCurveMultipleCompareForm, PlanByRscoreForm
+ ForceCurveMultipleCompareForm, PlanByRscoreForm,
+ AssignChoices,
)
from django.urls import reverse, reverse_lazy
@@ -267,6 +268,7 @@ from rowers.tasks import (
handle_send_email_instantplan_notification,
handle_nk_async_workout,
check_tp_workout_id,
+ handle_assignworkouts,
)
from scipy.signal import savgol_filter
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index 288614a2..ced60404 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -2031,6 +2031,12 @@ def workouts_bulk_actions(request):
'Export to {destination} of workout {id} failed'.format(
id=encoder.encode_hex(w.id),
destination=destination))
+ elif action == 'rower assign':
+ assignchoices = AssignChoices(request.POST)
+ if assignchoices.is_valid():
+ remove_workout = assignchoices.cleaned_data['remove_workout']
+ rowers = assignchoices.cleaned_data['destination']
+ _ = myqueue(queuehigh, handle_assignworkouts, workouts, rowers, remove_workout)
url = reverse('workouts_view')
return HttpResponseRedirect(url)
else: # pragma: no cover
@@ -2041,6 +2047,12 @@ def workouts_bulk_actions(request):
exportchoice = ExportChoices()
actionform = WorkoutBulkActions()
actionform.fields["action"].initial = action
+ assignchoices = AssignChoices()
+ teams = Team.objects.filter(manager=request.user)
+ assignchoices.fields["destination"].queryset = Rower.objects.filter(
+ team__in=teams
+ ).distinct().order_by("user__last_name", "user__first_name").exclude(rowerplan='freecoach')
+ assignchoices.fields["destination"].initial = []
form = WorkoutMultipleCompareForm()
form.fields["workouts"].queryset = Workout.objects.filter(id__in=[w.id for w in workouts])
form.fields["workouts"].initial = workouts
@@ -2049,6 +2061,7 @@ def workouts_bulk_actions(request):
{'action':action,
'exportchoice':exportchoice,
'actionform':actionform,
+ 'assignchoices': assignchoices,
'form':form,
'workouts':workouts})
From 235dc7217b132c23cb09bd26eb5dbc165a82187d Mon Sep 17 00:00:00 2001
From: Sander Roosendaal
Date: Sat, 1 Jun 2024 16:10:52 +0200
Subject: [PATCH 2/2] adding coach assign rower (test)
---
rowers/tests/testdata/testdata.tcx.gz | Bin 4000 -> 4000 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz
index 2c7970968853c425fa962df8780b7db0add8220f..a4fcfdf7f1d9ac77c64fc1190b01838b1e45714a 100644
GIT binary patch
delta 3884
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
u|0xHHD_`uM{Pr;&-r1sFt