diff --git a/rowers/models.py b/rowers/models.py index 209fed30..6846f06f 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -3343,3 +3343,28 @@ class WorkoutCommentForm(ModelForm): 'comment': forms.Textarea, } +# A comment by a user on a training +class PlannedSessionComment(models.Model): + comment = models.TextField(max_length=300) + created = models.DateTimeField(default=timezone.now) + read = models.BooleanField(default=False) + notification = models.BooleanField(default=True,verbose_name="Subscribe to new comment notifications") + user = models.ForeignKey(User) + plannedsession = models.ForeignKey(PlannedSession) + + def __unicode__(self): + return u'Comment to: {w} by {u1} {u2}'.format( + w=self.workout, + u1 = self.user.first_name, + u2 = self.user.last_name, + ) + + +class PlannedSessionCommentForm(ModelForm): + class Meta: + model = PlannedSessionComment + fields = ['comment','notification'] + widgets = { + 'comment': forms.Textarea, + } + diff --git a/rowers/tasks.py b/rowers/tasks.py index ad3a8947..7f4b756c 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -1737,7 +1737,7 @@ def handle_sendemailnewresponse(first_name, last_name, debug=False,**kwargs): fullemail = email from_email = 'Rowsandall ' - subject = 'New comment on workout ' + workoutname + subject = 'New comment on session ' + workoutname comment = u''+comment @@ -1745,7 +1745,14 @@ def handle_sendemailnewresponse(first_name, last_name, if debug: siteurl = SITE_URL_DEV + sessiontype = 'workout' + if 'sessiontype' in kwargs: + sessiontype=kwargs.pop('sessiontype') + commentlink = '/rowers/workout/{workoutid}/comment/'.format(workoutid=workoutid) + if 'commentlink' in kwargs: + commentlink = kwargs.pop('commentlink') + d = { 'first_name':first_name, 'commenter_first_name':commenter_first_name, @@ -1754,10 +1761,14 @@ def handle_sendemailnewresponse(first_name, last_name, 'workoutname':workoutname, 'siteurl':siteurl, 'workoutid':workoutid, - 'commentid':commentid + 'commentid':commentid, + 'sessiontype':sessiontype, + 'commentlink':commentlink, } - res = send_template_email(from_email,[fullemail],subject,'teamresponseemail.html',d,**kwargs) + res = send_template_email(from_email, + [fullemail], + subject,'teamresponseemail.html',d,**kwargs) return 1 @@ -1776,7 +1787,7 @@ def handle_sendemailnewcomment(first_name, fullemail = email from_email = 'Rowsandall ' - subject = 'New comment on workout ' + workoutname + subject = 'New comment on session ' + workoutname comment = u''+comment @@ -1784,7 +1795,14 @@ def handle_sendemailnewcomment(first_name, if debug: siteurl = SITE_URL_DEV + sessiontype = 'workout' + if 'sessiontype' in kwargs: + sessiontype=kwargs.pop('sessiontype') + commentlink = '/rowers/workout/{workoutid}/comment/'.format(workoutid=workoutid) + if 'commentlink' in kwargs: + commentlink = kwargs.pop('commentlink') + d = { 'first_name':first_name, 'commenter_first_name':commenter_first_name, @@ -1793,6 +1811,8 @@ def handle_sendemailnewcomment(first_name, 'workoutname':workoutname, 'siteurl':siteurl, 'workoutid':workoutid, + 'sessiontype':sessiontype, + 'commentlink':commentlink, } res = send_template_email(from_email,[fullemail],subject, diff --git a/rowers/templates/plannedsession_comments.html b/rowers/templates/plannedsession_comments.html new file mode 100644 index 00000000..1236e0c5 --- /dev/null +++ b/rowers/templates/plannedsession_comments.html @@ -0,0 +1,47 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} +{% load tz %} + +{% block scripts %} +{% include "monitorjobs.html" %} +{% endblock %} + +{% block title %}Comment Session {% endblock %} + +{% block main %} + +

Comments {{ plannedession.name }}

+ +
    +
  • + {% for c in comments %} +

    + {{ c.created }} + {{ c.user.first_name }} {{ c.user.last_name }} +

    +
    + {{ c.comment }} +
    +
    +

    + {% endfor %} +
    + + {{ form.as_table }} +
    + {% csrf_token %} + +
    +
  • +
+ +{% endblock %} + +{% block sidebar %} +{% if 'racing' in active %} +{% include 'menu_racing.html' %} +{% else %} +{% include 'menu_plan.html' %} +{% endif %} +{% endblock %} diff --git a/rowers/templates/plannedsessionview.html b/rowers/templates/plannedsessionview.html index dc96de68..24510a1d 100644 --- a/rowers/templates/plannedsessionview.html +++ b/rowers/templates/plannedsessionview.html @@ -135,6 +135,20 @@ title="Compare the workouts of all athletes who did this session">Compare Workouts

+
  • +
    +

    Comments ({{ comments|length }})

    + {% for c in comments %} +

    + {{ c.user.first_name }} {{ c.user.last_name }} - {{ c.created }}
    + {{ c.comment }} +

    + {% endfor %} +

    + Add a comment +

    +
    +
  • {% if coursescript %}
  • Course

    diff --git a/rowers/templates/teamresponseemail.html b/rowers/templates/teamresponseemail.html index 9ee293bc..4144b282 100644 --- a/rowers/templates/teamresponseemail.html +++ b/rowers/templates/teamresponseemail.html @@ -5,7 +5,7 @@

    {{ commenter_first_name }} {{ commenter_last_name }} has written - a new comment on workout {{ workoutname }} + a new comment on {{ sessiontype }} {{ workoutname }}

    {{ comment }} @@ -14,8 +14,8 @@ You can read the comment here:

    - - {{ siteurl }}/rowers/workout/{{ workoutid }}/comment + + {{ siteurl }}{{ commentlink }}

    diff --git a/rowers/templates/virtualevent.html b/rowers/templates/virtualevent.html index 9711bb3c..e60338cd 100644 --- a/rowers/templates/virtualevent.html +++ b/rowers/templates/virtualevent.html @@ -423,6 +423,20 @@ {% endif %} {% endfor %}

  • +
  • +
    +

    Comments ({{ comments|length }})

    + {% for c in comments %} +

    + {{ c.user.first_name }} {{ c.user.last_name }} - {{ c.created }}
    + {{ c.comment }} +

    + {% endfor %} +

    + Add a comment +

    +
    +
  • diff --git a/rowers/tests/test_plans.py b/rowers/tests/test_plans.py index d6d73410..ff464120 100644 --- a/rowers/tests/test_plans.py +++ b/rowers/tests/test_plans.py @@ -486,6 +486,25 @@ class SessionCompleteTest(TestCase): self.assertEqual(verdict,'on target') + def test_session_comment(self): + login = self.c.login(username=self.u.username, password=self.password) + self.assertTrue(login) + + + url = reverse('plannedsession_comment_view',kwargs={'id':self.ps_rscore.id}) + + response = self.c.get(url) + self.assertEqual(response.status_code,200) + + form_data = { + 'comment': faker.text() + } + + form = WorkoutCommentForm(form_data) + self.assertTrue(form.is_valid()) + + response = self.c.post(url,form_data,follow=True) + self.assertEqual(response.status_code,200) def test_session1_exact_complete(self): diff --git a/rowers/tests/testdata/testdata.csv.gz b/rowers/tests/testdata/testdata.csv.gz index 813fcfa3..36e5640a 100644 Binary files a/rowers/tests/testdata/testdata.csv.gz and b/rowers/tests/testdata/testdata.csv.gz differ diff --git a/rowers/urls.py b/rowers/urls.py index 3fe4c64b..5200a70e 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -608,6 +608,10 @@ urlpatterns = [ name='plannedsessions_coach_view'), url(r'^sessions/print/?/$',views.plannedsessions_print_view, name='plannedsessions_print_view'), + url(r'^sessions/(?P\d+)/comments/user/(?P\d+)/$',views.plannedsession_comment_view, + name='plannedsession_comment_view'), + url(r'^sessions/(?P\d+)/comments/$',views.plannedsession_comment_view, + name='plannedsession_comment_view'), url(r'^sessions/print/user/(?P\d+)/$',views.plannedsessions_print_view, name='plannedsessions_print_view'), url(r'^sessions/sendcalendar/$',views.plannedsessions_icsemail_view, diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index 32de7ce1..f4d43afa 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -1,6 +1,150 @@ from statements import * +@login_required() +def plannedsession_comment_view(request,id=0,userid=0): + r = getrequestrower(request,userid=userid) + try: + ps = PlannedSession.objects.get(id=id) + except PlannedSession.DoesNotExist: + raise Http404("Planned Session does not exist") + + m = ps.manager + mm = Rower.objects.get(user=m) + + if ps.manager != request.user and ps.sessiontype not in ['race','indoorrace']: + if r.rowerplan == 'coach' and r not in ps.rower.all(): + teams = Team.objects.filter(manager=request.user) + members = Rower.objects.filter(team__in=teams).distinct() + teamusers = [m.user for m in members] + if ps.manager not in teamusers: + raise PermissionDenied("You do not have access to this session") + elif r not in ps.rower.all(): + raise PermissionDenied("You do not have access to this session") + + comments = PlannedSessionComment.objects.filter(plannedsession=ps).order_by("created") + + if request.method == 'POST': + manager = ps.manager + form = PlannedSessionCommentForm(request.POST) + if form.is_valid(): + cd = form.cleaned_data + comment = cd['comment'] + comment = bleach.clean(comment) + if isinstance(comment,unicode): + comment = comment.encode('utf8') + elif isinstance(comment, str): + comment = comment.decode('utf8') + + notification = cd['notification'] + c = PlannedSessionComment(plannedsession=ps,user=request.user,comment=comment, + notification=notification) + c.save() + url = reverse('plannedsession_comment_view', + kwargs={ + 'id':id + }) + message = '{name} says: {comment}'.format( + name = request.user.first_name, + comment = comment, + url = url, + ) + if request.user != manager: + a_messages.info(r.user,message.encode('ascii','ignore')) + + sessiontype = 'training session' + if ps.sessiontype == 'race': + sessiontype = 'online virtual race' + elif ps.sessiontype == 'indoorrace': + sessiontype = 'indoor online virtual race' + + res = myqueue(queuehigh, + handle_sendemailnewcomment,r.user.first_name, + r.user.last_name, + r.user.email, + request.user.first_name, + request.user.last_name, + comment,ps.name,ps.id, + emailbounced = r.emailbounced, + sessiontype = sessiontype, + commentlink = url + ) + + commenters = {oc.user for oc in comments if oc.notification} + for u in commenters: + a_messages.info(u,message) + if u != request.user and u != r.user: + ocr = Rower.objects.get(user=u) + res = myqueue(queuelow, + handle_sendemailnewresponse, + u.first_name, + u.last_name, + u.email, + request.user.first_name, + request.user.last_name, + comment, + ps.name, + ps.id, + c.id, + emailbounced = ocr.emailbounced, + sessiontype = sessiontype, + commentlink = url + ) + + url = reverse('plannedsession_comment_view',kwargs={'id':ps.id}) + return HttpResponseRedirect(url) + + + form = WorkoutCommentForm() + + rower = getrower(request.user) + + if ps.sessiontype in ['race','indoorrace']: + breadcrumbs = [ + { + 'url':reverse('virtualevents_view'), + 'name': 'Races' + }, + { + 'url': reverse('virtualevent_view',kwargs={'id':ps.id}), + 'name': ps.name + }, + { + 'url':reverse('plannedsession_comment_view',kwargs={'id':ps.id}), + 'name': 'Comments' + } + ] + + active = 'nav-racing' + + else: + breadcrumbs = [ + { + 'url':reverse('virtualevents_view'), + 'name': 'Races' + }, + { + 'url': reverse('virtualevent_view',kwargs={'id':ps.id}), + 'name': ps.name + }, + { + 'url':reverse('plannedsession_comment_view',kwargs={'id':ps.id}), + 'name': 'Comments' + } + ] + + active = 'nav-plan' + + return render(request, + 'plannedsession_comments.html', + {'plannedsession':ps, + 'rower':rower, + 'breadcrumbs':breadcrumbs, + 'active':active, + 'comments':comments, + 'form':form, + }) + # Cloning sessions @user_passes_test(hasplannedsessions,login_url="/rowers/paidplans/", message="This functionality requires a Coach or Self-Coach plan", @@ -1385,7 +1529,7 @@ def plannedsession_view(request,id=0,userid=0): raise Http404("Planned Session does not exist") if ps.sessiontype in ['race','indoorrace']: - url = reverse(virtualevent_view, + url = reverse('virtualevent_view', kwargs={'id':ps.id} ) return HttpResponseRedirect(url) @@ -1522,6 +1666,8 @@ def plannedsession_view(request,id=0,userid=0): } ] + comments = PlannedSessionComment.objects.filter(plannedsession=ps).order_by("created") + return render(request,'plannedsessionview.html', { 'psdict': psdict, @@ -1544,7 +1690,8 @@ def plannedsession_view(request,id=0,userid=0): 'timeperiod':timeperiod, 'ranking':ranking, 'coursescript': coursescript, - 'coursediv': coursediv + 'coursediv': coursediv, + 'comments': comments, } ) diff --git a/rowers/views/racesviews.py b/rowers/views/racesviews.py index 139d82c3..e7addf43 100644 --- a/rowers/views/racesviews.py +++ b/rowers/views/racesviews.py @@ -882,6 +882,9 @@ def virtualevent_view(request,id=0): else: racelogo = None + comments = PlannedSessionComment.objects.filter(plannedsession=race).order_by("created") + + return render(request,'virtualevent.html', { 'coursescript':script, @@ -896,6 +899,7 @@ def virtualevent_view(request,id=0): 'racelogo':racelogo, 'form':form, 'active':'nav-racing', + 'comments':comments }) def virtualevent_ranking_view(request,id=0): diff --git a/rowers/views/statements.py b/rowers/views/statements.py index 2af32b38..ee597981 100644 --- a/rowers/views/statements.py +++ b/rowers/views/statements.py @@ -87,6 +87,7 @@ from rowers.models import ( microcyclecheckdates,mesocyclecheckdates,macrocyclecheckdates, TrainingMesoCycleForm, TrainingMicroCycleForm, RaceLogo,RowerBillingAddressForm,PaidPlan, + PlannedSessionComment, ) from rowers.models import ( RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm, @@ -97,7 +98,7 @@ from rowers.models import ( PlannedSessionFormSmall,GeoCourseEditForm,VirtualRace, VirtualRaceForm,VirtualRaceResultForm,RowerImportExportForm, IndoorVirtualRaceResultForm,IndoorVirtualRaceResult, - IndoorVirtualRaceForm, + IndoorVirtualRaceForm,PlannedSessionCommentForm, ) from rowers.models import ( FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement,BasePlannedSessionFormSet,