diff --git a/rowers/tasks.py b/rowers/tasks.py index 4e469477..dc71b058 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -357,6 +357,78 @@ def correct_intensity(workout): return workout +<<<<<<< HEAD +======= +import io +import zipfile + +@app.task +def email_all_user_workouts_zip(rower, start_date=None, end_date=None, debug=False, **kwargs): + # Get all workouts for this user, optionally filtered by date range + workouts = Workout.objects.filter(user=rower).order_by('-date') + + # Apply date filters if provided + if start_date: + workouts = workouts.filter(date__gte=start_date) + if end_date: + workouts = workouts.filter(date__lte=end_date) + + # for debug, limit to 5 workouts + if settings.DEBUG: + workouts = workouts[:5] + + if not workouts.exists(): + dologging('export_all_workouts.log', f"No workouts found for user {rower.user.id} in date range {start_date} to {end_date}") + return 0 + + # Create ZIP file in memory + zip_buffer = io.BytesIO() + + with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for workout in workouts: + try: + rowdata = rdata(csvfile=workout.csvfilename) + workouttype = mytypes.fitmapping.get(workout.workouttype, 'generic') + + fit_filename = f"workout_{workout.id}_{workout.date.strftime('%Y%m%d')}.fit" + # exporttofit creates a file, we need to add it to the zip_file + rowdata.exporttofit(fit_filename, sport=workouttype, notes=workout.name) + zip_file.write(fit_filename, arcname=fit_filename) + os.remove(fit_filename) + except Exception as e: # pragma: no cover + dologging('export_all_workouts.log', f"Error exporting workout {workout.id}: {str(e)}") + continue + + # Save ZIP file to disk + export_date = datetime.datetime.now().strftime('%Y%m%d') + filename = f"{rower.user.username}_workouts_{export_date}_from_{start_date}_to_{end_date}_{uuid4().hex[:8]}.zip" + zip_file_path = os.path.join(settings.MEDIA_ROOT, filename) + + try: + with open(zip_file_path, 'wb') as f: + f.write(zip_buffer.getvalue()) + except Exception as e: # pragma: no cover + dologging('export_all_workouts.log', f"Error saving ZIP file: {str(e)}") + return 0 + + # Send email with download link + subject = "Rowsandall Workouts Export" + from_email = 'Rowsandall ' + useremail = rower.user.email + + # Generate download URL + download_url = f"{SITE_URL}/rowers/workouts/download/?file={filename}" + + _ = send_template_email( + from_email, [useremail], + subject, + 'workouts_export_email.html', + {'download_url': download_url, 'filename': filename}, + ) + + return 1 + +>>>>>>> release/v23.9.0 @app.task def handle_loadnextweek(rower, debug=False, **kwargs): diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html index ea138baf..a503805f 100644 --- a/rowers/templates/menu_workout.html +++ b/rowers/templates/menu_workout.html @@ -260,6 +260,11 @@ TCX +
  • + + FIT + +
  • diff --git a/rowers/urls.py b/rowers/urls.py index 3289b01d..b92ea953 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -490,6 +490,8 @@ urlpatterns = [ name='workout_comment_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/emailtcx/$', views.workout_tcxemail_view, name='workout_tcxemail_view'), + re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/emailfit/$', views.workout_fitemail_view, + name='workout_fitemail_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/emailgpx/$', views.workout_gpxemail_view, name='workout_gpxemail_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/emailcsv/$', views.workout_csvemail_view, diff --git a/rowers/views/exportviews.py b/rowers/views/exportviews.py index 0a4a0151..3504e59f 100644 --- a/rowers/views/exportviews.py +++ b/rowers/views/exportviews.py @@ -22,6 +22,27 @@ def workout_tcxemail_view(request, id=0): os.remove(tcxfilename) return response +@permission_required('workout.change_workout', fn=get_workout_by_opaqueid, raise_exception=True) +def workout_fitemail_view(request, id=0): + w = get_workout(id) + + row = rdata(csvfile=w.csvfilename) + + code = str(uuid4()) + fitfilename = code+'.fit' + + workouttype = mytypes.fitmapping.get(w.workouttype, 'generic') + + row.exporttofit(fitfilename, sport=workouttype, notes=w.name) + + with open(fitfilename, 'rb') as f: + response = HttpResponse(f) + response['Content-Disposition'] = 'attachment; filename="%s"' % fitfilename + response['Content-Type'] = 'application/octet-stream' + + os.remove(fitfilename) + return response + @login_required() def plannedsessions_icsemail_view(request, userid=0):