team managment
This commit is contained in:
parent
c54dda8dc5
commit
b2b9144cbd
9 changed files with 291 additions and 9 deletions
|
@ -27,26 +27,27 @@ def project_perm_required(perm):
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def confirmation_required(message, previous=None):
|
def confirmation_required(message, prev=None):
|
||||||
|
|
||||||
def decorator(view):
|
def decorator(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def wrapper(request, *args, **kwargs):
|
def wrapper(request, *args, **kwargs):
|
||||||
if request.GET.get('force'):
|
if request.GET.get('force'):
|
||||||
return view(request, *args, **kwargs)
|
return view(request, *args, **kwargs)
|
||||||
prev = previous
|
previous = request.GET.get('prev')
|
||||||
if not prev:
|
if not previous:
|
||||||
prev = request.GET.get('prev')
|
if prev:
|
||||||
if not prev:
|
previous = reverse(prev)
|
||||||
|
else:
|
||||||
# improvising
|
# improvising
|
||||||
if hasattr(request, 'project'):
|
if hasattr(request, 'project'):
|
||||||
prev = reverse('list-issue',
|
previous = reverse('list-issue',
|
||||||
args=[request.project.name])
|
args=[request.project.name])
|
||||||
else:
|
else:
|
||||||
prev = reverse('list-project')
|
previous = reverse('list-project')
|
||||||
c = {
|
c = {
|
||||||
'message': message,
|
'message': message,
|
||||||
'prev': prev,
|
'prev': previous,
|
||||||
'next': request.path + '?force=1',
|
'next': request.path + '?force=1',
|
||||||
}
|
}
|
||||||
return render(request, 'confirm.html', c)
|
return render(request, 'confirm.html', c)
|
||||||
|
|
|
@ -13,6 +13,8 @@ EditProjectForm = modelform_factory(Project,
|
||||||
fields=['display_name', 'description', 'public'])
|
fields=['display_name', 'description', 'public'])
|
||||||
LabelForm = modelform_factory(Label,
|
LabelForm = modelform_factory(Label,
|
||||||
fields=['name', 'color', 'inverted'])
|
fields=['name', 'color', 'inverted'])
|
||||||
|
TeamForm = modelform_factory(Team,
|
||||||
|
fields=['name', 'users', 'groups'])
|
||||||
|
|
||||||
|
|
||||||
class MilestoneForm(forms.ModelForm):
|
class MilestoneForm(forms.ModelForm):
|
||||||
|
|
|
@ -70,7 +70,8 @@
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ request.user.username }} <span class="caret"></span></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ request.user.username }} <span class="caret"></span></a>
|
||||||
<ul class="dropdown-menu" role="menu">
|
<ul class="dropdown-menu" role="menu">
|
||||||
<li role="presentation"><a role="menuitem" href="{% url 'profile' %}"><span class="glyphicon glyphicon-dashboard"></span> Profile</a></li>
|
<li role="presentation"><a role="menuitem" href="{% url 'profile' %}"><span class="glyphicon glyphicon-user"></span> My profile</a></li>
|
||||||
|
<li role="presentation"><a role="menuitem" href="{% url 'list-team' %}"><span class="glyphicon glyphicon-dashboard"></span> Teams</a></li>
|
||||||
{% if perm.manage_global_permission %}
|
{% if perm.manage_global_permission %}
|
||||||
<li role="presentation"><a role="menuitem" href="{% url 'list-global-permission' %}"><span class="glyphicon glyphicon-cog"></span> Manage permissions</a></li>
|
<li role="presentation"><a role="menuitem" href="{% url 'list-global-permission' %}"><span class="glyphicon glyphicon-cog"></span> Manage permissions</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
47
issue/templates/issue/team.html
Normal file
47
issue/templates/issue/team.html
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h1>
|
||||||
|
Teams
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'list-team' %}" class="btn btn-success">Go back to teams</a>
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<h3>Users</h3>
|
||||||
|
{% if team.users.exists %}
|
||||||
|
<ul>
|
||||||
|
{% for user in team.users.all %}
|
||||||
|
<li>{{ user }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<em>There aren't any users in this team.</em>
|
||||||
|
{% endif %}
|
||||||
|
<hr />
|
||||||
|
<h3>Groups</h3>
|
||||||
|
{% if team.groups.exists %}
|
||||||
|
<ul>
|
||||||
|
{% for group in team.groups.all %}
|
||||||
|
<li>{{ group }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<em>There aren't any groups in this team.</em>
|
||||||
|
{% endif %}
|
||||||
|
<hr />
|
||||||
|
<div class="row text-center">
|
||||||
|
<a href="{% url 'list-team' %}" class="btn btn-default"><span class="glyphicon glyphicon-chevron-left"></span> Go back to list</a>
|
||||||
|
<a href="{% url 'edit-team' team.pk %}" class="btn btn-primary"><span class="glyphicon glyphicon-edit"></span> Modify team</a>
|
||||||
|
<a href="{% url 'delete-team' team.pk %}?prev={{ request.path }}" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> Delete team</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
38
issue/templates/issue/team_edit.html
Normal file
38
issue/templates/issue/team_edit.html
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% load bootstrap_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h1>
|
||||||
|
{% if team %}
|
||||||
|
Edit team
|
||||||
|
{% else %}
|
||||||
|
Add team
|
||||||
|
{% endif %}
|
||||||
|
<div class="pull-right">
|
||||||
|
{% if team %}
|
||||||
|
<a href="{% url 'show-team' team.pk %}" class="btn btn-warning">Go back to team</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'list-team' %}" class="btn btn-warning">Go back to teams</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="col-md-offset-3 col-md-6">
|
||||||
|
<form action="" method="post" role="form">
|
||||||
|
{{ form|as_bootstrap }}
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
47
issue/templates/issue/team_list.html
Normal file
47
issue/templates/issue/team_list.html
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% load humanize %}
|
||||||
|
{% load issue_filters %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
|
<div class="panel panel-default">
|
||||||
|
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h1>
|
||||||
|
Teams
|
||||||
|
{% if perm.add_team %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'add-team' %}" class="btn btn-success">Add team</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if teams.exists %}
|
||||||
|
<table class="table table-hover">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Users</th>
|
||||||
|
<th>Groups</th>
|
||||||
|
</tr>
|
||||||
|
{% for team in teams %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{% url 'show-team' team.pk %}"><b>{{ team }}</b></a></td>
|
||||||
|
<td>{{ team.users|first_few:'user' }}</td>
|
||||||
|
<td>{{ team.groups|first_few:'group' }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<a href="{% url 'edit-team' team.pk %}" class="btn btn-primary btn-xs">Edit</a>
|
||||||
|
<a href="{% url 'delete-team' team.pk %}" class="btn btn-danger btn-xs">Delete</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div class="panel-body">
|
||||||
|
<em>There aren't any teams quite yet.</em>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -13,3 +13,16 @@ def boolean(value):
|
||||||
glyph = 'remove'
|
glyph = 'remove'
|
||||||
return mark_safe('<span class="glyphicon glyphicon-'
|
return mark_safe('<span class="glyphicon glyphicon-'
|
||||||
+ glyph + '" style="vertical-align: middle;"></span>')
|
+ glyph + '" style="vertical-align: middle;"></span>')
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def first_few(items, arg='item'):
|
||||||
|
if items.exists():
|
||||||
|
if items.count() < 4:
|
||||||
|
return ', '.join(map(lambda x: x.__str__(), items.all()))
|
||||||
|
else:
|
||||||
|
r = ', '.join(map(lambda x: x.__str__(), items.all()[0:3]))
|
||||||
|
plural = 's' if items.count() > 4 else ''
|
||||||
|
r += ', … (%s other%s)' % (items.count() - 3, plural)
|
||||||
|
return r
|
||||||
|
else:
|
||||||
|
return 'no ' + arg + 's'
|
||||||
|
|
|
@ -38,6 +38,15 @@ urlpatterns = [
|
||||||
url(r'^permissions/(?P<id>[0-9]+)/edit$', 'issue.views.global_permission_edit', name='edit-global-permission'),
|
url(r'^permissions/(?P<id>[0-9]+)/edit$', 'issue.views.global_permission_edit', name='edit-global-permission'),
|
||||||
url(r'^permissions/(?P<id>[0-9]+)/toggle/(?P<perm>[a-z-]+)$', 'issue.views.global_permission_toggle', name='toggle-global-permission'),
|
url(r'^permissions/(?P<id>[0-9]+)/toggle/(?P<perm>[a-z-]+)$', 'issue.views.global_permission_toggle', name='toggle-global-permission'),
|
||||||
url(r'^permissions/(?P<id>[0-9]+)/delete$', 'issue.views.global_permission_delete', name='delete-global-permission'),
|
url(r'^permissions/(?P<id>[0-9]+)/delete$', 'issue.views.global_permission_delete', name='delete-global-permission'),
|
||||||
|
url(r'^teams$', 'issue.views.team_list', name='list-team'),
|
||||||
|
url(r'^teams/add$', 'issue.views.team_edit', name='add-team'),
|
||||||
|
url(r'^teams/(?P<team>[0-9]+)$', 'issue.views.team', name='show-team'),
|
||||||
|
url(r'^teams/(?P<team>[0-9]+)/edit$', 'issue.views.team_edit', name='edit-team'),
|
||||||
|
url(r'^teams/(?P<team>[0-9]+)/users/(?P<user>[0-9]+)/add$', 'issue.views.team_add_user', name='add-user-to-team'),
|
||||||
|
url(r'^teams/(?P<team>[0-9]+)/users/(?P<user>[0-9]+)/delete$', 'issue.views.team_remove_user', name='remove-user-from-team'),
|
||||||
|
url(r'^teams/(?P<team>[0-9]+)/groups/(?P<group>[0-9]+)/add$', 'issue.views.team_add_group', name='add-group-to-team'),
|
||||||
|
url(r'^teams/(?P<team>[0-9]+)/groups/(?P<group>[0-9]+)/delete$', 'issue.views.team_remove_group', name='remove-group-from-team'),
|
||||||
|
url(r'^teams/(?P<team>[0-9]+)/delete$', 'issue.views.team_delete', name='delete-team'),
|
||||||
url(r'^login$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name='login'),
|
url(r'^login$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}, name='login'),
|
||||||
url(r'^profile$', 'issue.views.profile', name='profile'),
|
url(r'^profile$', 'issue.views.profile', name='profile'),
|
||||||
url(r'^logout$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='logout'),
|
url(r'^logout$', 'django.contrib.auth.views.logout', {'next_page': '/'}, name='logout'),
|
||||||
|
|
124
issue/views.py
124
issue/views.py
|
@ -767,3 +767,127 @@ def milestone_delete(request, project, name):
|
||||||
messages.success(request, "Label deleted successfully.")
|
messages.success(request, "Label deleted successfully.")
|
||||||
|
|
||||||
return redirect('list-milestone', project.name)
|
return redirect('list-milestone', project.name)
|
||||||
|
|
||||||
|
|
||||||
|
def team_list(request):
|
||||||
|
|
||||||
|
teams = Team.objects.all()
|
||||||
|
|
||||||
|
c = {
|
||||||
|
'teams': teams,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'issue/team_list.html', c)
|
||||||
|
|
||||||
|
|
||||||
|
def team(request, team):
|
||||||
|
|
||||||
|
team = get_object_or_404(Team, pk=team)
|
||||||
|
|
||||||
|
c = {
|
||||||
|
'team': team,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'issue/team.html', c)
|
||||||
|
|
||||||
|
|
||||||
|
@project_perm_required('manage_team')
|
||||||
|
def team_edit(request, team=None):
|
||||||
|
|
||||||
|
if team:
|
||||||
|
team = get_object_or_404(Team, pk=team)
|
||||||
|
|
||||||
|
form = TeamForm(request.POST or None, instance=team)
|
||||||
|
|
||||||
|
if request.method == 'POST' and form.is_valid():
|
||||||
|
|
||||||
|
formteam = form.save()
|
||||||
|
if team:
|
||||||
|
messages.success(request, 'Team modified successfully.')
|
||||||
|
else:
|
||||||
|
messages.success(request, 'Team added successfully.')
|
||||||
|
|
||||||
|
return redirect('show-team', formteam.pk)
|
||||||
|
|
||||||
|
c = {
|
||||||
|
'team': team,
|
||||||
|
'form': form,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'issue/team_edit.html', c)
|
||||||
|
|
||||||
|
|
||||||
|
@project_perm_required('manage_team')
|
||||||
|
def team_add_user(request, team, user):
|
||||||
|
|
||||||
|
team = get_object_or_404(Team, pk=team)
|
||||||
|
user = get_object_or_404(User, pk=user)
|
||||||
|
|
||||||
|
if user in team.users.all():
|
||||||
|
messages.warning(request, 'This user already belong to this team.')
|
||||||
|
else:
|
||||||
|
team.users.add(user)
|
||||||
|
team.save()
|
||||||
|
messages.success(request, 'User added to team successfully.')
|
||||||
|
|
||||||
|
return redirect('show-team', team.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@project_perm_required('manage_team')
|
||||||
|
def team_remove_user(request, team, user):
|
||||||
|
|
||||||
|
team = get_object_or_404(Team, pk=team)
|
||||||
|
user = get_object_or_404(User, pk=user)
|
||||||
|
|
||||||
|
if user in team.users.all():
|
||||||
|
team.users.remove(user)
|
||||||
|
team.save()
|
||||||
|
messages.success(request, 'User removed from team successfully.')
|
||||||
|
else:
|
||||||
|
messages.error(request, 'This user does not belong to this team.')
|
||||||
|
|
||||||
|
return redirect('show-team', team.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@project_perm_required('manage_team')
|
||||||
|
def team_add_group(request, team, group):
|
||||||
|
|
||||||
|
team = get_object_or_404(Team, pk=team)
|
||||||
|
group = get_object_or_404(Group, pk=group)
|
||||||
|
|
||||||
|
if group in team.groups.all():
|
||||||
|
messages.warning(request, 'This group already belong to this team.')
|
||||||
|
else:
|
||||||
|
team.groups.add(group)
|
||||||
|
team.save()
|
||||||
|
messages.success(request, 'Group added to team successfully.')
|
||||||
|
|
||||||
|
return redirect('show-team', team.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@project_perm_required('manage_team')
|
||||||
|
def team_remove_group(request, team, group):
|
||||||
|
|
||||||
|
team = get_object_or_404(Team, pk=team)
|
||||||
|
group = get_object_or_404(Group, pk=group)
|
||||||
|
|
||||||
|
if group in team.groups.all():
|
||||||
|
team.groups.remove(group)
|
||||||
|
team.save()
|
||||||
|
messages.success(request, 'Group removed from team successfully.')
|
||||||
|
else:
|
||||||
|
messages.error(request, 'This group does not belong to this team.')
|
||||||
|
|
||||||
|
return redirect('show-team', team.pk)
|
||||||
|
|
||||||
|
|
||||||
|
@project_perm_required('manage_team')
|
||||||
|
@confirmation_required('Are you sure to delete this team?', prev='list-team')
|
||||||
|
def team_delete(request, team):
|
||||||
|
|
||||||
|
team = get_object_or_404(Team, pk=team)
|
||||||
|
|
||||||
|
team.delete()
|
||||||
|
messages.success(request, 'Team deleted successfully.')
|
||||||
|
|
||||||
|
return redirect('list-team')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue