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 %} +
  • +
    +

    Bulk Operation

    +

    Please select workouts from the list to do a bulk operation on.

    + {{ actionform.as_p }} + {% csrf_token %} + +
  • + {% 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 != '' %} +

    + {% if team %} + + {{ workout.name }} ({{ workout.user.user.first_name }} {{ workout.user.user.last_name}}) + + {% else %} + + {{ workout.name }} + {% endif %} - {% if workout.rankingpiece %} - - {% endif %} - {% if workout.rpe == 0 and not workout.duplicate %} - - No RPE - - {% endif %} -
    - {% if workout.name != '' %} -

    - {% if team %} - - {{ workout.name }} ({{ workout.user.user.first_name }} {{ workout.user.user.last_name}}) - - {% else %} - - {{ workout.name }} - - {% endif %} -

    - {% else %} -

    - {% if team %} - - No Name ({{ workout.user.user.first_name }} {{ workout.user.user.last_name}}) - - {% else %} - - No Name - - {% endif %} -

    - {% endif %} -
    -
    -
    - {% with workout.workouttype|icon|safe as templateName %} - {% include templateName %} - {% endwith %} -
    -
    - Distance
    - {{ workout.distance|distanceprint }} -
    -
    - Time
    - {{ workout.duration |durationprint:"%H:%M:%S.%f" }} -
    -
    - {% if workout|may_edit:request %} - + {% else %} +

    + {% if team %} + + No Name ({{ workout.user.user.first_name }} {{ workout.user.user.last_name}}) + + {% else %} + + No Name + + {% endif %} +

    + {% endif %} +
    +
    +
    + {% with workout.workouttype|icon|safe as templateName %} + {% include templateName %} + {% endwith %} +
    +
    + Distance
    + {{ workout.distance|distanceprint }} +
    +
    + Time
    + {{ workout.duration |durationprint:"%H:%M:%S.%f" }} +
    +
    + {% if workout|may_edit:request %} + - {% else %} -   - {% endif %} -
    -
    - {% if workout|may_edit:request %} - {% if rower.defaultlandingpage2 != 'workout_delete' %} - + {% else %} +   + {% endif %} +
    +
    + {% if workout|may_edit:request %} + {% if rower.defaultlandingpage2 != 'workout_delete' %} + - {% else %} - + {% else %} + - {% endif %} - {% else %} -   - {% endif %} -
    -
    -

  • - - {% endfor %} + {% endif %} + {% else %} +   + {% endif %} + + + + + {% endfor %} {% else %}
  • No workouts found
  • {% endif %} + {% if request.GET.selectworkouts %} + + {% endif %}
  • Filter on date

    diff --git a/rowers/templates/workout_bulk_actions.html b/rowers/templates/workout_bulk_actions.html new file mode 100644 index 00000000..4fcb356b --- /dev/null +++ b/rowers/templates/workout_bulk_actions.html @@ -0,0 +1,39 @@ +{% extends "newbase.html" %} +{% load static %} +{% load rowerfilters %} + +{% block title %}Change Workout {% endblock %} + +{% block main %} +

    Bulk Actions

    +
      +
    • + +

      + Edit and confirm the action you want to perform on the following workouts: +

      +

      + {{ form.as_p }} +

      +

      + {{ actionform.as_p }} +

      + {% if action == 'export' %} +

      + {{ exportchoice.as_p }} +

      + {% endif %} +
    • +
    • + {% csrf_token %} + +
    • + +
    + + +{% 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,