diff --git a/rowers/forms.py b/rowers/forms.py index e9e4f31e..da1c5ed0 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -90,6 +90,24 @@ class UploadOptionsForm(forms.Form): class Meta: fields = ['make_plot','plottype','upload_toc2','makeprivate'] +# The form to indicate additional actions to be performed immediately +# after a successful upload. This version allows the Team manager to select +# a team member +class TeamUploadOptionsForm(forms.Form): + plotchoices = ( + ('timeplot','Time Plot'), + ('distanceplot','Distance Plot'), + ('pieplot','Pie Chart'), + ) + make_plot = forms.BooleanField(initial=False,required=False) + plottype = forms.ChoiceField(required=False, + choices=plotchoices, + initial='timeplot', + label='Plot Type') + + class Meta: + fields = ['make_plot','plottype'] + # This form is used on the Analysis page to add a custom distance/time # trial and predict the pace class PredictedPieceForm(forms.Form): diff --git a/rowers/teams.py b/rowers/teams.py index dcf7371b..6ab792d3 100644 --- a/rowers/teams.py +++ b/rowers/teams.py @@ -159,7 +159,7 @@ def create_request(team,user): if r2 in Rower.objects.filter(team=team): return (0,'Already a member of that team') - if count_club_members(team.manager)+count_invites(team.manager) < r.clubsize: + if count_club_members(team.manager)+count_invites(team.manager) <= r.clubsize: codes = [i.code for i in TeamRequest.objects.all()] code = uuid.uuid4().hex[:10].upper() # prevent duplicates @@ -200,7 +200,7 @@ def create_invite(team,manager,user=None,email=''): except Rower.MultipleObjectsReturned: return (0,'There is more than one user with that email address') - if count_club_members(team.manager)+count_invites(team.manager) < r.clubsize: + if count_club_members(team.manager)+count_invites(team.manager) <= r.clubsize: codes = [i.code for i in TeamInvite.objects.all()] code = uuid.uuid4().hex[:10].upper() # prevent duplicates diff --git a/rowers/templates/base.html b/rowers/templates/base.html index b24c425f..8e6487a2 100644 --- a/rowers/templates/base.html +++ b/rowers/templates/base.html @@ -1,5 +1,6 @@ {% load cookielaw_tags %} {% load analytical %} +{% load rowerfilters %} @@ -143,13 +144,17 @@ {% endif %}
- {% if user.is_authenticated and teams %} + {% if user.is_authenticated and user|has_teams %}
+ Facebook group

{% endblock %}
diff --git a/rowers/templates/basefront.html b/rowers/templates/basefront.html index 8dc45748..15017318 100644 --- a/rowers/templates/basefront.html +++ b/rowers/templates/basefront.html @@ -1,5 +1,6 @@ {% load cookielaw_tags %} {% load analytical %} +{% load rowerfilters %} @@ -31,7 +32,6 @@ -
+
{% analytical_body_top %}
@@ -163,13 +163,17 @@ {% endif %}
- {% if user.is_authenticated and teams %} + {% if user.is_authenticated and user|user_teams %} {% endblock %}
@@ -257,5 +261,16 @@
{% analytical_body_bottom %} + diff --git a/rowers/templates/document_form.html b/rowers/templates/document_form.html index c6f42e9b..26d6d14f 100644 --- a/rowers/templates/document_form.html +++ b/rowers/templates/document_form.html @@ -1,12 +1,17 @@ {% extends "base.html" %} {% load staticfiles %} +{% load rowerfilters %} {% block title %}File loading{% endblock %} {% block content %}
-

Upload Workout File

+

Upload Workout File

+ {% if user.is_authenticated and user|is_manager %} +

Looking for Team Manager + Upload?

+ {% endif %} {% if form.errors %}

Please correct the error{{ form.errors|pluralize }} below. diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index 9073b53a..9adf278b 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -35,7 +35,9 @@

{% if team %} - {% include "teambuttons.html" with teamid=team.id %} +
+ {% include "teambuttons.html" with teamid=team.id %} +

{{ team.name }} Team Workouts

{% else %}

My Workouts

diff --git a/rowers/templates/team_document_form.html b/rowers/templates/team_document_form.html new file mode 100644 index 00000000..aadc4f9e --- /dev/null +++ b/rowers/templates/team_document_form.html @@ -0,0 +1,55 @@ +{% extends "base.html" %} +{% load staticfiles %} + +{% block title %}File loading{% endblock %} + +{% block content %} + +
+

Upload Workout File

+ {% if form.errors %} +

+ Please correct the error{{ form.errors|pluralize }} below. +

+ {% endif %} + + + {{ rowerform.as_table }} + {{ form.as_table }} +
+ {% csrf_token %} +
+ +
+
+ + + + + +{% endblock %} diff --git a/rowers/templates/teambuttons.html b/rowers/templates/teambuttons.html index 6fa2c663..12351b95 100644 --- a/rowers/templates/teambuttons.html +++ b/rowers/templates/teambuttons.html @@ -1,3 +1,4 @@ +{% load rowerfilters %}

Team Workouts @@ -8,8 +9,17 @@ Multi Compare

-
+

Team Page -

-
+

+
+
+ {% if user|is_manager and user|has_teams %} +

+ Upload Workout +

+ {% else %} +

 

+ {% endif %} +
diff --git a/rowers/templates/teams.html b/rowers/templates/teams.html index cf798b52..959a4748 100644 --- a/rowers/templates/teams.html +++ b/rowers/templates/teams.html @@ -36,10 +36,76 @@

+
+
+ {% if otherteams %} +

Other Teams

+ + + + + + + + + {% for team in otherteams %} + + + + + {% endfor %} + +
NameManager
+ {{ team.name }} + + {{ team.manager.first_name }} {{ team.manager.last_name }} +
+ + {% else %} +

 

+ {% endif %} +
+
+ +
+
+ {% if user.rower.rowerplan == 'coach' %} +

Teams I manage

+

Number of members: {{ clubsize }}

+

Maximum club size: {{ max_clubsize }}

+ {% if myteams %} + + + + + + + + + {% for team in myteams %} + + + + + {% endfor %} + +
NameManager
+ {{ team.name }} + +
+ Delete +
+
+ {% endif %} +
+ New Team +
+ {% else %} +

 

+ {% endif %}
{% if invites or requests or myrequests or myinvites %} -

Invitations and Requests

@@ -126,70 +192,5 @@ -
-
- {% if user.rower.rowerplan == 'coach' %} -

Teams I manage

- {% if myteams %} -
- - - - - - - - {% for team in myteams %} - - - - - {% endfor %} - -
NameManager
- {{ team.name }} - -
- Delete -
-
- {% endif %} -
- New Team -
- {% else %} -

 

- {% endif %} -
-
- {% if otherteams %} -

Other Teams

- - - - - - - - - {% for team in otherteams %} - - - - - {% endfor %} - -
NameManager
- {{ team.name }} - - {{ team.manager.first_name }} {{ team.manager.last_name }} -
- - {% else %} -

 

- {% endif %} -
-
- {% endblock %} diff --git a/rowers/templatetags/rowerfilters.py b/rowers/templatetags/rowerfilters.py index dcbef68d..d3a610fd 100644 --- a/rowers/templatetags/rowerfilters.py +++ b/rowers/templatetags/rowerfilters.py @@ -65,3 +65,35 @@ def times(number): def get_field_id(id,s,form): field_name = s+str(id) return form.__getitem__(field_name) + +from rowers.models import Rower,Team + +@register.filter +def is_manager(user): + r = Rower.objects.get(user=user) + return r.rowerplan == 'coach' + +@register.filter +def user_teams(user): + try: + therower = Rower.objects.get(user=user) + teams1 = therower.team.all() + teams2 = Team.objects.filter(manager=user) + teams = list(set(teams1).union(set(teams2))) + except TypeError: + teams = [] + + return teams + +@register.filter +def has_teams(user): + try: + therower = Rower.objects.get(user=user) + teams1 = therower.team.all() + teams2 = Team.objects.filter(manager=user) + teams = list(set(teams1).union(set(teams2))) + return True + except TypeError: + return False + + return False diff --git a/rowers/tests.py b/rowers/tests.py index ffb4d821..7eb5a4b2 100644 --- a/rowers/tests.py +++ b/rowers/tests.py @@ -702,7 +702,7 @@ class ViewTest(TestCase): f.close() - self.assertRedirects(response, expected_url='/rowers/workout/upload/This%20C2%20logbook%20summary%20does%20not%20contain%20stroke%20data.%20Please%20download%20the%20Export%20Stroke%20Data%20file%20from%20the%20workout%20details%20on%20the%20C2%20logbook.', + self.assertRedirects(response, expected_url='/rowers/workout/upload/c/This%20C2%20logbook%20summary%20does%20not%20contain%20stroke%20data.%20Please%20download%20the%20Export%20Stroke%20Data%20file%20from%20the%20workout%20details%20on%20the%20C2%20logbook.', status_code=302,target_status_code=200) self.assertEqual(response.status_code, 200) diff --git a/rowers/urls.py b/rowers/urls.py index 9097ed93..48d544fe 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -157,8 +157,12 @@ urlpatterns = [ url(r'^graph/(\d+)/$',views.graph_show_view), url(r'^graph/(\d+)/deleteconfirm$',views.graph_delete_confirm_view), url(r'^graph/(\d+)/delete$',views.graph_delete_view), + url(r'^workout/upload/team/s/(?P\w+.*)/c/(?P\w+.*)/$',views.team_workout_upload_view), + url(r'^workout/upload/team/c/(?P\w+.*)/$',views.team_workout_upload_view), + url(r'^workout/upload/team/s/(?P\w+.*)/$',views.team_workout_upload_view), + url(r'^workout/upload/team/$',views.team_workout_upload_view), url(r'^workout/upload/$',views.workout_upload_view), - url(r'^workout/upload/(.+.*)$',views.workout_upload_view), + url(r'^workout/upload/c/(?P\w+.*)$',views.workout_upload_view), url(r'^workout/(?P\d+)/histo$',views.workout_histo_view), url(r'^workout/(?P\d+)/forcecurve$',views.workout_forcecurve_view), url(r'^workout/(?P\d+)/unsubscribe$',views.workout_unsubscribe_view), diff --git a/rowers/views.py b/rowers/views.py index 387bcb4e..e9d6c065 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -14,7 +14,10 @@ from django.http import ( HttpResponseNotFound,Http404 ) from django.contrib.auth import authenticate, login, logout -from rowers.forms import LoginForm,DocumentsForm,UploadOptionsForm +from rowers.forms import ( + LoginForm,DocumentsForm,UploadOptionsForm, + TeamUploadOptionsForm, + ) from django.core.urlresolvers import reverse from django.core.exceptions import PermissionDenied from django.template import RequestContext @@ -1004,6 +1007,7 @@ def workout_strava_upload_view(request,id=0): # ready to upload. Hurray try: w = Workout.objects.get(id=id) + r = w.user except Workout.DoesNotExist: raise Http404("Workout doesn't exist") if (checkworkoutuser(request.user,w)): @@ -1086,16 +1090,18 @@ def workout_strava_upload_view(request,id=0): @login_required() def workout_c2_upload_view(request,id=0): message = "" - try: - thetoken = c2_open(request.user) - except C2NoTokenError: - return HttpResponseRedirect("/rowers/me/c2authorize/") - # ready to upload. Hurray try: w = Workout.objects.get(id=id) + r = w.user except Workout.DoesNotExist: raise Http404("Workout doesn't exist") + + try: + thetoken = c2_open(r.user) + except C2NoTokenError: + return HttpResponseRedirect("/rowers/me/c2authorize/") + if (checkworkoutuser(request.user,w)): c2userid = c2stuff.get_userid(thetoken) if not c2userid: @@ -1172,15 +1178,17 @@ def workout_c2_upload_view(request,id=0): def workout_runkeeper_upload_view(request,id=0): message = "" try: - thetoken = runkeeper_open(request.user) + w = Workout.objects.get(id=id) + r = w.user + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + + try: + thetoken = runkeeper_open(r.user) except RunKeeperNoTokenError: return HttpResponseRedirect("/rowers/me/runkeeperauthorize/") # ready to upload. Hurray - try: - w = Workout.objects.get(id=id) - except Workout.DoesNotExist: - raise Http404("Workout doesn't exist") if (checkworkoutuser(request.user,w)): data = runkeeperstuff.createrunkeeperworkoutdata(w) @@ -1233,17 +1241,19 @@ def workout_runkeeper_upload_view(request,id=0): @login_required() def workout_sporttracks_upload_view(request,id=0): message = "" - try: - thetoken = sporttracks_open(request.user) - except SportTracksNoTokenError: - return HttpResponseRedirect("/rowers/me/sporttracksauthorize/") - # ready to upload. Hurray try: w = Workout.objects.get(id=id) + r = w.user except Workout.DoesNotExist: raise Http404("Workout doesn't exist") + try: + thetoken = sporttracks_open(r.user) + except SportTracksNoTokenError: + return HttpResponseRedirect("/rowers/me/sporttracksauthorize/") + + if (checkworkoutuser(request.user,w)): data = sporttracksstuff.createsporttracksworkoutdata(w) if not data: @@ -5350,6 +5360,183 @@ def workout_upload_view(request,message="", 'optionsform': optionsform, 'message':message}) +# This is the main view for processing uploaded files +@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None) +def team_workout_upload_view(request,message="", + successmessage="", + uploadoptions={ + 'make_plot':False, + 'plottype':'timeplot', + }): + + if 'uploadoptions' in request.session: + uploadoptions = request.session['uploadoptions'] + else: + request.session['uploadoptions'] = uploadoptions + + + myteams = Team.objects.filter(manager=request.user) + + make_plot = uploadoptions['make_plot'] + plottype = uploadoptions['plottype'] + + r = Rower.objects.get(user=request.user) + if request.method == 'POST': + form = DocumentsForm(request.POST,request.FILES) + optionsform = TeamUploadOptionsForm(request.POST) + + rowerform = TeamInviteForm(request.POST) + rowerform.fields.pop('email') + rowerform.fields['user'].queryset = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct() + if form.is_valid(): + f = request.FILES['file'] + res = handle_uploaded_file(f) + t = form.cleaned_data['title'] + if rowerform.is_valid(): + u = rowerform.cleaned_data['user'] + if u: + r = Rower.objects.get(user=u) + else: + message = 'Please select a rower' + response = render(request, + 'team_document_form.html', + {'form':form, + 'teams':get_my_teams(request.user), + 'optionsform': optionsform, + 'rowerform': rowerform, + 'message':message, + 'successmessage':successmessage, + }) + + return response + + workouttype = form.cleaned_data['workouttype'] + + notes = form.cleaned_data['notes'] + + if optionsform.is_valid(): + make_plot = optionsform.cleaned_data['make_plot'] + plottype = optionsform.cleaned_data['plottype'] + + uploadoptions = { + 'makeprivate':False, + 'make_plot':make_plot, + 'plottype':plottype, + 'upload_to_C2':False, + } + + + request.session['uploadoptions'] = uploadoptions + + f1 = res[0] # file name + f2 = res[1] # file name incl media directory + + + id,message,f2 = dataprep.new_workout_from_file(r,f2, + workouttype=workouttype, + makeprivate=False, + title = t, + notes='') + if not id: + url = reverse(team_workout_upload_view, + args=[str(message)]) + response = HttpResponseRedirect(url) + return response + + else: + if message: + successmessage = "The workout was added to the user's account" + url = reverse(team_workout_upload_view, + kwargs = { + 'message':message, + 'successmessage':successmessage, + }) + else: + successmessage = "The workout was added to the user's account" + url = reverse(team_workout_upload_view, + kwargs = { + 'successmessage':successmessage, + }) + + response = HttpResponseRedirect(url) + w = Workout.objects.get(id=id) + + if (make_plot): + imagename = f1[:-4]+'.png' + fullpathimagename = 'static/plots/'+imagename + powerperc = 100*np.array([r.pw_ut2, + r.pw_ut1, + r.pw_at, + r.pw_tr,r.pw_an])/r.ftp + + hrpwrdata = { + 'hrmax':r.max, + 'hrut2':r.ut2, + 'hrut1':r.ut1, + 'hrat':r.at, + 'hrtr':r.tr, + 'hran':r.an, + 'ftp':r.ftp, + 'powerperc':serialize_list(powerperc), + 'powerzones':serialize_list(r.powerzones), + } + + # make plot - asynchronous task + plotnrs = { + 'timeplot':1, + 'distanceplot':2, + 'pieplot':3, + } + + plotnr = plotnrs[plottype] + if (workouttype=='water'): + plotnr = plotnr+3 + + + if settings.DEBUG: + res = handle_makeplot.delay(f1,f2,t, + hrpwrdata,plotnr, + imagename) + else: + res = queue.enqueue(handle_makeplot,f1,f2, + t,hrpwrdata, + plotnr,imagename) + + + i = GraphImage(workout=w, + creationdatetime=timezone.now(), + filename=fullpathimagename) + i.save() + + + + else: + response = render(request, + 'team_document_form.html', + {'form':form, + 'teams':get_my_teams(request.user), + 'optionsform': optionsform, + 'rowerform': rowerform, + 'message':message, + 'successmessage':successmessage, + }) + + return response + else: + form = DocumentsForm() + optionsform = TeamUploadOptionsForm(initial=uploadoptions) + rowerform = TeamInviteForm() + rowerform.fields.pop('email') + rowerform.fields['user'].queryset = User.objects.filter(rower__isnull=False,rower__team__in=myteams).distinct() + return render(request, 'team_document_form.html', + {'form':form, + 'teams':get_my_teams(request.user), + 'optionsform': optionsform, + 'rowerform':rowerform, + 'message':message, + 'successmessage':successmessage, + }) + # Ask the user if he really wants to delete the workout @@ -6416,11 +6603,14 @@ def rower_teams_view(request,message='',successmessage=''): requests = TeamRequest.objects.filter(user=request.user) myrequests = TeamRequest.objects.filter(team__in=myteams) myinvites = TeamInvite.objects.filter(team__in=myteams) + clubsize = teams.count_invites(request.user)+teams.count_club_members(request.user) + max_clubsize = r.clubsize return render(request, 'teams.html', { 'teams':ts, - 'teams':get_my_teams(request.user), + 'clubsize':clubsize, + 'max_clubsize':max_clubsize, 'myteams':myteams, 'invites':invites, 'otherteams':otherteams, @@ -6431,6 +6621,7 @@ def rower_teams_view(request,message='',successmessage=''): 'successmessage':successmessage, 'myinvites':myinvites, }) + @user_passes_test(iscoachmember,login_url="/",redirect_field_name=None) def invitation_revoke_view(request,id): res,text = teams.revoke_invite(request.user,id) diff --git a/rowsandall_app/settings.py b/rowsandall_app/settings.py index c2a90e49..c62a27bb 100644 --- a/rowsandall_app/settings.py +++ b/rowsandall_app/settings.py @@ -29,7 +29,8 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'z_@2yia858=2i1cygo4ne3+14&i_&@wlty68$q1igdvn=9k5mo' +SECRET_KEY = CFG['secret_key'] + # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False @@ -104,7 +105,6 @@ TEMPLATES = [ 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', - 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.i18n', diff --git a/static/cookielaw/banner.html b/static/cookielaw/banner.html new file mode 100644 index 00000000..e9695a3f --- /dev/null +++ b/static/cookielaw/banner.html @@ -0,0 +1,11 @@ +{% load i18n %} + +
+
+
{% trans "COOKIE_INFO_HEADER" %}
+

+ {% trans "COOKIE_INFO_OK" %} + {% trans "COOKIE_INFO_PARA" %} +

+
+
diff --git a/static/cookielaw/css/cookielaw.css b/static/cookielaw/css/cookielaw.css index 5e76378e..0795ae89 100644 --- a/static/cookielaw/css/cookielaw.css +++ b/static/cookielaw/css/cookielaw.css @@ -8,7 +8,7 @@ } #CookielawBanner #CookielawCross { - background: url('../img/close.png') no-repeat 0 0; + background: url('/static/img/close.png') no-repeat 0 0; cursor: pointer; display: block; position: absolute; diff --git a/static/css/rowsandall.css b/static/css/rowsandall.css index 1f72281d..c45dbe8b 100644 --- a/static/css/rowsandall.css +++ b/static/css/rowsandall.css @@ -1,4 +1,30 @@ +#main { + background-color: transparent; + -webkit-background-size: cover; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + min-height: 759px; + min-width: 1024px; + width: 100%; + height: auto; +} +.one { + background-image: url("/static/img/landing1.jpg"); +} + +.two { + background-image: url("/static/img/landing1.jpg"); +} + +.three { + background-image: url("/static/img/landing1.jpg"); +} + +.four { + background-image: url("/static/img/landing1.jpg"); +} html { font-size: 62.5%; diff --git a/static/img/close.png b/static/img/close.png new file mode 100644 index 00000000..839576e6 Binary files /dev/null and b/static/img/close.png differ diff --git a/static/img/landing2.jpg b/static/img/landing2.jpg index b780db2f..7795d10c 100644 Binary files a/static/img/landing2.jpg and b/static/img/landing2.jpg differ diff --git a/static/js/background.js b/static/js/background.js new file mode 100644 index 00000000..cb970851 --- /dev/null +++ b/static/js/background.js @@ -0,0 +1,8 @@ + var num = (Math.floor(Math.random()*4)); + + + var array = ['one', 'two', 'three', 'four']; + + var elem = document.getElementById('main'); + + elem.classList.add(array[num]);