From 0cc080b5cfb0c33f7916adf030d2f2d1a44bb206 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 14 Feb 2017 22:25:49 +0100 Subject: [PATCH 1/6] adding sharing behavior as a team setting --- rowers/models.py | 10 +++++++++- rowers/teams.py | 7 ++++--- rowers/templates/teamcreate.html | 15 ++++++++++++--- rowers/templates/teamedit.html | 13 +++++++++++-- rowers/views.py | 8 ++++++-- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index 755b3ede..41083237 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -104,19 +104,27 @@ class Team(models.Model): ('private','private'), ('open','open'), ) + + viewchoices = ( + ('coachonly','Coach Only'), + ('allmembers','All Members') + ) + name = models.CharField(max_length=150,unique=True,verbose_name='Team Name') notes = models.CharField(blank=True,max_length=200,verbose_name='Team Purpose') manager = models.ForeignKey(User) private = models.CharField(max_length=30,choices=choices,default='open', verbose_name='Team Type') + viewing = models.CharField(max_length=30,choices=viewchoices,default='allmembers',verbose_name='Sharing Behavior') + def __unicode__(self): return self.name class TeamForm(ModelForm): class Meta: model = Team - fields = ['name','notes','private'] + fields = ['name','notes','private','viewing'] widgets = { 'notes': forms.Textarea, } diff --git a/rowers/teams.py b/rowers/teams.py index eadc2dc1..9891facb 100644 --- a/rowers/teams.py +++ b/rowers/teams.py @@ -43,7 +43,7 @@ def handle_add_workouts_team(ws,t): return 1 -def update_team(t,name,manager,private,notes): +def update_team(t,name,manager,private,notes,viewing): if t.manager != manager: return (0,'You are not the manager of this team') try: @@ -51,16 +51,17 @@ def update_team(t,name,manager,private,notes): t.manager = manager t.private = private t.notes = notes + t.viewing = viewing t.save() except IntegrityError: return (0,'Team name duplication') return (1,'Team Updated') -def create_team(name,manager,private='open',notes=''): +def create_team(name,manager,private='open',notes='',viewing='allmembers'): # needs some error testing try: t = Team(name=name,manager=manager,notes=notes, - private=private) + private=private,viewing=viewing) t.save() r = Rower.objects.get(user=manager) res = add_member(t.id,r) diff --git a/rowers/templates/teamcreate.html b/rowers/templates/teamcreate.html index 557e366b..4d4e6a98 100644 --- a/rowers/templates/teamcreate.html +++ b/rowers/templates/teamcreate.html @@ -4,9 +4,11 @@ {% block title %}New Team{% endblock %} {% block content %} -
+
+

Create a new Team

+
-

Create a new Team

+ {% if form.errors %}

Please correct the error{{ form.errors|pluralize }} below. @@ -21,7 +23,14 @@

- + +
diff --git a/rowers/templates/teamedit.html b/rowers/templates/teamedit.html index 4d037b22..b8ea6366 100644 --- a/rowers/templates/teamedit.html +++ b/rowers/templates/teamedit.html @@ -4,9 +4,11 @@ {% block title %}New Team{% endblock %} {% block content %} -
+
+

Edit Team {{ team.name }}

+
-

Edit Team {{ team.name }}

+ {% if form.errors %}

Please correct the error{{ form.errors|pluralize }} below. @@ -21,6 +23,13 @@

+ diff --git a/rowers/views.py b/rowers/views.py index 663bd326..9a2006be 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -5432,7 +5432,9 @@ def team_edit_view(request,id=0): notes = cd['notes'] manager = request.user private = cd['private'] - res,message=teams.update_team(t,name,manager,private,notes) + viewing = cd['viewing'] + res,message=teams.update_team(t,name,manager,private,notes, + viewing) if res: url = reverse(team_view, kwargs={ @@ -5470,7 +5472,9 @@ def team_create_view(request): notes = cd['notes'] manager = request.user private = cd['private'] - res,message=teams.create_team(name,manager,private,notes) + viewing = cd['viewing'] + res,message=teams.create_team(name,manager,private,notes, + viewing) url = reverse(rower_teams_view) response = HttpResponseRedirect(url) return response From 4571f24daba2daf726a94e9face9eaee9da64bfc Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 15 Feb 2017 12:31:00 +0100 Subject: [PATCH 2/6] added privacy settings to workouts & users --- rowers/models.py | 22 ++++++++++++++++++++-- rowers/views.py | 5 +++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index 41083237..f4b842be 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -204,6 +204,11 @@ class Rower(models.Model): ('coach','coach') ) + privacychoices = ( + ('visible','Visible'), + ('hidden','Hidden'), + ) + rowerplan = models.CharField(default='basic',max_length=30, choices=plans) @@ -211,8 +216,11 @@ class Rower(models.Model): teamplanexpires = models.DateField(default=timezone.now) clubsize = models.IntegerField(default=0) + # Friends/Team friends = models.ManyToManyField("self",blank=True) + privacy = models.CharField(default='visible',max_length=30, + choices=privacychoices) team = models.ManyToManyField(Team,blank=True) @@ -359,6 +367,11 @@ class Workout(models.Model): ('4-', '4- (four)'), ('8+', '8+ (eight)'), ) + + privacychoices = ( + ('private','Private'), + ('visible','Visible'), + ) user = models.ForeignKey(Rower) team = models.ManyToManyField(Team,blank=True) @@ -382,6 +395,8 @@ class Workout(models.Model): uploadedtosporttracks = models.IntegerField(default=0) notes = models.CharField(blank=True,null=True,max_length=200) summary = models.TextField(blank=True) + privacy = models.CharField(default='visible',max_length=30, + choices=privacychoices) def __str__(self): @@ -499,7 +514,7 @@ class WorkoutForm(ModelForm): duration = forms.TimeInput(format='%H:%M:%S.%f') class Meta: model = Workout - fields = ['name','date','starttime','duration','distance','workouttype','boattype','notes'] + fields = ['name','date','starttime','duration','distance','workouttype','notes','privacy','boattype'] widgets = { 'date': DateInput(), 'notes': forms.Textarea, @@ -508,9 +523,12 @@ class WorkoutForm(ModelForm): def __init__(self, *args, **kwargs): super(WorkoutForm, self).__init__(*args, **kwargs) + # this line to be removed + del self.fields['privacy'] + if self.instance.workouttype != 'water': del self.fields['boattype'] - + # Used for the rowing physics calculations class AdvancedWorkoutForm(ModelForm): class Meta: diff --git a/rowers/views.py b/rowers/views.py index 9a2006be..ed51a361 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -3259,6 +3259,10 @@ def workout_edit_view(request,id=0,message="",successmessage=""): boattype = request.POST['boattype'] except KeyError: boattype = Workout.objects.get(id=id).boattype + try: + privacy = request.POST['privacy'] + except KeyError: + privacy = Workout.objects.get(id=id).privacy startdatetime = (str(date) + ' ' + str(starttime)) startdatetime = datetime.datetime.strptime(startdatetime, "%Y-%m-%d %H:%M:%S") @@ -3277,6 +3281,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""): row.duration = duration row.distance = distance row.boattype = boattype + row.privacy = privacy row.save() # change data in csv file From 478b6f2fa03c15360e3e1a1d73f1ca04596e5c1a Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 15 Feb 2017 12:58:38 +0100 Subject: [PATCH 3/6] added set private/public to workout edit form --- rowers/templates/workout_form.html | 11 ++++- rowers/urls.py | 2 + rowers/views.py | 64 ++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/rowers/templates/workout_form.html b/rowers/templates/workout_form.html index 1c085c73..356a245a 100644 --- a/rowers/templates/workout_form.html +++ b/rowers/templates/workout_form.html @@ -69,7 +69,16 @@ {{ form.as_table }} {% csrf_token %} -
+
+ {% if workout.privacy == 'visible' %} + Set Private + Only you can see this workout + {% else %} + Make Public + Make this workout visible to your teams and followers + {% endif %} +
+
diff --git a/rowers/urls.py b/rowers/urls.py index 8b4a1137..37ec074b 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -157,6 +157,8 @@ urlpatterns = [ url(r'^workout/(?P\d+)/edit/c/(?P.+.*)$',views.workout_edit_view), url(r'^workout/(?P\d+)/edit/s/(?P.+.*)$',views.workout_edit_view), url(r'^workout/(?P\d+)/edit$',views.workout_edit_view), + url(r'^workout/(?P\d+)/setprivate$',views.workout_setprivate_view), + url(r'^workout/(?P\d+)/makepublic$',views.workout_makepublic_view), url(r'^workout/(?P\d+)/advanced/c/(?P.+.*)$',views.workout_advanced_view), url(r'^workout/(?P\d+)/advanced/s/(?P.+.*)$',views.workout_advanced_view), url(r'^workout/(?P\d+)/geeky$',views.workout_geeky_view), diff --git a/rowers/views.py b/rowers/views.py index ed51a361..88d9fa30 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1884,7 +1884,71 @@ def workout_recalcsummary_view(request,id=0): }) return HttpResponseRedirect(url) + +@login_required() +def workout_makepublic_view(request,id, + message='', + successmessage=''): + try: + row = Workout.objects.get(id=id) + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + if (checkworkoutuser(request.user,row)==False): + message = "You are not allowed to edit this workout" + url = reverse(workouts_view,args=[str(message)]) + + return HttpResponseRedirect(url) + + row.privacy = 'visible' + row.save() + rr = Rower.objects.get(user=request.user) + + teams = rr.team.all() + for team in teams: + row.team.add(team) + + + message = "Workout set to public. Your followers and team members will see it" + + url = reverse(workout_edit_view, + kwargs = { + 'id':str(id), + 'successmessage':str(message), + }) + return HttpResponseRedirect(url) + +@login_required() +def workout_setprivate_view(request,id, + message='', + successmessage=''): + try: + row = Workout.objects.get(id=id) + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + + if (checkworkoutuser(request.user,row)==False): + message = "You are not allowed to edit this workout" + url = reverse(workouts_view,args=[str(message)]) + + return HttpResponseRedirect(url) + + row.privacy = 'private' + row.save() + + for team in row.team.all(): + row.team.remove(team) + + message = "Workout set to private. Only you will see it" + + url = reverse(workout_edit_view, + kwargs = { + 'id':str(id), + 'successmessage':str(message), + }) + return HttpResponseRedirect(url) + + # List Workouts @login_required() def workouts_view(request,message='',successmessage='', From f327f1aa8e9d027b49f1ca06f9d6f8da36230185 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 15 Feb 2017 13:07:55 +0100 Subject: [PATCH 4/6] team manager can edit workout --- rowers/templates/list_workouts.html | 2 +- rowers/views.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index 354a4842..7a1d58ea 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -62,7 +62,7 @@ {{ workout.date |truncatechars:15}} {{ workout.starttime }} - {% if workout.user.user == user %} + {% if workout.user.user == user or user == team.manager %} {% if workout.name != '' %} {{ workout.name }} {% else %} diff --git a/rowers/views.py b/rowers/views.py index 88d9fa30..0a5aa3d1 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -302,7 +302,8 @@ def sendmail(request): def checkworkoutuser(user,workout): try: r = Rower.objects.get(user=user) - return (workout.user == r) + managers = [team.manager for team in workout.team.all()] + return (workout.user == r or user in managers) except Rower.DoesNotExist: return(False) From 154619a0dcd7b95ba069137f83b3b597f6d45a81 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 15 Feb 2017 15:07:27 +0100 Subject: [PATCH 5/6] private workouts invisible, implemented coachonly workout list view --- rowers/views.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rowers/views.py b/rowers/views.py index 0a5aa3d1..137f4b77 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1993,9 +1993,14 @@ def workouts_view(request,message='',successmessage='', except Team.DoesNotExist: raise Http404("Team doesn't exist") - workouts = Workout.objects.filter(team=theteam, + if theteam.viewing == 'allmembers' or theteam.manager == request.user: + workouts = Workout.objects.filter(team=theteam, startdatetime__gte=startdate, startdatetime__lte=enddate).order_by("-date", "-starttime") + elif theteam.viewing == 'coachonly': + workouts = Workout.objects.filter(team=theteam,user=r, + startdatetime__gte=startdate, + startdatetime__lte=enddate).order_by("-date","-starttime") else: @@ -2128,6 +2133,9 @@ def workout_view(request,id=0): try: # check if valid ID exists (workout exists) row = Workout.objects.get(id=id) + if row.privacy == 'private': + raise Http404("Not allowed to view this workout") + g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") r = Rower.objects.get(id=row.user.id) u = User.objects.get(id=r.user.id) From 5deaa194f06879d196452c3a9be5581730554f3e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 15 Feb 2017 16:16:02 +0100 Subject: [PATCH 6/6] making upload options sticky and adding make private --- rowers/dataprep.py | 22 ++++++++++++++++------ rowers/forms.py | 7 ++++--- rowers/views.py | 42 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 386f8a88..0b16e99f 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -228,7 +228,8 @@ def timedeltaconv(x): # Processes painsled CSV file to database def save_workout_database(f2,r,dosmooth=True,workouttype='rower', dosummary=True,title='Workout', - notes='',totaldist=0,totaltime=0): + notes='',totaldist=0,totaltime=0, + makeprivate=False): message = None powerperc = 100*np.array([r.pw_ut2, r.pw_ut1, @@ -313,12 +314,18 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', workoutstarttime = row.rowdatetime.strftime('%H:%M:%S') workoutstartdatetime = thetimezone.localize(row.rowdatetime).astimezone(utc) + if makeprivate: + privacy = 'private' + else: + privacy = 'visible' + # check for duplicate start times ws = Workout.objects.filter(starttime=workoutstarttime, user=r) if (len(ws) != 0): message = "Warning: This workout probably already exists in the database" + w = Workout(user=r,name=title,date=workoutdate, workouttype=workouttype, duration=duration,distance=totaldist, @@ -326,15 +333,16 @@ def save_workout_database(f2,r,dosmooth=True,workouttype='rower', starttime=workoutstarttime, csvfilename=f2,notes=notes,summary=summary, maxhr=maxhr,averagehr=averagehr, - startdatetime=workoutstartdatetime) + startdatetime=workoutstartdatetime, + privacy=privacy) w.save() - ts = Team.objects.filter(rower=r) - - for t in ts: - w.team.add(t) + if privacy == 'visible': + ts = Team.objects.filter(rower=r) + for t in ts: + w.team.add(t) # put stroke data in database res = dataprep(row.df,id=w.id,bands=True, @@ -422,6 +430,7 @@ def handle_nonpainsled(f2,fileformat,summary=''): def new_workout_from_file(r,f2, workouttype='rower', title='Workout', + makeprivate=False, notes=''): message = None fileformat = get_file_type(f2) @@ -470,6 +479,7 @@ def new_workout_from_file(r,f2, dosummary = (fileformat != 'fit') id,message = save_workout_database(f2,r, workouttype=workouttype, + makeprivate=makeprivate, dosummary=dosummary, title=title) diff --git a/rowers/forms.py b/rowers/forms.py index 0644ff5b..0b7c53b7 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -76,14 +76,15 @@ class UploadOptionsForm(forms.Form): ('distanceplot','Distance Plot'), ('pieplot','Pie Chart'), ) - make_plot = forms.BooleanField(initial=False) + make_plot = forms.BooleanField(initial=False,required=False) plottype = forms.ChoiceField(required=False, choices=plotchoices, initial='timeplot') - upload_to_C2 = forms.BooleanField(initial=False) + upload_to_C2 = forms.BooleanField(initial=False,required=False) + makeprivate = forms.BooleanField(initial=False,required=False) class Meta: - fields = ['make_plot','plottype','upload_toc2'] + fields = ['make_plot','plottype','upload_toc2','makeprivate'] # This form is used on the Analysis page to add a custom distance/time # trial and predict the pace diff --git a/rowers/views.py b/rowers/views.py index 137f4b77..b18410b2 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -4181,7 +4181,26 @@ def workout_getc2workout_view(request,c2id): # This is the main view for processing uploaded files @login_required() -def workout_upload_view(request,message=""): +def workout_upload_view(request,message="", + uploadoptions={ + 'makeprivate':False, + 'make_plot':False, + 'upload_to_C2':False, + 'plottype':'timeplot', + }): + + if 'uploadoptions' in request.session: + uploadoptions = request.session['uploadoptions'] + else: + request.session['uploadoptions'] = uploadoptions + + + + makeprivate = uploadoptions['makeprivate'] + make_plot = uploadoptions['make_plot'] + plottype = uploadoptions['plottype'] + upload_toc2 = uploadoptions['upload_to_C2'] + r = Rower.objects.get(user=request.user) if request.method == 'POST': form = DocumentsForm(request.POST,request.FILES) @@ -4193,10 +4212,22 @@ def workout_upload_view(request,message=""): workouttype = form.cleaned_data['workouttype'] notes = form.cleaned_data['notes'] - make_plot = request.POST.getlist('make_plot') - plottype = request.POST['plottype'] - upload_to_c2 = request.POST.getlist('upload_to_C2') + if optionsform.is_valid(): + make_plot = optionsform.cleaned_data['make_plot'] + plottype = optionsform.cleaned_data['plottype'] + upload_to_c2 = optionsform.cleaned_data['upload_to_C2'] + makeprivate = optionsform.cleaned_data['makeprivate'] + + uploadoptions = { + 'makeprivate':makeprivate, + 'make_plot':make_plot, + 'plottype':plottype, + 'upload_to_C2':upload_to_c2, + } + + + request.session['uploadoptions'] = uploadoptions f1 = res[0] # file name f2 = res[1] # file name incl media directory @@ -4204,6 +4235,7 @@ def workout_upload_view(request,message=""): id,message = dataprep.new_workout_from_file(r,f2, workouttype=workouttype, + makeprivate=makeprivate, title = t, notes='') if not id: @@ -4344,7 +4376,7 @@ def workout_upload_view(request,message=""): return response else: form = DocumentsForm() - optionsform = UploadOptionsForm() + optionsform = UploadOptionsForm(initial=uploadoptions) return render(request, 'document_form.html', {'form':form, 'optionsform': optionsform,