add teamtask support

This commit is contained in:
Thomas Steen Rasmussen 2017-11-23 23:09:14 +01:00
parent d4265edaa0
commit 142afa5ead
20 changed files with 279 additions and 111 deletions

View file

@ -10,7 +10,7 @@
<div class="row">
<h2>Infodesk Backoffice</h2>
<div class="lead">
Orders with one or more Products that are not handed out
Paid (and not later refunded) orders with at least one product that is not yet handed out
</div>
</div>
<div class="row">

View file

@ -297,6 +297,7 @@ urlpatterns = [
VillageUpdateView.as_view(),
name='village_update'
),
# this has to be the last url in the list
url(
r'(?P<slug>[-_\w+]+)/$',
VillageDetailView.as_view(),
@ -306,45 +307,11 @@ urlpatterns = [
),
url(
r'^teams/', include([
url(
r'^$',
TeamListView.as_view(),
name='team_list'
),
url(
r'^members/(?P<pk>[0-9]+)/remove/$',
TeamMemberRemoveView.as_view(),
name='teammember_remove',
),
url(
r'^members/(?P<pk>[0-9]+)/approve/$',
TeamMemberApproveView.as_view(),
name='teammember_approve',
),
url(
r'(?P<slug>[-_\w+]+)/join/$',
TeamJoinView.as_view(),
name='team_join'
),
url(
r'(?P<slug>[-_\w+]+)/leave/$',
TeamLeaveView.as_view(),
name='team_leave'
),
url(
r'(?P<slug>[-_\w+]+)/manage/$',
TeamManageView.as_view(),
name='team_manage'
),
# this has to be the last url in the list
url(
r'(?P<slug>[-_\w+]+)/$',
TeamDetailView.as_view(),
name='team_detail'
),
])
r'^teams/',
include('teams.urls', namespace='teams')
),
])
)
]

View file

@ -37,7 +37,7 @@
<div class="row">
<div class="col-md-9 col-sm-9 text-container">
<div class="lead">
The BornHack team looks forward to organising another great event for the hacker community. We <a href="{% url 'team_list' camp_slug=camp.slug %}">still need volunteers</a>, so please let us know if you want to help!
The BornHack team looks forward to organising another great event for the hacker community. We <a href="{% url 'teams:list' camp_slug=camp.slug %}">still need volunteers</a>, so please let us know if you want to help!
</div>
</div>
<div class="col-md-3">

View file

@ -37,7 +37,7 @@
<div class="row">
<div class="col-md-9 col-sm-9 text-container">
<div class="lead">
The BornHack team looks forward to organising another great event for the hacker community. We <a href="{% url 'team_list' camp_slug=camp.slug %}">still need volunteers</a>, so please let us know if you want to help!
The BornHack team looks forward to organising another great event for the hacker community. We <a href="{% url 'teams:list' camp_slug=camp.slug %}">still need volunteers</a>, so please let us know if you want to help!
</div>
</div>
<div class="col-md-3">

View file

@ -37,7 +37,7 @@
<div class="row">
<div class="col-md-9 col-sm-9 text-container">
<div class="lead">
The BornHack team looks forward to organising another great event for the hacker community. We <a href="{% url 'team_list' camp_slug=camp.slug %}">still need volunteers</a>, so please let us know if you want to help!
The BornHack team looks forward to organising another great event for the hacker community. We <a href="{% url 'teams:list' camp_slug=camp.slug %}">still need volunteers</a>, so please let us know if you want to help!
</div>
</div>
<div class="col-md-3">

View file

@ -1,8 +1,18 @@
from django.contrib import admin
from .models import Team, TeamArea, TeamMember
from .models import Team, TeamArea, TeamMember, TeamTask
from .email import add_added_membership_email, add_removed_membership_email
@admin.register(TeamTask)
class TeamTaskAdmin(admin.ModelAdmin):
list_display = [
'id',
'team',
'name',
'description',
]
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
def get_responsible(self, obj):

View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-11-22 18:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('teams', '0016_auto_20170711_2247'),
]
operations = [
migrations.CreateModel(
name='TeamTask',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('name', models.CharField(help_text='Short name of this task', max_length=100)),
('slug', models.SlugField(blank=True, help_text='url slug, leave blank to autogenerate', max_length=255)),
('description', models.TextField(help_text='Description of the task. Markdown is supported.')),
('team', models.ForeignKey(help_text='The team this task belongs to', on_delete=django.db.models.deletion.CASCADE, to='teams.Team')),
],
options={
'ordering': ['name'],
},
),
migrations.AlterUniqueTogether(
name='teamtask',
unique_together=set([('slug', 'team'), ('name', 'team')]),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-11-22 21:04
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('teams', '0017_auto_20171122_1928'),
]
operations = [
migrations.AlterField(
model_name='teamtask',
name='team',
field=models.ForeignKey(help_text='The team this task belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='teams.Team'),
),
]

View file

@ -115,3 +115,38 @@ def add_responsible_email(sender, instance, created, **kwargs):
if created:
if not add_new_membership_email(instance):
logger.error('Error adding email to outgoing queue')
class TeamTask(CampRelatedModel):
team = models.ForeignKey(
'teams.Team',
related_name='tasks',
help_text='The team this task belongs to',
)
name = models.CharField(
max_length=100,
help_text='Short name of this task',
)
slug = models.SlugField(
max_length=255,
blank=True,
help_text='url slug, leave blank to autogenerate',
)
description = models.TextField(
help_text='Description of the task. Markdown is supported.'
)
class Meta:
ordering = ['name']
unique_together = (('name', 'team'), ('slug', 'team'))
@property
def camp(self):
return self.team.camp
def save(self, **kwargs):
if not self.slug:
slug = slugify(self.name)
self.slug = slug
super().save(**kwargs)

View file

@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% load commonmark %}
{% block title %}
{{ task.name }}
{% endblock %}
{% block content %}
<div class="panel panel-default">
<div class="panel-heading"><h4>Task: {{ task.name }}</h4></div>
<div class="panel-body">{{ task.description|commonmark }}</div>
<div class="panel-footer"><i>This task belongs to the <a href="{% url 'teams:detail' slug=task.team.slug camp_slug=task.team.camp.slug %}">{{ task.team.name }} Team</a></i></div>
</div>
{% endblock %}

View file

@ -9,56 +9,81 @@ Team: {{ team.name }} | {{ block.super }}
{% block content %}
<h3>{{ team.name }} Team ({{ team.area.name }} area)</h3>
<div class="panel panel-default">
<div class="panel-heading"><h4>{{ team.name }} Team</h4></div>
<div class="panel-body">
{{ team.description|unsafecommonmark }}
{% if request.user in team.responsible.all %}
<a href="{% url 'teams:manage' camp_slug=camp.slug slug=team.slug %}" class="btn btn-success">Manage Team</a>
{% endif %}
{{ team.description|unsafecommonmark }}
<p>Currently {{ team.approvedmembers.count }} people are members of this team{% if request.user in team.members.all %} (including you){% endif %}.</p>
<hr>
{% if request.user in team.members.all %}
<p>Your team status: {% membershipstatus request.user team %}</p>
{% endif %}
<h3>Members</h3>
<p>The following <b>{{ team.approvedmembers.count }}</b> people are members of the <b>{{ team.name }} team</b>:</p>
<table class="table">
<thead>
<tr>
<th>
Name
</th>
<th>
Status
</th>
</tr>
</thead>
<tbody>
{% for teammember in team.approvedmembers.all %}
<tr>
<td>
{% if teammember.user.profile.approved_public_credit_name %}
{{ teammember.user.profile.approved_public_credit_name }}
{% else %}
anonymous
{% endif %}
</td>
<td>
{% if teammember.responsible %}Team Responsible{% else %}Team Member{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if request.user in team.members.all %}
<a href="{% url 'team_leave' camp_slug=camp.slug slug=team.slug %}" class="btn btn-danger">Leave Team</a>
{% else %}
{% if team.needs_members %}
<a href="{% url 'team_join' camp_slug=camp.slug slug=team.slug %}" class="btn btn-success">Join Team</a>
{% endif %}
{% endif %}
{% if request.user in team.members.all %}
<p>Your membership status: <b>{% membershipstatus request.user team %}</b></p>
{% endif %}
{% if request.user in team.members.all %}
<a href="{% url 'teams:leave' camp_slug=camp.slug slug=team.slug %}" class="btn btn-danger">Leave Team</a>
{% else %}
{% if team.needs_members %}
<b>This team is looking for members!</b> <a href="{% url 'teams:join' camp_slug=camp.slug slug=team.slug %}" class="btn btn-xs btn-success">Join Team</a>
{% endif %}
{% endif %}
<hr>
<h3>Tasks</h3>
<p>This team is responsible for the following tasks</p>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for task in team.tasks.all %}
<tr>
<td><a href="{% url 'teams:task_detail' slug=task.slug camp_slug=camp.slug team_slug=team.slug %}">{{ task.name }}</a></td>
<td>{{ task.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if request.user in team.responsible.all %}
<a href="{% url 'team_manage' camp_slug=camp.slug slug=team.slug %}" class="btn btn-success">Manage Team</a>
{% endif %}
<hr />
<h3>Team Members</h3>
<p>The following people are members of the <b>{{ team.name }} team</b>:</p>
<table class="table">
<thead>
<tr>
<th>
Name
</th>
<th>
Status
</th>
</tr>
</thead>
<tbody>
{% for teammember in team.approvedmembers.all %}
{% if teammember.user.profile.approved_public_credit_name and teammember.approved or teammember.responsible %}
<tr>
<td>
{{ teammember.user.profile.approved_public_credit_name }}
</td>
<td>
{% if teammember.responsible %}Team Responsible{% else %}Team Member{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
{% if team.anoncount %}
<p>Plus <b>{{ team.anoncount }}</b> member(s) who prefer to remain anonymous.</p>
{% endif %}
{% endblock %}

View file

@ -13,6 +13,6 @@ Join Team: {{ team.name }} | {{ block.super }}
{% csrf_token %}
{{ form }}
<button class="btn btn-success" type="submit"><i class="fa fa-check"></i> Join {{ team.name }} Team</button>
<a href="{% url 'team_list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
<a href="{% url 'teams:list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
</form>
{% endblock %}

View file

@ -13,6 +13,6 @@ Leave Team: {{ team.name }} | {{ block.super }}
{% csrf_token %}
{{ form }}
<button class="btn btn-success" type="submit"><i class="fa fa-check"></i> Leave {{ team.name }} Team</button>
<a href="{% url 'team_list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
<a href="{% url 'teams:list' camp_slug=camp.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
</form>
{% endblock %}

View file

@ -22,6 +22,7 @@ Teams | {{ block.super }}
<th>Description</th>
<th>Responsible</th>
<th class="text-center">Members</th>
<th class="text-center">Tasks</th>
{% if request.user.is_authenticated %}
<th>Member?</th>
<th>Actions</th>
@ -32,7 +33,7 @@ Teams | {{ block.super }}
{% for team in teams %}
<tr>
<td>
<a href="{% url 'team_detail' camp_slug=camp.slug slug=team.slug %}">
<a href="{% url 'teams:detail' camp_slug=camp.slug slug=team.slug %}">
{{ team.name }} Team
</a>
</td>
@ -51,6 +52,10 @@ Teams | {{ block.super }}
{% if team.needs_members %}(more needed){% endif %}
</td>
<td class="text-center">
<span class="badge">{{ team.tasks.count }}</span><br>
</td>
{% if request.user.is_authenticated %}
<td class="text-center">
{% membershipstatus request.user team as membership_status %}
@ -67,15 +72,15 @@ Teams | {{ block.super }}
<td>
{% if request.user in team.members.all %}
<a href="{% url 'team_leave' camp_slug=camp.slug slug=team.slug %}" class="btn btn-danger"><i class="fa fa-minus"></i> Leave</a>
<a href="{% url 'teams:leave' camp_slug=camp.slug slug=team.slug %}" class="btn btn-danger"><i class="fa fa-minus"></i> Leave</a>
{% else %}
{% if team.needs_members %}
<a href="{% url 'team_join' camp_slug=camp.slug slug=team.slug %}" class="btn btn-success"><i class="fa fa-plus"></i> Join</a>
<a href="{% url 'teams:join' camp_slug=camp.slug slug=team.slug %}" class="btn btn-success"><i class="fa fa-plus"></i> Join</a>
{% endif %}
{% endif %}
{% if request.user in team.responsible.all %}
<a href="{% url 'team_manage' camp_slug=camp.slug slug=team.slug %}" class="btn btn-primary"><i class="fa fa-cog"></i> Manage</a>
<a href="{% url 'teams:manage' camp_slug=camp.slug slug=team.slug %}" class="btn btn-primary"><i class="fa fa-cog"></i> Manage</a>
{% endif %}
{% endif %}
</td>

View file

@ -71,10 +71,10 @@ Manage Team: {{ team.name }} | {{ block.super }}
</td>
<td>
{% if membership.approved %}
<a class="btn btn-danger" href="{% url 'teammember_remove' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-trash-o"></i> Remove</a>
<a class="btn btn-danger" href="{% url 'teams:teammember_remove' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-trash-o"></i> Remove</a>
{% else %}
<a class="btn btn-danger" href="{% url 'teammember_remove' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-trash-o"></i> Remove</a>
<a class="btn btn-success" href="{% url 'teammember_approve' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-check"></i> Approve</a>
<a class="btn btn-danger" href="{% url 'teams:teammember_remove' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-trash-o"></i> Remove</a>
<a class="btn btn-success" href="{% url 'teams:teammember_approve' camp_slug=camp.slug pk=membership.id %}"><i class="fa fa-check"></i> Approve</a>
{% endif %}
</td>
</tr>

View file

@ -13,6 +13,6 @@ Approve team member {{ teammember.user.profile.name }} for the {{ teammember.tea
{% csrf_token %}
{{ form }}
<button class="btn btn-success" type="submit"><i class="fa fa-check"></i> Add teammember</button>
<a href="{% url 'team_detail' camp_slug=teammember.team.camp.slug slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
</form>
{% endblock %}

View file

@ -13,6 +13,6 @@ Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.nam
{% csrf_token %}
{{ form }}
<button class="btn btn-danger" type="submit"><i class="fa fa-trash-o"></i> Remove teammember</button>
<a href="{% url 'team_detail' camp_slug=teammember.team.camp.slug slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fa fa-remove"></i> Cancel</a>
</form>
{% endblock %}

47
src/teams/urls.py Normal file
View file

@ -0,0 +1,47 @@
from django.conf.urls import url
from .views import *
urlpatterns = [
url(
r'^$',
TeamListView.as_view(),
name='list'
),
url(
r'^members/(?P<pk>[0-9]+)/remove/$',
TeamMemberRemoveView.as_view(),
name='teammember_remove',
),
url(
r'^members/(?P<pk>[0-9]+)/approve/$',
TeamMemberApproveView.as_view(),
name='teammember_approve',
),
url(
r'(?P<team_slug>[-_\w+]+)/tasks/(?P<slug>[-_\w+]+)/$',
TaskDetailView.as_view(),
name='task_detail',
),
url(
r'(?P<slug>[-_\w+]+)/join/$',
TeamJoinView.as_view(),
name='join'
),
url(
r'(?P<slug>[-_\w+]+)/leave/$',
TeamLeaveView.as_view(),
name='leave'
),
url(
r'(?P<slug>[-_\w+]+)/manage/$',
TeamManageView.as_view(),
name='manage'
),
# this has to be the last url in the list
url(
r'(?P<slug>[-_\w+]+)/$',
TeamDetailView.as_view(),
name='detail'
),
]

View file

@ -1,7 +1,7 @@
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, FormView
from camps.mixins import CampViewMixin
from .models import Team, TeamMember
from .models import Team, TeamMember, TeamTask
from .forms import ManageTeamForm
from .email import add_added_membership_email, add_removed_membership_email
from django.contrib.auth.mixins import LoginRequiredMixin
@ -62,22 +62,22 @@ class TeamJoinView(LoginRequiredMixin, CampViewMixin, UpdateView):
request,
"Please fill the description in your profile before joining a team"
)
return redirect('team_list', camp_slug=self.camp.slug)
return redirect('teams:list', camp_slug=self.camp.slug)
if request.user in self.get_object().members.all():
messages.warning(request, "You are already a member of this team")
return redirect('team_list', camp_slug=self.camp.slug)
return redirect('teams:list', camp_slug=self.camp.slug)
if not self.get_object().needs_members:
messages.warning(request, "This team does not need members right now")
return redirect('team_list', camp_slug=self.get_object().camp.slug)
return redirect('teams:list', camp_slug=self.get_object().camp.slug)
return super().get(request, *args, **kwargs)
def form_valid(self, form):
TeamMember.objects.create(team=self.get_object(), user=self.request.user)
messages.success(self.request, "You request to join the team %s has been registered, thank you." % self.get_object().name)
return redirect('team_list', camp_slug=self.get_object().camp.slug)
return redirect('teams:list', camp_slug=self.get_object().camp.slug)
class TeamLeaveView(LoginRequiredMixin, CampViewMixin, UpdateView):
@ -88,14 +88,14 @@ class TeamLeaveView(LoginRequiredMixin, CampViewMixin, UpdateView):
def get(self, request, *args, **kwargs):
if request.user not in self.get_object().members.all():
messages.warning(request, "You are not a member of this team")
return redirect('team_list', camp_slug=self.get_object().camp.slug)
return redirect('teams:list', camp_slug=self.get_object().camp.slug)
return super().get(request, *args, **kwargs)
def form_valid(self, form):
TeamMember.objects.filter(team=self.get_object(), user=self.request.user).delete()
messages.success(self.request, "You are no longer a member of the team %s" % self.get_object().name)
return redirect('team_list', camp_slug=self.get_object().camp.slug)
return redirect('teams:list', camp_slug=self.get_object().camp.slug)
class EnsureTeamMemberResponsibleMixin(SingleObjectMixin):
@ -145,3 +145,9 @@ class TeamMemberApproveView(LoginRequiredMixin, CampViewMixin, EnsureTeamMemberR
)
return redirect('team_detail', camp_slug=self.camp.slug, slug=form.instance.team.slug)
class TaskDetailView(CampViewMixin, DetailView):
template_name = "task_detail.html"
context_object_name = "task"
model = TeamTask

View file

@ -91,7 +91,7 @@
<a class="btn {% menubuttonclass 'program' %}" href="{% url 'schedule_index' camp_slug=camp.slug %}">Program</a>
<a class="btn {% menubuttonclass 'villages' %}" href="{% url 'village_list' camp_slug=camp.slug %}">Villages</a>
<a class="btn {% menubuttonclass 'sponsors' %}" href="{% url 'sponsors' camp_slug=camp.slug %}">Sponsors</a>
<a class="btn {% menubuttonclass 'teams' %}" href="{% url 'team_list' camp_slug=camp.slug %}">Teams</a>
<a class="btn {% menubuttonclass 'teams' %}" href="{% url 'teams:list' camp_slug=camp.slug %}">Teams</a>
</div>
<div class="btn-group-vertical visible-xs">
<a class="btn {% menubuttonclass 'camps' %}" href="{% url 'camp_detail' camp_slug=camp.slug %}">{{ camp.title }}</a>
@ -99,7 +99,7 @@
<a class="btn {% menubuttonclass 'program' %}" href="{% url 'schedule_index' camp_slug=camp.slug %}">Program</a>
<a class="btn {% menubuttonclass 'villages' %}" href="{% url 'village_list' camp_slug=camp.slug %}">Villages</a>
<a class="btn {% menubuttonclass 'sponsors' %}" href="{% url 'sponsors' camp_slug=camp.slug %}">Sponsors</a>
<a class="btn {% menubuttonclass 'teams' %}" href="{% url 'team_list' camp_slug=camp.slug %}">Teams</a>
<a class="btn {% menubuttonclass 'teams' %}" href="{% url 'teams:list' camp_slug=camp.slug %}">Teams</a>
</div>
<p>
</div>