From 95bc02e1225decaeb9723a693acb58a95e044a2f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 22 Jan 2025 20:17:47 +0100 Subject: [PATCH 1/3] step s --- rowers/models.py | 6 + rowers/templates/stepeditor.html | 331 ++++++++++++++++--------------- rowers/views/planviews.py | 3 + 3 files changed, 176 insertions(+), 164 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index b805b234..b69cb380 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2755,6 +2755,12 @@ class PlannedSessionStep(models.Model): return d + # string + def __str__(self): + str = 'Step {id} {name} {intensity}'.format(id=self.pk, name=self.name, intensity=self.intensity) + + return str + class StepEditorForm(ModelForm): class Meta: model = PlannedSessionStep diff --git a/rowers/templates/stepeditor.html b/rowers/templates/stepeditor.html index efc9daf1..9500219b 100644 --- a/rowers/templates/stepeditor.html +++ b/rowers/templates/stepeditor.html @@ -20,9 +20,9 @@

Training Steps for {{ ps.name }}

-

- - + + +

{% for step in currentsteps %} @@ -175,216 +175,219 @@ descriptions["{{ key }}"] = "{{ value }}" {% endfor %} $(document).ready(function() { - csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); + csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); }); function csrfSafeMethod(method) { - // these HTTP methods do not require CSRF protection - return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); + // these HTTP methods do not require CSRF protection + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ - beforeSend: function(xhr, settings) { - if (!csrfSafeMethod(settings.type) && !this.crossDomain) { - xhr.setRequestHeader("X-CSRFToken", csrftoken); + beforeSend: function(xhr, settings) { + if (!csrfSafeMethod(settings.type) && !this.crossDomain) { + xhr.setRequestHeader("X-CSRFToken", csrftoken); + } } - } }); - - let dragged; - let origcolor; - - function saveSession() { - + + let dragged; + let origcolor; + + function saveSession() { + var list = []; steps = document.querySelector('.drop-zone'); steps.childNodes.forEach(function(item) { - if (item.className && item.className.includes("trainingstep")) { - item.childNodes.forEach(function(child) { - if (child.id && child.className == 'stepcontent') { - list.push(child.id); - } - }) - } + if (item.className && item.className.includes("trainingstep")) { + item.childNodes.forEach(function(child) { + if (child.id && child.className == 'stepcontent') { + list.push(child.id); + } + }) + } }); $.ajax({ - data: JSON.stringify(list), - type: 'POST', - url: '/rowers/plans/stepadder/{{ ps.id }}/?save=1', - error: function(result) { - $("#id_waiting").replaceWith( - '
Your upload failed
' - ); - }, - success: function(result) { - console.log(result) - } - }) - window.location.reload(true); - } + data: JSON.stringify(list), + type: 'POST', + url: '/rowers/plans/stepadder/{{ ps.id }}/?save=1', + error: function(result) { + $("#id_waiting").replaceWith( + '
Your upload failed
' + ); + }, + success: function(result) { + console.log(result) + } + }) + saveState(); + // reload the page + location.reload(); + // window.location.reload(true); + } - function saveState() { + function saveState() { var cntr = 0; var list = []; steps = document.querySelector('.drop-zone'); steps.childNodes.forEach(function(item) { - if (item.className && item.className.includes("trainingstep")) { - item.childNodes.forEach(function(child) { - if (child.id && child.className == 'stepcontent') { - list.push(child.id); - } - if (child.className == "stepid") { - s = `(${cntr})` - child.replaceWith(s); - cntr++; - } - }) - } + if (item.className && item.className.includes("trainingstep")) { + item.childNodes.forEach(function(child) { + if (child.id && child.className == 'stepcontent') { + list.push(child.id); + } + if (child.className == "stepid") { + s = `(${cntr})` + child.replaceWith(s); + cntr++; + } + }) + } }); $.ajax({ - data: JSON.stringify(list), - type: 'POST', - url: '/rowers/plans/stepadder/{{ ps.id }}/', - error: function(result) { - $("#id_waiting").replaceWith( - '
Your upload failed
' - ); - }, - success: function(result) { - console.log(result) - } - }) + data: JSON.stringify(list), + type: 'POST', + url: '/rowers/plans/stepadder/{{ ps.id }}/', + error: function(result) { + $("#id_waiting").replaceWith( + '
Your upload failed
' + ); + }, + success: function(result) { + console.log(result) + } + }) }; - - function handleDragStart(event) { + + function handleDragStart(event) { let target = event.target; if (target) { - dragged = target; - origcolor = dragged.style.backgroundColor; - event.dataTransfer.setData('text', target.id); - event.dataTransfer.dropEffect = 'move'; - // Make it half transparent - event.target.style.opacity = .3; + dragged = target; + origcolor = dragged.style.backgroundColor; + event.dataTransfer.setData('text', target.id); + event.dataTransfer.dropEffect = 'move'; + // Make it half transparent + event.target.style.opacity = .3; } - } - - function handleDragEnd(event) { + } + + function handleDragEnd(event) { if (event.target) { - // Reset the transparency - event.target.style.opacity = ''; // reset opacity when drag ends - items.forEach(function (item) { - item.classList.remove('over'); - }); - dragged = null; - origcolor = null; + // Reset the transparency + event.target.style.opacity = ''; // reset opacity when drag ends + items.forEach(function (item) { + item.classList.remove('over'); + }); + dragged = null; + origcolor = null; } saveState(); - } - - function handleMouseOver(event) { + } + + function handleMouseOver(event) { + if (event.preventDefault) { + const id = event.target.id; + if (id) { + var name = mysteps[id]['name']; + var txt = descriptions[id]; + var divstring = `

${name}

${txt}

` + const div = document.getElementById("stepinfo"); + div.innerHTML = divstring; + } + } + + return false; + } + + function handleMouseLeave(event) { if (event.preventDefault) { - const id = event.target.id; - if (id) { - var name = mysteps[id]['name']; - var txt = descriptions[id]; - var divstring = `

${name}

${txt}

` const div = document.getElementById("stepinfo"); - div.innerHTML = divstring; - } + div.innerHTML = '

Hover over a step to get details

' } return false; - } - - function handleMouseLeave(event) { - if (event.preventDefault) { - const div = document.getElementById("stepinfo"); - div.innerHTML = '

Hover over a step to get details

' - } - - return false; - } - - function handleDragOver(event) { + } + + function handleDragOver(event) { event.preventDefault(); - } - - function handleDragEnter(event) { + } + + function handleDragEnter(event) { this.classList.add('over'); const target = event.target; overcolor = event.target.style.backgroundColor; - + if (target && dragged) { - event.preventDefault(); - // Set the dropEffect to move - event.dataTransfer.dropEffect = 'move' - target.style.background = '#1f904e'; + event.preventDefault(); + // Set the dropEffect to move + event.dataTransfer.dropEffect = 'move' + target.style.background = '#1f904e'; } - } - - function handleDragLeave(event) { + } + + function handleDragLeave(event) { event.target.style.backgroundColor = ''; - + if (dragged.parentNode.className.includes("drop-zone")) { - trash(event); + trash(event); } saveState(); - } - - function trash(event) { + } + + function trash(event) { const target = event.target; event.target.style.backgroundColor = ''; if (target && dragged) { - event.preventDefault(); - if (dragged.parentNode.className.includes("drop-zone")) { - dragged.remove(); - } + event.preventDefault(); + if (dragged.parentNode.className.includes("drop-zone")) { + dragged.remove(); + } } - } - - function handleDrop(event) { + } + + function handleDrop(event) { const target = event.target; if (target && dragged) { - target.style.backgroundColor = ''; - event.preventDefault(); - // Get the id of the target and add the moved element to the target's DOM - // dragged.parentNode.removeChild(dragged); - if (target.nodeName == "SECTION") { - dragged.style.opacity = ''; - dragged.style.backgroundColor = origcolor; - var dropped = dragged.cloneNode(true); - dropped.id = 0; - target.appendChild(dropped); - } - if (target.nodeName == 'DIV') { - // insert after - dragged.style.opacity = ''; - dragged.style.backgroundColor = origcolor; - var dropped = dragged.cloneNode(true); - if (target.parentNode.nodeName == 'SECTION') { - target.after(dropped); + target.style.backgroundColor = ''; + event.preventDefault(); + // Get the id of the target and add the moved element to the targets DOM + // dragged.parentNode.removeChild(dragged); + if (target.nodeName == "SECTION") { + dragged.style.opacity = ''; + dragged.style.backgroundColor = origcolor; + var dropped = dragged.cloneNode(true); + dropped.id = 0; + target.appendChild(dropped); } - if (target.parentNode.parentNode.nodeName == 'SECTION') { - target.parentNode.after(dropped); + if (target.nodeName == 'DIV') { + // insert after + dragged.style.opacity = ''; + dragged.style.backgroundColor = origcolor; + var dropped = dragged.cloneNode(true); + if (target.parentNode.nodeName == 'SECTION') { + target.after(dropped); + } + if (target.parentNode.parentNode.nodeName == 'SECTION') { + target.parentNode.after(dropped); + } } - } } saveState(); } - + function noDrop(event) { - event.preventDefault(); + event.preventDefault(); } - - - let dropzone = document.querySelector('.drop-zone') - dropzone.addEventListener('dragenter', handleDragEnter); - dropzone.addEventListener('dragleave', handleDragLeave); - dropzone.addEventListener('drop', handleDrop) - dropzone.addEventListener('dragover', handleDragOver); - dropzone.addEventListener('dragstart', handleDragStart); - dropzone.addEventListener('dragend', handleDragEnd); - - let items = document.querySelectorAll('.stepcontainer .trainingstep'); - items.forEach(function(item) { + + + let dropzone = document.querySelector('.drop-zone') + dropzone.addEventListener('dragenter', handleDragEnter); + dropzone.addEventListener('dragleave', handleDragLeave); + dropzone.addEventListener('drop', handleDrop) + dropzone.addEventListener('dragover', handleDragOver); + dropzone.addEventListener('dragstart', handleDragStart); + dropzone.addEventListener('dragend', handleDragEnd); + + let items = document.querySelectorAll('.stepcontainer .trainingstep'); + items.forEach(function(item) { item.addEventListener('dragstart', handleDragStart); item.addEventListener('dragend', handleDragEnd); item.addEventListener('dragleave', handleDragLeave); @@ -392,10 +395,10 @@ item.addEventListener('dragenter',noDrop); item.addEventListener('mouseover',handleMouseOver); item.addEventListener('mouseleave',handleMouseLeave); - }); - - - + }); + + + {% endblock %} {% block sidebar %} diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index 33255a17..8ba2c0ba 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -3077,6 +3077,7 @@ def rower_create_trainingplan(request, id=0): 'old_targets': old_targets, }) +@csrf_exempt @user_passes_test(can_plan, login_url="/rowers/paidplans", message="This functionality requires a Coach or Self-Coach plan", redirect_field_name=None) @@ -3116,6 +3117,7 @@ def stepadder(request, id=0): 'message': 'permission denied for host '+hostt[0]} return JSONResponse(status=403, data=message) + if ps.steps: filename = ps.steps.get('filename','') sport = ps.steps.get('sport','rowing') @@ -3208,6 +3210,7 @@ def stepedit(request, id=0, psid=0): ps.fitfile = None ps.interval_string = "" + ps.save() step.durationtype = form.cleaned_data['durationtype'] From 7190d97d29d4d0463cfa7c58ca0c22675bc9ed89 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 22 Jan 2025 20:50:02 +0100 Subject: [PATCH 2/3] fixing step editor --- rowers/utils.py | 4 ++++ rowers/views/otherviews.py | 1 + rowers/views/planviews.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rowers/utils.py b/rowers/utils.py index 0072805b..070f1a45 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -740,6 +740,10 @@ def steps_write_fit(steps): # convert to json, value of keys called wkt_step_name to string for step in steps['steps']: step['wkt_step_name'] = str(step['wkt_step_name']) + # convert numerical values in the dict to integers + for key in step.keys(): + if isinstance(step[key], (int, float)): + step[key] = int(step[key]) response = requests.post(url=url, headers=headers, json=steps) diff --git a/rowers/views/otherviews.py b/rowers/views/otherviews.py index 3fef3dd5..bd59a9bf 100644 --- a/rowers/views/otherviews.py +++ b/rowers/views/otherviews.py @@ -50,6 +50,7 @@ def download_fit(request, filename=''): try: response = HttpResponse(fitfile) except FileNotFoundError: + print(fitfile, "not found") raise Http404("File not found") response['Content-Disposition'] = 'attachment; filename="%s"' % filename # pragma: no cover diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index 8ba2c0ba..5833c5a5 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -3204,7 +3204,7 @@ def stepedit(request, id=0, psid=0): if form.cleaned_data['durationtype'] == 'Time': ss['durationValue'] = form.cleaned_data['durationvalue']*60000 elif form.cleaned_data['durationtype'] == 'Distance': - ss[durationValue] = form.cleaned_data['durationvalue']*100 + ss['durationValue'] = form.cleaned_data['durationvalue']*100 ss['durationValue'] = int(ss['durationValue']) ps.fitfile = None From 3c686726780760074f7eae837592bf1dbe3e4aeb Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 25 Jan 2025 14:56:37 +0100 Subject: [PATCH 3/3] fixes fixes --- rowers/forms.py | 5 +++-- rowers/integrations/trainingpeaks.py | 6 +++++- rowers/tests/testdata/testdata.tcx.gz | Bin 3989 -> 3989 bytes rowers/uploads.py | 9 +++++---- rowers/views/otherviews.py | 1 - rowers/views/workoutviews.py | 9 +++++++-- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 2db9b8bf..face5583 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -1232,10 +1232,11 @@ bulkactions = ( ('unset commute','unset commute'), ) destinations = ( - ('c2','c2'), + ('c2','concept2'), ('strava','strava'), ('sporttracks','sporttracks'), - ('trainingpeaks','trainingpeaks') + ('trainingpeaks','trainingpeaks'), + ('intervals','intervals.icu') ) class WorkoutBulkActions(forms.Form): diff --git a/rowers/integrations/trainingpeaks.py b/rowers/integrations/trainingpeaks.py index cd3b6bb4..e56095c7 100644 --- a/rowers/integrations/trainingpeaks.py +++ b/rowers/integrations/trainingpeaks.py @@ -79,13 +79,17 @@ class TPIntegration(SyncIntegration): def workout_export(self, workout, *args, **kwargs) -> str: thetoken = self.open() tcxfilename = self.createworkoutdata(workout) + try: + wtype = tpmapping[workout.workouttype] + except KeyError: + wtype = 'rowing' job = myqueue( queue, handle_workout_tp_upload, workout, thetoken, tcxfilename, - tpmapping[workout.workouttype] + wtype ) return job.id diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 415ce353fe916f370478946207c46bf181052e89..8c06d57a103c30792fa538ffe0fe9c1a591b7590 100644 GIT binary patch delta 16 XcmbO#KUJPxzMF$%;_WFL+57kbEQ$qx delta 16 XcmbO#KUJPxzMF&Njm*T2?0x(IDt-kR diff --git a/rowers/uploads.py b/rowers/uploads.py index 48f54ede..06273c63 100644 --- a/rowers/uploads.py +++ b/rowers/uploads.py @@ -130,6 +130,9 @@ def make_plot(r, w, f1, f2, plottype, title, imagename='', plotnr=0): def do_sync(w, options, quick=False): + if w.duplicate: + return 0 + do_strava_export = False if w.user.strava_auto_export is True: do_strava_export = True @@ -236,8 +239,6 @@ def do_sync(w, options, quick=False): except KeyError: pass - if w.duplicate: - return 0 if do_c2_export: # pragma: no cover dologging('c2_log.log','Exporting workout to C2 for user {user}'.format(user=w.user.user.id)) @@ -305,7 +306,6 @@ def do_sync(w, options, quick=False): except NoTokenError: dologging('st_export.log','No Token Error') - return 0 do_tp_export = w.user.trainingpeaks_auto_export try: @@ -315,6 +315,8 @@ def do_sync(w, options, quick=False): do_tp_export = upload_to_tp except KeyError: upload_to_st = False + + if do_tp_export: try: tp_integration = TPIntegration(w.user.user) @@ -327,7 +329,6 @@ def do_sync(w, options, quick=False): ) except NoTokenError: dologging('tp_export.log','No Token Error') - return 0 # we do Strava last. if do_strava_export: # pragma: no cover diff --git a/rowers/views/otherviews.py b/rowers/views/otherviews.py index bd59a9bf..3fef3dd5 100644 --- a/rowers/views/otherviews.py +++ b/rowers/views/otherviews.py @@ -50,7 +50,6 @@ def download_fit(request, filename=''): try: response = HttpResponse(fitfile) except FileNotFoundError: - print(fitfile, "not found") raise Http404("File not found") response['Content-Disposition'] = 'attachment; filename="%s"' % filename # pragma: no cover diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 31864c0d..b8dd6c72 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -2044,6 +2044,12 @@ def workouts_bulk_actions(request): workoutids = request.session.get('ids',[]) workouts = [] exportchoice = 'strava' + + # exportchoice = ExportChoices() + actionform = WorkoutBulkActions() + actionform.fields["action"].initial = action + assignchoices = AssignChoices() + try: for encid in workoutids: w = get_workout_by_opaqueid(request, encid) @@ -2286,8 +2292,7 @@ def workouts_view(request, message='', successmessage='', (Q(name__icontains=q) for q in query_list)) | reduce(operator.and_, (Q(notes__icontains=q) for q in query_list)), - exclude_strava=False, - ) + ).exclude(workoutsource='strava') searchform = SearchForm(initial={'q': query}) else: searchform = SearchForm()