diff --git a/rowers/forms.py b/rowers/forms.py
index 3cafb247..8dce9f6d 100644
--- a/rowers/forms.py
+++ b/rowers/forms.py
@@ -1265,6 +1265,25 @@ class ForceCurveMultipleCompareForm(forms.Form):
widget=forms.CheckboxSelectMultiple()
)
+bulkactions = (
+ ('remove','remove'),
+ ('export','export'),
+)
+destinations = (
+ ('C2','C2'),
+ ('strava','strava'),
+ ('sporttracks','sporttracks'),
+ ('trainingpeaks','trainingpeaks')
+)
+
+class WorkoutBulkActions(forms.Form):
+ action = forms.ChoiceField(
+ choices=bulkactions, label='Action', required=True, initial='remove')
+
+class ExportChoices(forms.Form):
+ destination = forms.ChoiceField(
+ choices=destinations, label='Destination', required=False, initial='strava'
+ )
class WorkoutMultipleCompareForm(forms.Form):
workouts = forms.ModelMultipleChoiceField(
diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html
index 28475a29..beeafdad 100644
--- a/rowers/templates/list_workouts.html
+++ b/rowers/templates/list_workouts.html
@@ -79,108 +79,134 @@
{% endif %}
{% endif %}
+ {% if not request.GET.selectworkouts %}
+
+ Bulk Actions
+
+ {% else %}
+
+ Close Bulk Actions
+
+ {% endif %}
{% if workouts %}
- {% for workout in workouts %}
-
- {{ workout.date|date:"Y-m-d" }} {{ workout.starttime|date:"H:i" }}
- {% if workout.duplicate %}
-
+ {% if request.GET.selectworkouts %}
+
+
+ {% endif %}
+
+ {% for workout in workouts %}
+
+ {% if request.GET.selectworkouts %}
+
+ {% endif %}
+ {{ workout.date|date:"Y-m-d" }} {{ workout.starttime|date:"H:i" }}
+ {% if workout.duplicate %}
+
+ {% endif %}
+ {% if workout.rankingpiece %}
+
+ {% endif %}
+ {% if workout.rpe == 0 and not workout.duplicate %}
+
+ No RPE
+
+ {% endif %}
+
+ {% if workout.name != '' %}
+
+
+
+ {% endfor %}
{% else %}
No workouts found
{% endif %}
+ {% if request.GET.selectworkouts %}
+
+ {% endif %}
Filter on date
+
+
+
+{% endblock %}
+
+{% block sidebar %}
+{% include 'menu_workout.html' %}
+{% endblock %}
diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz
index b10382b5..a8f6a010 100644
Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ
diff --git a/rowers/urls.py b/rowers/urls.py
index a784096c..6824aa6f 100644
--- a/rowers/urls.py
+++ b/rowers/urls.py
@@ -292,6 +292,9 @@ urlpatterns = [
views.agegrouprecordview, name='agegrouprecordview'),
re_path(r'^agegrouprecords/$',
views.agegrouprecordview, name='agegrouprecordview'),
+ re_path(r'^workouts/actions/$',
+ views.workouts_bulk_actions,
+ name='workouts_bulk_actions'),
re_path(r'^workouts/setrpe/$', views.workouts_setrpe_view,
name='workouts_setrpe_view'),
re_path(r'^workouts/setrpe/user/(?P\d+)/$', views.workouts_setrpe_view,
diff --git a/rowers/views/statements.py b/rowers/views/statements.py
index 5c07b20e..4c7706e3 100644
--- a/rowers/views/statements.py
+++ b/rowers/views/statements.py
@@ -96,6 +96,7 @@ from django.http import (
)
from django.contrib.auth import authenticate, login, logout
from rowers.forms import (
+ WorkoutBulkActions, ExportChoices,
ForceCurveOptionsForm, HistoForm, TeamMessageForm,
LoginForm, DocumentsForm, UploadOptionsForm, ImageForm, CourseForm,
CourseConfirmForm, ResampleForm,
diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py
index a1e4d704..1b2705ac 100644
--- a/rowers/views/workoutviews.py
+++ b/rowers/views/workoutviews.py
@@ -2,6 +2,7 @@ from shutil import copyfile
from rowers.views.statements import *
import rowers.mytypes as mytypes
+from rowers.integrations import importsources
import numpy
import rowers.uploads as uploads
import rowers.utils as utils
@@ -2031,6 +2032,63 @@ def workouts_setrpe_view(request,userid=0):
})
+# Workout bulk actions
+@login_required()
+def workouts_bulk_actions(request):
+ r = getrower(request.user)
+ action = request.session['action']
+ workoutids = request.session['ids']
+ workouts = []
+ try:
+ for encid in workoutids:
+ w = get_workout_by_opaqueid(request, encid)
+ if w.user == r:
+ workouts.append(w)
+ except KeyError:
+ pass
+
+ if request.method == 'POST':
+ actionform = WorkoutBulkActions(request.POST)
+ form = WorkoutMultipleCompareForm(request.POST)
+ if form.is_valid() and actionform.is_valid():
+ workouts = form.cleaned_data['workouts']
+ action = actionform.cleaned_data['action']
+ if action == 'remove':
+ for w in workouts:
+ messages.info(request,'Removed workout '+str(encoder.encode_hex(w.id)))
+ w.delete()
+ elif action == 'export':
+ exportchoice = ExportChoices(request.POST)
+ if exportchoice.is_valid():
+ destination = exportchoice.cleaned_data['destination']
+ for w in workouts:
+ integration = importsources[destination](request.user)
+ try:
+ id = integration.workout_export(w)
+ messages.info(request,'Workout {id} exported to {destination}'.format(id=encoder.encode_hex(w.id),
+ destination=destination))
+ except NoTokenError:
+ messages.error(request,'Export to {destination} of workout {id} failed'.format(id=encoder.encode_hex(w.id),
+ destination=destination))
+ url = reverse('workouts_view')
+ return HttpResponseRedirect(url)
+
+ else:
+ exportchoice = ExportChoices()
+ actionform = WorkoutBulkActions()
+ actionform.fields["action"].initial = action
+ form = WorkoutMultipleCompareForm()
+ form.fields["workouts"].queryset = Workout.objects.filter(id__in=[w.id for w in workouts])
+ form.fields["workouts"].initial = workouts
+
+
+ return render(request,'workout_bulk_actions.html',
+ {'action':action,
+ 'exportchoice':exportchoice,
+ 'actionform':actionform,
+ 'form':form,
+ 'workouts':workouts})
+
# List Workouts
@login_required()
def workouts_view(request, message='', successmessage='',
@@ -2047,7 +2105,13 @@ def workouts_view(request, message='', successmessage='',
enddate = datetime.datetime.combine(enddate, datetime.time(23, 59, 59))
query = None
+
if request.method == 'POST':
+ if 'selectworkouts' in request.POST:
+ request.session['action']=request.POST['action']
+ request.session['ids'] = request.POST.getlist('workoutid')
+ url = reverse('workouts_bulk_actions')
+ return HttpResponseRedirect(url)
dateform = DateRangeForm(request.POST)
searchform = SearchForm(request.POST)
if dateform.is_valid(): # pragma: no cover
@@ -2227,10 +2291,13 @@ def workouts_view(request, message='', successmessage='',
Click here to update them. \
You can switch off this warning in settings.')
+ actionform = WorkoutBulkActions()
+
return render(request, 'list_workouts.html',
{'workouts': workouts,
'active': 'nav-workouts',
'rower': r,
+ 'actionform': actionform,
'searchform': searchform,
'breadcrumbs': breadcrumbs,
'dateform': dateform,