Private
Public Access
1
0

Team create, delete, leave, create invite

This commit is contained in:
Sander Roosendaal
2017-02-09 17:50:32 +01:00
parent f459cea2bd
commit 40e18fee21
10 changed files with 473 additions and 60 deletions

View File

@@ -51,7 +51,10 @@ database_url = 'mysql://{user}:{password}@{host}:{port}/{database_name}'.format(
if settings.DEBUG or user=='':
database_url = 'sqlite:///db.sqlite3'
class UserFullnameChoiceField(forms.ModelChoiceField):
def label_from_instance(self,obj):
return obj.get_full_name()
# model for Power Zone names
class PowerZonesField(models.TextField):
# __metaclass__ = models.SubfieldBase
@@ -101,20 +104,35 @@ class Team(models.Model):
('private','private'),
('open','open'),
)
name = models.CharField(max_length=150,unique=True)
notes = models.CharField(blank=True,max_length=200)
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')
private = models.CharField(max_length=30,choices=choices,default='open',
verbose_name='Team Type')
def __unicode__(self):
return self.name
class TeamForm(ModelForm):
class Meta:
model = Team
fields = ['name','notes','private']
widgets = {
'notes': forms.Textarea,
}
class TeamInvite(models.Model):
team = models.ForeignKey(Team)
user = models.ForeignKey(User,null=True)
issuedate = models.DateField(default=timezone.now)
code = models.CharField(max_length=150,unique=True)
email = models.CharField(max_length=150,null=True,blank=True)
class TeamInviteForm(ModelForm):
user = UserFullnameChoiceField(queryset=User.objects.all())
class Meta:
model = TeamInvite
fields = ['user','email']
# Extension of User with rowing specific data
class Rower(models.Model):

View File

@@ -22,6 +22,7 @@ from utils import serialize_list,deserialize_list
from rowers.dataprepnodjango import update_strokedata
from django.core.mail import send_mail, BadHeaderError,EmailMessage
# testing task
@@ -54,39 +55,6 @@ def handle_sendemail_unrecognized(unrecognizedfile,useremail):
os.remove(unrecognizedfile)
return 1
# Send email with team invitation
@app.task
def handle_sendemail_invite(email,name,code,teamname,manager):
fullemail = name+' <'+email+'>'
subject = 'Invitation to join team '+teamname
message = 'Dear '+name+',\n\n'
message += manager+' is inviting you to join his team '+teamname
message += ' on rowsandall.com\n\n'
message += 'If you already have an account on rowsandall.com, you can login to the site and you will find the invitation here on the Teams page:\n'
message += ' https://rowsandall.com/rowers/me/teams \n\n'
message += 'You can also click the direct link: \n'
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
message += 'If you are not yet registered to rowsandall.com, '
message += 'you can register for free at https://rowsandall.com/rowers/register\n'
message += 'After you set up your account, you can use the direct link: '
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
message += 'You can also manually accept your team membership with the code.\n'
message += 'You will need to do this if you registered under a differnt email address than this one.\n'
message += 'Code: '+code+'\n'
message += 'Link to manually accept your team membership: '
message += 'https://rowsandall.com/rowers/me/invitation\n\n'
message += "Best Regards, the Rowsandall Team"
email = EmailMessage(subject, message,
'Rowsandall <info@rowsandall.com>',
[fullemail])
res = email.send()
return 1
# Send email with TCX attachment
@app.task
@@ -267,6 +235,53 @@ def handle_makeplot(f1,f2,t,hrdata,plotnr,imagename):
gc.collect()
return imagename
# Team related remote tasks
@app.task
def handle_sendemail_invite(email,name,code,teamname,manager):
fullemail = name+' <'+email+'>'
subject = 'Invitation to join team '+teamname
message = 'Dear '+name+',\n\n'
message += manager+' is inviting you to join his team '+teamname
message += ' on rowsandall.com\n\n'
message += 'If you already have an account on rowsandall.com, you can login to the site and you will find the invitation here on the Teams page:\n'
message += ' https://rowsandall.com/rowers/me/teams \n\n'
message += 'You can also click the direct link: \n'
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
message += 'If you are not yet registered to rowsandall.com, '
message += 'you can register for free at https://rowsandall.com/rowers/register\n'
message += 'After you set up your account, you can use the direct link: '
message += 'https://rowsandall.com/rowers/me/invitation/'+code+' \n\n'
message += 'You can also manually accept your team membership with the code.\n'
message += 'You will need to do this if you registered under a differnt email address than this one.\n'
message += 'Code: '+code+'\n'
message += 'Link to manually accept your team membership: '
message += 'https://rowsandall.com/rowers/me/invitation\n\n'
message += "Best Regards, the Rowsandall Team"
email = EmailMessage(subject, message,
'Rowsandall <info@rowsandall.com>',
[fullemail])
res = email.send()
return 1
@app.task
def handle_remove_workouts_team(ws,t):
for w in ws:
w.team.remove(t)
return 1
@app.task
def handle_add_workouts_team(ws,t):
for w in ws:
w.team.add(t)
return 1
# Another simple task for debugging purposes
def add2(x,y):

View File

@@ -7,21 +7,26 @@ from datetime import timedelta
import time
from django.db import IntegrityError
import uuid
from rowers.tasks import handle_sendemail_invite
from django.conf import settings
from rowers.models import (
Rower, Workout, Team, TeamInvite,User
)
from rowers.tasks import (
handle_remove_workouts_team,handle_sendemail_invite,
handle_add_workouts_team
)
# Low level functions - to be called by higher level methods
inviteduration = 14 # days
def create_team(name,manager,notes=''):
def create_team(name,manager,private='open',notes=''):
# needs some error testing
try:
t = Team(name=name,manager=manager,notes=notes)
t = Team(name=name,manager=manager,notes=notes,
private=private)
t.save()
r = Rower.objects.get(user=manager)
r.team.add(t)
@@ -52,8 +57,12 @@ def add_member(id,rower):
rower.team.add(t)
# code to add all workouts
ws = Workout.objects.filter(user=rower)
for w in ws:
w.team.add(t)
if settings.DEBUG:
res = handle_add_workouts_team(ws,t)
else:
res = queuehigh.enqueue(handle_add_workouts_team,ws,t)
set_teamplanexpires(rower)
@@ -63,10 +72,13 @@ def remove_member(id,rower):
t = Team.objects.get(id=id)
rower.team.remove(t)
# remove the team from rower's workouts:
ws = Workout.objects.filter(user=rower)
for w in ws:
w.team.remove(t)
ws = Workout.objects.filter(user=rower,team=t)
if settings.DEBUG:
res = handle_remove_workouts_team(ws,t)
else:
res = queuehigh.enqueue(handle_remove_workouts_team,ws,t)
set_teamplanexpires(rower)
return (1,'Member removed')
@@ -110,7 +122,7 @@ def get_team_workouts(id):
# Medium level functionality
def create_invite(team,manager,user=None):
def create_invite(team,manager,user=None,email=''):
r = Rower.objects.get(user=manager)
if team.manager != manager:
return (0,'Not the team manager')
@@ -121,6 +133,9 @@ def create_invite(team,manager,user=None):
return (0,'Rower does not exist')
if r2 in Rower.objects.filter(team=team):
return (0,'Already member of that team')
elif email==None or email=='':
return (0,'Invalid request - missing email or user')
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()
@@ -128,7 +143,7 @@ def create_invite(team,manager,user=None):
while code in codes:
code = uuid.uuid4().hex[:10].upper()
invite = TeamInvite(team=team,code=code,user=user)
invite = TeamInvite(team=team,code=code,user=user,email=email)
invite.save()
return (invite.id,'Invitation created')
@@ -144,6 +159,7 @@ def revoke_invite(id):
return (1,'Invitation revoked')
def send_invite_email(id):
invitation = TeamInvite.objects.get(id=id)
if invitation.user:
@@ -157,15 +173,14 @@ def send_invite_email(id):
manager = invitation.team.manager.first_name+' '+invitation.team.manager.last_name
if settings.DEBUG:
res = handle_sendemail_invite(email,name,code,teamname,manager)
res = handle_sendemail_invite.delay(email,name,code,teamname,manager)
else:
res = queue.enqueue(handle_sendemail_invite(email,name,code,
teamname,
manager))
queue.enqueue(handle_sendemail_invite,email,name,code,teamname,manager)
return (1,'Invitation email sent')
def process_invite_code(user,code):
code = code.upper()
try:
invitation = TeamInvite.objects.get(code=code)
except TeamInvite.DoesNotExist:

View File

@@ -0,0 +1,61 @@
{% extends "base.html" %}
{% block title %}Team {% endblock %}
{% block content %}
<div class="grid_12 alpha">
<h1>{{ team.name }}</h1>
<p>{{ team.notes }}</p>
<div class="grid_2 suffix_10 alpha">
<a class="button red small" href="/rowers/team/{{ team.id }}/leaveconfirm">Leave this team</a>
</div>
<div class="grid_6 alpha">
<p>
<h2>Members</h2>
<table width="100%" class="listtable">
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td> {{ member.user.first_name }} {{ member.user.last_name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</p>
</div>
<div class="grid_6 omega">
{% if team.manager == user %}
<p>Use the form to add a new user. You can either select a user from the slist, or you can type his email address, which also works for users who have not registered to the site yet.</p>
{% if inviteform.errors %}
<p style="color: red;">
Please correct the error{{ inviteform.errors|pluralize }} below.
</p>
{% endif %}
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
<table>
{{ inviteform.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Submit">
</div>
</form>
{% else %}
<p>
&nbsp;
</p>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends "base.html" %}
{% load staticfiles %}
{% block title %}New Team{% endblock %}
{% block content %}
<form enctype="multipart/form-data" action="{{ formloc }}" method="post">
<div id="left" class="grid_6 alpha">
<h1>Create a new Team</h1>
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<table>
{{ form.as_table }}
</table>
{% csrf_token %}
<div id="formbutton" class="grid_1 prefix_4 suffix_1">
<input class="button green" type="submit" value="Submit">
</div>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Leave Team {% endblock %}
{% block content %}
<div id="workouts" class="grid_6 alpha">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<h1>Confirm Deleting the team {{ team.name }}</h1>
<p>This will remove the team. Your team members and their workouts
will not be deleted.
</p>
<div class="grid_2 alpha">
<p>
<a class="button green small" href="/rowers/team/{{ team.id }}">Cancel</a>
</div>
<div class="grid_2">
<p>
<a class="button red small" href="/rowers/team/{{ team.id }}/delete">Delete</a>
</p>
</div>
</div>
<div id="images" class="grid_6 omega">
<p>
&nbsp;
</p>
</div>
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends "base.html" %}
{% load staticfiles %}
{% load rowerfilters %}
{% block title %}Leave Team {% endblock %}
{% block content %}
<div id="workouts" class="grid_6 alpha">
{% if form.errors %}
<p style="color: red;">
Please correct the error{{ form.errors|pluralize }} below.
</p>
{% endif %}
<h1>Confirm Leaving the team {{ team.name }}</h1>
<p>This will remove you and all your workouts from the team. If this
is a closed team, you can only return when the team manager
reinvites you.
If this is an open team, you can return by applying for team membership.
</p>
<div class="grid_2 alpha">
<p>
<a class="button green small" href="/rowers/team/{{ team.id }}">Cancel</a>
</div>
<div class="grid_2">
<p>
<a class="button red small" href="/rowers/team/{{ team.id }}/leave">Leave</a>
</p>
</div>
</div>
<div id="images" class="grid_6 omega">
<p>
&nbsp;
</p>
</div>
{% endblock %}

View File

@@ -6,10 +6,33 @@
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<p>
<h2>Teams</h2>
Future teams page
<h2>My Teams</h2>
{% if teams %}
<table width="70%" class="listtable">
<thead>
<tr>
<th>Name</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for team in teams %}
<tr>
<td>
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
</td>
<td>
<div class="grid_1">
<a class="button small red" href="/rowers/team/{{ team.id }}/leaveconfirm">Leave</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>You are not a member of any team.</p>
{% endif %}
</p>
@@ -21,7 +44,40 @@
</div>
</div>
{% if user.rower.rowerplan == 'coach' %}
<div class="grid_12 alpha">
<div class="grid_6 alpha">
<h2>Teams I manage</h2>
{% if myteams %}
<table width="70%" class="listtable">
<thead>
<tr>
<th>Name</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for team in myteams %}
<tr>
<td>
<a href="/rowers/team/{{ team.id }}/">{{ team.name }}</a>
</td>
<td>
<div class="grid_1">
<a class="button small red" href="/rowers/team/{{ team.id }}/deleteconfirm">Delete</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
<div class="grid_2 suffix_2 prefix_2 alpha">
<a class="button green" href="/rowers/team/create">New Team</a>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -200,8 +200,14 @@ urlpatterns = [
url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view),
url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view),
url(r'^me/teams/$',views.rower_teams_view),
url(r'^team/(\d+)/$',views.team_view),
url(r'^team/(\d+)/leaveconfirm/$',views.team_leaveconfirm_view),
url(r'^team/(\d+)/leave/$',views.team_leave_view),
url(r'^team/(\d+)/deleteconfirm/$',views.team_deleteconfirm_view),
url(r'^team/(\d+)/delete/$',views.team_delete_view),
url(r'^team/create/$',views.team_create_view),
url(r'^me/invitation/$',views.rower_invitations_view),
url(r'^me/invitation/(\d+)/$',views.rower_invitations_view),
url(r'^me/invitation/(\w+.*)/$',views.rower_invitations_view),
url(r'^me/edit/c/(?P<message>\w+.*)$',views.rower_edit_view),
url(r'^me/edit/s/(?P<successmessage>\w+.*)$',views.rower_edit_view),
url(r'^me/edit/$',views.rower_edit_view),

View File

@@ -31,6 +31,7 @@ from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart
from rowers.models import (
RowerPowerForm,RowerForm,GraphImage,AdvancedWorkoutForm,
RowerPowerZonesForm,AccountRowerForm,UserForm,StrokeData,
Team,TeamForm,TeamInviteForm
)
from rowers.models import FavoriteForm,BaseFavoriteFormSet,SiteAnnouncement
from django.forms.formsets import formset_factory
@@ -57,6 +58,7 @@ from rest_framework.parsers import JSONParser
from rowers.rows import handle_uploaded_file
from rowers.tasks import handle_makeplot,handle_otwsetpower,handle_sendemailtcx,handle_sendemailcsv
from rowers.tasks import handle_sendemail_unrecognized
from scipy.signal import savgol_filter
from django.shortcuts import render_to_response
from Cookie import SimpleCookie
@@ -198,6 +200,12 @@ def splitstdata(lijst):
from utils import geo_distance,serialize_list,deserialize_list
# Check if a user is a Coach member
def iscoachmember(user):
r = Rower.objects.get(user=user)
result = user.is_authenticated() and (r.rowerplan=='coach')
return result
# Check if a user is a Pro member
def ispromember(user):
r = Rower.objects.get(user=user)
@@ -5278,13 +5286,76 @@ def strokedatajson(request,id):
return HttpResponseNotAllowed("Method not supported")
# Teams related views
import teams
@login_required()
def team_view(request,id=0):
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("Team doesn't exist")
if request.method == 'POST' and request.user == t.manager:
inviteform = TeamInviteForm(request.POST)
if inviteform.is_valid():
cd = inviteform.cleaned_data
newmember = cd['user']
email = cd['email']
inviteid,message = teams.create_invite(t,t.manager,
user=newmember,
email=email)
if inviteid:
teams.send_invite_email(inviteid)
elif request.user == t.manager:
inviteform = TeamInviteForm()
inviteform.fields['user'].queryset = User.objects.filter(rower__isnull=False)
else:
inviteform = ''
members = Rower.objects.filter(team=t).order_by('user__last_name','user__first_name')
return render(request, 'team.html',
{
'team':t,
'members':members,
'inviteform':inviteform,
})
@login_required()
def team_leaveconfirm_view(request,id=0):
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("Team doesn't exist")
return render(request,'teamleaveconfirm.html',
{
'team':t
})
@login_required()
def team_leave_view(request,id=0):
r = Rower.objects.get(user=request.user)
teams.remove_member(id,r)
url = reverse(rower_teams_view)
response = HttpResponseRedirect(url)
return response
@login_required()
def rower_teams_view(request):
def rower_teams_view(request):
r = Rower.objects.get(user=request.user)
teams = Team.objects.filter(rower=r)
myteams = Team.objects.filter(manager=request.user)
return render(request, 'teams.html',
{
{
'teams':teams,
'myteams':myteams,
})
@@ -5292,3 +5363,58 @@ def rower_invitations_view(request,code=None):
def rower_invitations_view(request,code=None):
return render(request, 'invitations.html',
{
})
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_create_view(request):
if request.method == 'POST':
teamcreateform = TeamForm(request.POST)
if teamcreateform.is_valid():
cd = teamcreateform.cleaned_data
name = cd['name']
notes = cd['notes']
manager = request.user
private = cd['private']
res,message=teams.create_team(name,manager,private,notes)
url = reverse(rower_teams_view)
response = HttpResponseRedirect(url)
return response
else:
teamcreateform = TeamForm()
return render(request,'teamcreate.html',
{
'form':teamcreateform,
})
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_deleteconfirm_view(request,id):
r = Rower.objects.get(user=request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("This team doesn't exist")
if t.manager != request.user:
return HttpResponse("You are not allowed to delete this team")
return render(request,'teamdeleteconfirm.html',
{
'team':t
})
@user_passes_test(iscoachmember,login_url="/",redirect_field_name=None)
def team_delete_view(request,id):
r = Rower.objects.get(user=request.user)
try:
t = Team.objects.get(id=id)
except Team.DoesNotExist:
return HttpResponse("This team doesn't exist")
if t.manager != request.user:
return HttpResponse("You are not allowed to delete this team")
teams.remove_team(t.id)
url = reverse(rower_teams_view)
response = HttpResponseRedirect(url)