diff --git a/rowers/models.py b/rowers/models.py index ca7f3869..b4426431 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -386,7 +386,6 @@ class TeamInviteForm(ModelForm): - class TeamRequest(models.Model): team = models.ForeignKey(Team) @@ -633,6 +632,8 @@ class PaidPlan(models.Model): paymentprocessor = self.paymentprocessor, ) +class CoachingGroup(models.Model): + pass # Extension of User with rowing specific data class Rower(models.Model): @@ -832,7 +833,8 @@ class Rower(models.Model): # Friends/Team friends = models.ManyToManyField("self",blank=True) - coaches = models.ManyToManyField("self",blank=True) + mycoachgroup = models.ForeignKey(CoachingGroup,related_name='coachingrole',null=True) + coachinggroups = models.ManyToManyField(CoachingGroup,related_name='coaches') privacy = models.CharField(default='visible',max_length=30, choices=privacychoices) @@ -870,6 +872,19 @@ class DeleteUserForm(forms.ModelForm): model = User fields = [] +# requestor is user +class CoachRequest(models.Model): + coach = models.ForeignKey(Rower) + user = models.ForeignKey(User,null=True) + issuedate = models.DateField(default=current_day) + code = models.CharField(max_length=150,unique=True) + +# requestor is coach +class CoachOffer(models.Model): + coach = models.ForeignKey(Rower) + user = models.ForeignKey(User,null=True) + issuedate = models.DateField(default=current_day) + code = models.CharField(max_length=150,unique=True) from django.db.models.signals import m2m_changed @@ -988,7 +1003,7 @@ def checkworkoutuser(user,workout): return False try: r = Rower.objects.get(user=user) - coaches = user.rower.coaches.filter(rowerplan='coach') + coaches = rower_get_coaches(user.rower) if workout.user == r: return True elif coaches: @@ -1007,7 +1022,7 @@ def checkaccessuser(user,rower): r = Rower.objects.get(user=user) if rower == r: return True - coaches = rower.coaches.filter(rowerplan='coach') + coaches = rower_get_coaches(rower) if coaches: for coach in coaches: if user.rower == coach: @@ -3434,3 +3449,4 @@ class PlannedSessionCommentForm(ModelForm): 'comment': forms.Textarea, } + diff --git a/rowers/tasks.py b/rowers/tasks.py index 7f4b756c..21531d98 100644 --- a/rowers/tasks.py +++ b/rowers/tasks.py @@ -1699,6 +1699,57 @@ def handle_makeplot(f1, f2, t, hrdata, plotnr, imagename, # Team related remote tasks +@app.task +def handle_sendemail_coachrequest(email,name,code,coachname, + debug=False,**kwargs): + + fullemail = email + subject = 'Invitation to add {n} to your athletes'.format(n=name) + + siteurl = SITE_URL + if debug: + siteurl = SITE_URL_DEV + + d = { + 'name':name, + 'coach':coachname, + 'code':code, + 'siteurl':siteurl + } + + form_email = 'Rowsandall ' + + res = send_template_email(from_email,[fullemail], + subject,'coachrequestemail.html',d, + **kwargs) + + return 1 + +@app.task +def handle_sendemail_coacheerequest(email,name,code,coachname, + debug=False,**kwargs): + + fullemail = email + subject = '{n} asks coach access to your data on rowsandall.com'.format(n=coachname) + + siteurl = SITE_URL + if debug: + siteurl = SITE_URL_DEV + + d = { + 'name':name, + 'coach':coachname, + 'code':code, + 'siteurl':siteurl + } + + form_email = 'Rowsandall ' + + res = send_template_email(from_email,[fullemail], + subject,'coacheerequestemail.html',d, + **kwargs) + + return 1 @app.task def handle_sendemail_invite(email, name, code, teamname, manager, diff --git a/rowers/teams.py b/rowers/teams.py index e6604f98..91b9e75d 100644 --- a/rowers/teams.py +++ b/rowers/teams.py @@ -17,7 +17,7 @@ queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low') from rowers.models import ( - Rower, Workout, Team, TeamInvite,User,TeamRequest + Rower, Workout, Team, TeamInvite,User,TeamRequest, CoachRequest, CoachOffer ) from rowers.tasks import ( @@ -26,6 +26,7 @@ from rowers.tasks import ( handle_sendemail_member_dropped,handle_sendemail_request_accept, handle_sendemail_request_reject,handle_sendemail_invite_reject, handle_sendemail_invite_accept,handle_sendemail_team_removed, + handle_sendemail_coachrequest,handle_sendemail_coacheerequest, ) from rowers.models import ValidationError @@ -102,8 +103,17 @@ def remove_team(id): return (1,'Updated rower team expiry') -def add_coach(manager,rower): - rower.coaches.add(m) +def add_coach(coach,rower): + # get coaching group + try: + coachgroup = coach.mycoachgroup + except CoachingGroup.DoesNotExist: + coachgroup = CoachingGroup() + coachgroup.save() + coach.mycoachgroup = coachgroup + coach.save() + + rower.coachinggroups.add(coach) return (1,"Added Coach") @@ -134,6 +144,44 @@ def remove_member(id,rower): # set_teamplanexpires(rower) return (id,'Member removed') +def remove_coach(coach,rower): + try: + coachgroup = coach.mycoachgroup + except CoachingGroup.DoesNotExist: + coachgroup = CoachingGroup() + coachgroup.save() + coach.mycoachgroup = coachgroup + coach.save() + + rower.coachinggroups.remove(coachgroup) + + return (1,'Coach removed') + +def rower_get_coaches(rower): + coaches = [] + for group in rower.coachinggroups: + coach = Rower.objects.get(mycoachgroup=group) + coaches.append(coach) + + return coaches + + +def coach_getcoachees(coach): + return Rower.objects.filter(coachinggroups__in=[coach.mycoachgroup]) + +def coach_remove_athlete(coach,rower): + try: + coachgroup = coach.mycoachgroup + except CoachingGroup.DoesNotExist: + coachgroup = CoachingGroup() + coachgroup.save() + coach.mycoachgroup = coachgroup + coach.save() + + rower.coachingrgroups.remove(coachgroup) + + return (1,'Coach removed') + def mgr_remove_member(id,manager,rower): t = Team.objects.get(id=id) if t.manager == manager: @@ -161,6 +209,51 @@ def count_club_members(manager): # Medium level functionality +# request by user to be coached by coach +def create_coaching_request(coach,user): + if coach in rower_get_coaches(user.rower): + return (0,'Already coached by that coach') + + codes = [i.code for i in CoachRequest.objects.all()] + code = uuid.uuid4().hex[:10].upper() + while code in codes: + code = uuid.uuid4().hex[:10].upper() + + if coach.rowerplan == 'coach': + rekwest = CoachRequest(coach=coach,user=user,code=code) + rekwest.save() + + send_coachrequest_email(rekwest) + + return (rekwest.id,'The request was created') + + else: + return (0,'That person is not a coach') + +def send_coachrequest_email(rekwest): + name = rekwest.user.first_name + " " + rekwest.user.last_name + email = rekwest.user.email + + code = rekwest.code + + coachname = rekwest.coach.user.first_name + " " + rekwest.coach.user.last_name + + res = myqueue(queuehigh, + handle_sendemail_coachrequest, + email,name,code,coachname) + +def send_coacheerequest_email(rekwest): + name = rekwest.user.first_name + " " + rekwest.user.last_name + email = rekwest.user.email + + code = rekwest.code + + coachname = rekwest.coach.user.first_name + " " + rekwest.coach.user.last_name + + res = myqueue(queuehigh, + handle_sendemail_coacheerequest, + email,name,code,coachname) + def create_request(team,user): r2 = Rower.objects.get(user=user) r = Rower.objects.get(user=team.manager) @@ -183,7 +276,33 @@ def create_request(team,user): return (rekwest.id,'The request was created') return (0,'Something went wrong in create_request') + +# request by coach to coach user +def create_coaching_offer(coach,user): + r = user.rower + + if coach in rower_get_coaches(user.rower): + return (0,'You are already coaching this person.') + + codes = [i.code for i in CoachOffer.objects.all()] + code = uuid.uuid4().hex[:10].upper() + while code in codes: + code = uuid.uuid4().hex[:10].upper() + + if coach.rowerplan == 'coach': + rekwest = CoachOffer(coach=coach,user=user,code=code) + rekwest.save() + + send_coacheerequest_email(rekwest) + + return (rekwest.id,'The request was created') + + else: + return (0,'You are not a coach') + + + def create_invite(team,manager,user=None,email=''): r = Rower.objects.get(user=manager) if team.manager != manager: @@ -235,6 +354,36 @@ def revoke_request(user,id): else: return (0,'You are not the requestor') +def reject_revoke_coach_offer(user,id): + try: + rekwest = CoachOffer.objects.get(id=id) + except CoachOffer.DoesNotExist: + return (0,'The request is invalid') + + if rekwest.coach.user == user: + rekwest.delete() + return (1,'Request removed') + elif rekwest.user == user: + rekwest.delete() + return (1,'Request removed') + else: + return (0,'Not permitted') + +def reject_revoke_coach_request(user,id): + try: + rekwest = CoachRequest.objects.get(id=id) + except CoachRequest.DoesNotExist: + return (0,'The request is invalid') + + if rekwest.coach.user == user: + rekwest.delete() + return (1,'Request rejected') + elif rekwest.user == user: + rekwest.delete() + return (1,'Request rejected') + else: + return (0,'Not permitted') + def revoke_invite(manager,id): try: invite = TeamInvite.objects.get(id=id) @@ -458,3 +607,43 @@ def remove_expired_invites(): revoke_invite(i.team.manager,i.id) return (1,'Expired invitations deleted') + +def process_coachrequest_code(coach,code): + code = code.upper() + + try: + rekwest = CoachRequest.objects.get(code=code) + except CoachRequest.DoesNotExist: + return (0,'The request has been revoked or is invalid') + + if rekwest.coach != coach: + return (0,'The request is invalid') + + result = add_coach(coach,rekwest.user.rower) + if not result: + return (result,"Something went wrong") + + rekwest.delete() + + return result + +def process_coachoffer_code(user,code): + code = code.upper() + + try: + rekwest = CoachOffer.objects.get(code=code) + except CoachOffer.DoesNotExist: + return (0,'The request has been revoked or is invalid') + + if rekwest.user != user: + return (0,'The request is invalid') + + result = add_coach(rekwest.coach,rekwest.user.rower) + if not result: + return (result,"Something went wrong") + + rekwest.delete() + + return result + + diff --git a/rowers/templates/coacheerequestemail.html b/rowers/templates/coacheerequestemail.html new file mode 100644 index 00000000..fd0fbaa5 --- /dev/null +++ b/rowers/templates/coacheerequestemail.html @@ -0,0 +1,32 @@ +{% extends "emailbase.html" %} + +{% block body %} +

Dear {{ name }},

+ +

+ {{ coachname }} is inviting you to become your coach on rowsandall.com. +

+

+ By accepting the invite, {{ coachname }} will have access to your + data and will be able to upload workouts and run analysis on rowsandall.com + on behalf of you. +

+

+ By accepting the invite, you are agreeing with the sharing + of personal data according to our privacy policy. +

+

+ To accept, login to the + site and you will find the invitation here on the Teams page: + {{ siteurl }}/rowers/me/teams +

+

+ You can also click the direct link: + + {{ siteurl }}/rowers/me/coacheeinvitation/{{ code }} +

+ +

+ Best Regards, the Rowsandall Team +

+{% endblock %} diff --git a/rowers/templates/coachrequestemail.html b/rowers/templates/coachrequestemail.html new file mode 100644 index 00000000..4523aa66 --- /dev/null +++ b/rowers/templates/coachrequestemail.html @@ -0,0 +1,33 @@ +{% extends "emailbase.html" %} + +{% block body %} +

Dear {{ coachname }},

+ +

+ {{ name }} is inviting you to become his/her coach + on rowsandall.com +

+

+ By accepting the invite, you will have access to {{ name }}'s + data and will be able to upload workouts and run analysis on rowsandall.com + on behalf of {{ name }}. +

+

+ By accepting the invite, you are agreeing with the sharing + of personal data according to our privacy policy. +

+

+ To accept the login to the + site and you will find the invitation here on the Teams page: + {{ siteurl }}/rowers/me/teams +

+

+ You can also click the direct link: + + {{ siteurl }}/rowers/me/coachinvitation/{{ code }} +

+ +

+ Best Regards, the Rowsandall Team +

+{% endblock %} diff --git a/rowers/templates/menu_analytics.html b/rowers/templates/menu_analytics.html index ac847155..07c6d218 100644 --- a/rowers/templates/menu_analytics.html +++ b/rowers/templates/menu_analytics.html @@ -78,7 +78,7 @@