From 8fdca8a1f6e79ce86703bf800baa0213716d8001 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 5 Nov 2019 22:09:26 +0100 Subject: [PATCH] VideoAnalysis object urls --- rowers/models.py | 9 + rowers/templates/embedded_video.html | 46 ++--- rowers/templates/video_delete_confirm.html | 30 ++++ rowers/templates/workout_form.html | 29 +-- rowers/urls.py | 6 +- rowers/views/statements.py | 3 +- rowers/views/workoutviews.py | 199 ++++++++++++++++++++- 7 files changed, 285 insertions(+), 37 deletions(-) create mode 100644 rowers/templates/video_delete_confirm.html diff --git a/rowers/models.py b/rowers/models.py index 68b6d7c2..e130d8a1 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -3721,3 +3721,12 @@ class BlogPost(models.Model): title = models.TextField(max_length=300) link = models.TextField(max_length=300) date = models.DateField() + +class VideoAnalysis(models.Model): + name = models.CharField(default='', max_length=150,blank=True,null=True) + video_id = models.CharField(default='',max_length=150) + delay = models.IntegerField(default=0) + workout = models.ForeignKey(Workout, on_delete=models.CASCADE) + + def __str__(self): + return self.name diff --git a/rowers/templates/embedded_video.html b/rowers/templates/embedded_video.html index c0ceff75..16e63d22 100644 --- a/rowers/templates/embedded_video.html +++ b/rowers/templates/embedded_video.html @@ -15,25 +15,10 @@ {% block main %} -

- {% if workout|previousworkout:rower.user %} - Previous  - {% endif %} - {% if workout|nextworkout:rower.user %} - Next - {% endif %} -

{% language 'en' %} -

Workout Video for {{ workout.name }}

+

Video Analysis for {{ workout.name }}

{% endlanguage %} diff --git a/rowers/templates/video_delete_confirm.html b/rowers/templates/video_delete_confirm.html new file mode 100644 index 00000000..6211e3b4 --- /dev/null +++ b/rowers/templates/video_delete_confirm.html @@ -0,0 +1,30 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Delete Graph Image {% endblock %} + +{% block main %} + + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_workouts.html' %} +{% endblock %} diff --git a/rowers/templates/workout_form.html b/rowers/templates/workout_form.html index 38c28cfe..0b78dd32 100644 --- a/rowers/templates/workout_form.html +++ b/rowers/templates/workout_form.html @@ -35,9 +35,9 @@ $('#id_workouttype').on('change', function(){ || $(this).val() == 'churchboat' ) { $('#id_boattype').toggle(true); - } else { + } else { $('#id_boattype').toggle(false); - $('#id_boattype').val('1x'); + $('#id_boattype').val('1x'); } if ( $(this).val() == 'rower' @@ -45,13 +45,13 @@ $('#id_workouttype').on('change', function(){ || $(this).val() == 'slides' ) { $('#id_dragfactor').toggle(true); - } else { + } else { $('#id_dragfactor').toggle(false); - $('#id_dragfactor').val('0'); + $('#id_dragfactor').val('0'); } }); -$('#id_workouttype').change(); +$('#id_workouttype').change(); }); {% endblock %} @@ -114,7 +114,7 @@ $('#id_workouttype').change(); Please correct the error{{ form.errors|pluralize }} below.

{% endif %} - +
@@ -126,9 +126,9 @@ $('#id_workouttype').change();
  • - +

    Workout Summary

    - +

             {{ workout.summary }}
    @@ -151,13 +151,13 @@ $('#id_workouttype').change();
         
    -    
    +
     
     
         
    {{ mapdiv|safe }}
    - + {{ mapscript|safe }}
  • @@ -165,12 +165,19 @@ $('#id_workouttype').change(); {% for graph in graphs %}
  • - {{ graph.filename }}
  • {% endfor %} + {% for video in videos %} +
  • + + + +
  • + {% endfor %} {% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index a54d85da..aecbdd64 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -343,7 +343,11 @@ urlpatterns = [ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/split/$',views.workout_split_view,name='workout_split_view'), # re_path(r'^workout/(?P\d+)/interactiveplot/$',views.workout_biginteractive_view), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/view/$',views.workout_view,name='workout_view'), - re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/video/$',views.workout_video_view,name='workout_video_view'), + re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/video/$',views.workout_video_create_view, + name='workout_video_create_view'), + re_path(r'^video/(?P\d+)/delete/$',views.VideoDelete.as_view(),name='video_delete'), + re_path(r'^video/(?P\w.+)/$',views.workout_video_view, + name='workout_video_view'), # re_path(r'^workout/(?P\d+)/$',views.workout_view,name='workout_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/$',views.workout_view,name='workout_view'), re_path(r'^workout/fusion/(?P\b[0-9A-Fa-f]+\b)/(?P\b[0-9A-Fa-f]+\b)/$',views.workout_fusion_view,name='workout_fusion_view'), diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 20d848a8..8a57029e 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -96,7 +96,8 @@ from rowers.models import ( TrainingMesoCycleForm, TrainingMicroCycleForm, RaceLogo,RowerBillingAddressForm,PaidPlan, AlertEditForm, ConditionEditForm, - PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser + PlannedSessionComment,CoachRequest,CoachOffer,checkaccessplanuser, + VideoAnalysis ) from rowers.models import ( RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index c21eeefa..afa2b791 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -47,10 +47,125 @@ def get_video_id(url): raise ValueError # Show a video compared with data +def workout_video_view(request,id=''): + try: + id = encoder.decode_hex(id) + analysis = VideoAnalysis.objects.get(id=id) + except VideoAnalysis.DoesNotExist: + raise Http404("Video Analysis does not exist") + + w = analysis.workout + delay = analysis.delay + + if request.user.is_authenticated: + mayedit = checkworkoutuser(request.user,w) and isprorower(request.user.rower) + rower = request.user.rower + else: + mayedit = False + rower = None + + # get video ID and offset + if mayedit and request.method == 'POST': + form = VideoAnalysisCreateForm(request.POST) + if form.is_valid(): + video_id = form.cleaned_data['url'] + delay = form.cleaned_data['delay'] + if 'save_button' in request.POST: + analysis.name = form.cleaned_data['name'] + analysis.video_id = video_id + analysis.delay = delay + analysis.save() + else: + video_id = id + delay = 0 + elif mayedit: + form = VideoAnalysisCreateForm( + initial = { + 'name':analysis.name, + 'delay': analysis.delay, + 'url': analysis.video_id, + } + ) + video_id = analysis.video_id + else: + form = None + + # get data + df = getsmallrowdata_db(['time','velo','spm'],ids=[w.id], + workstrokesonly=False,doclean=False,compute=False) + df['time'] = (df['time']-df['time'].min())/1000. + df.sort_values(by='time',inplace=True) + + + df.set_index(pd.to_timedelta(df['time'],unit='s'),inplace=True) + df2 = df.resample('1s').mean().interpolate() + + + mask = df2['time'] < delay + df2 = df2.mask(mask).dropna() + df2['time'] = (df2['time']-df2['time'].min()) + + boatspeed = (100*df2['velo']).astype(int)/100. + spm = (10*df2['spm']).astype(int)/10. + + coordinates = dataprep.get_latlon_time(w.id) + + coordinates.set_index(pd.to_timedelta(coordinates['time'],unit='s'),inplace=True) + coordinates = coordinates.resample('1s').mean().interpolate() + mask = coordinates['time'] < delay + coordinates = coordinates.mask(mask).dropna() + coordinates['time'] = coordinates['time']-coordinates['time'].min() + latitude = coordinates['latitude'] + longitude = coordinates['longitude'] + + # create map + mapscript, mapdiv = leaflet_chart_video(latitude,longitude, + w.name) + + # bundle data + data = { + 'boatspeed':[ v for v in boatspeed.values], + 'latitude':[ l for l in latitude.values], + 'longitude':[ l for l in longitude.values], + 'spm':[ s for s in spm.values ] + } + + breadcrumbs = [ + { + 'url':'/rowers/list-workouts/', + 'name':'Workouts' + }, + { + 'url':get_workout_default_page(request,encoder.encode_hex(w.id)), + 'name': w.name + }, + { + 'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(analysis.id)}), + 'name': 'Video Analysis' + } + + ] + + return render(request, + 'embedded_video.html', + { + 'workout':w, + 'rower':rower, + 'data': json.dumps(data,default=default), + 'mapscript': mapscript, + 'mapdiv': mapdiv, + 'video_id': analysis.video_id, + 'form':form, + 'breadcrumbs':breadcrumbs, + 'analysis':analysis, + }) + + +# Create a video compared with data @user_passes_test(ispromember,login_url="/rowers/paidplans/", message="This functionality requires a Pro plan or higher", redirect_field_name=None) -def workout_video_view(request,id=0): +def workout_video_create_view(request,id=0): # get workout w = get_workout_permitted(request.user,id) @@ -61,6 +176,17 @@ def workout_video_view(request,id=0): url = form.cleaned_data['url'] delay = form.cleaned_data['delay'] video_id = get_video_id(url) + if 'save_button' in request.POST: + analysis = VideoAnalysis( + workout=w, + name=form.cleaned_data['name'], + video_id = video_id, + delay=delay, + ) + analysis.save() + url = reverse('workout_video_view', + kwargs={'id':encoder.encode_hex(analysis.id)}) + return HttpResponseRedirect(url) else: video_id = None delay = 0 @@ -109,6 +235,22 @@ def workout_video_view(request,id=0): 'spm':[ s for s in spm.values ] } + breadcrumbs = [ + { + 'url':'/rowers/list-workouts/', + 'name':'Workouts' + }, + { + 'url':get_workout_default_page(request,encoder.encode_hex(w.id)), + 'name': w.name + }, + { + 'url':reverse('workout_video_create_view',kwargs={'id':encoder.encode_hex(w.id)}), + 'name': 'Video Analysis' + } + + ] + return render(request, 'embedded_video.html', { @@ -119,6 +261,7 @@ def workout_video_view(request,id=0): 'mapdiv': mapdiv, 'video_id': video_id, 'form':form, + 'breadcrumbs':breadcrumbs, }) # Show the EMpower Oarlock generated Stroke Profile @@ -3633,6 +3776,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""): except: pass + videos = VideoAnalysis.objects.filter(workout=row) # create interactive plot f1 = row.csvfilename @@ -3696,6 +3840,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""): 'workout':row, 'teams':get_my_teams(request.user), 'graphs':g, + 'videos':videos, 'breadcrumbs':breadcrumbs, 'rower':r, 'indoorraces':indoorraces, @@ -5357,6 +5502,58 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'formvalues':formvalues, }) +class VideoDelete(DeleteView): + login_required = True + model = VideoAnalysis + template_name = 'video_delete_confirm.html' + + # extra parameters + def get_context_data(self, **kwargs): + context = super(VideoDelete, self).get_context_data(**kwargs) + + breadcrumbs = [ + { + 'url':'/rowers/list-workouts/', + 'name':'Workouts' + }, + { + 'url':get_workout_default_page( + self.request, + encoder.encode_hex(self.object.workout.id)), + 'name': self.object.workout.name + }, + { + 'url':reverse('workout_video_view',kwargs={'id':encoder.encode_hex(self.object.id)}), + 'name': 'Video Analysis' + }, + { 'url':reverse('video_delete',kwargs={'pk':str(self.object.pk)}), + 'name': 'Delete' + } + + ] + + context['active'] = 'nav-workouts' + context['rower'] = getrower(self.request.user) + context['breadcrumbs'] = breadcrumbs + + return context + + + def get_success_url(self): + w = self.object.workout + try: + w = Workout.objects.get(id=w.id) + except Workout.DoesNotExist: + return reverse('workouts_view') + + return reverse('workout_edit_view',kwargs={'id':encoder.encode_hex(w.id)}) + + def get_object(self, *args, **kwargs): + obj = super(VideoDelete, self).get_object(*args, **kwargs) + if not checkaccessuser(self.request.user,obj.workout.user): + raise PermissionDenied('You are not allowed to delete this analysis') + + return obj class GraphDelete(DeleteView): login_required = True