Teams detail page was starting to get crowded. This is the start of a mostly visual, but also structural, refactor.

This commit is contained in:
Víðir Valberg Guðmundsson 2018-07-22 23:18:50 +02:00
parent cf9e9ebd5a
commit c68015fe26
24 changed files with 515 additions and 411 deletions

View file

@ -48,7 +48,7 @@ a, a:active, a:focus {
margin-top: 6px;
}
.nav li a {
#top-navbar > .nav li a {
padding: 30px 7px;
}

View file

@ -110,7 +110,7 @@ class Team(CampRelatedModel):
return '{} ({})'.format(self.name, self.camp)
def get_absolute_url(self):
return reverse_lazy('teams:detail', kwargs={'camp_slug': self.camp.slug, 'team_slug': self.slug})
return reverse_lazy('teams:general', kwargs={'camp_slug': self.camp.slug, 'team_slug': self.slug})
def save(self, **kwargs):
# generate slug if needed
@ -215,6 +215,7 @@ class Team(CampRelatedModel):
class TeamMember(CampRelatedModel):
user = models.ForeignKey(
'auth.User',
on_delete=models.PROTECT,

View file

@ -13,6 +13,6 @@ Fix IRC permissions for NickServ user {{ request.user.profile.nickserv_username
{% csrf_token %}
{{ form }}
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Yes Please</button>
<a href="{% url 'teams:detail' camp_slug=team.camp.slug team_slug=team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
<a href="{% url 'teams:general' camp_slug=team.camp.slug team_slug=team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
</form>
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
@ -11,7 +11,7 @@ Create Info item
in "{{ form.instance.category.headline }}"
{% endblock %}
{% block content %}
{% block team_content %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>
@ -36,6 +36,6 @@ in "{{ form.instance.category.headline }}"
{% endif %}
</form>
</div>
<div class="panel-footer"><i>This info item belongs to the <a href="{% url 'teams:detail' team_slug=team.slug camp_slug=team.camp.slug %}">{{ team.name }} Team</a></i></div>
<div class="panel-footer"><i>This info item belongs to the <a href="{% url 'teams:general' team_slug=team.slug camp_slug=team.camp.slug %}">{{ team.name }} Team</a></i></div>
</div>
{% endblock %}

View file

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

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
@ -11,7 +11,7 @@ Create Task
for {{ team.name }} Team
{% endblock %}
{% block content %}
{% block team_content %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>
@ -30,7 +30,7 @@ for {{ team.name }} Team
<button type="submit" class="btn btn-primary">{% if form.instance.id %}Save{% else %}Create{% endif %}</button>
</form>
</div>
<div class="panel-footer"><i>This task belongs to the <a href="{% url 'teams:detail' team_slug=team.slug camp_slug=team.camp.slug %}">{{ team.name }} Team</a></i></div>
<div class="panel-footer"><i>This task belongs to the <a href="{% url 'teams:general' team_slug=team.slug camp_slug=team.camp.slug %}">{{ team.name }} Team</a></i></div>
</div>
{% endblock %}

View file

@ -0,0 +1,69 @@
{% extends 'base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% load teams_tags %}
{% block title %}
Team: {{ team.name }} | {{ block.super }}
{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ team.name }} Team</h1>
</div>
<div class="row">
<div class="col-md-2">
<ul class="nav nav-pills nav-stacked">
<li {% if view.template_name == "team_general.html" %}class="active"{% endif %}>
<a href="{% url "teams:general" camp_slug=team.camp.slug team_slug=team.slug %}">
General
</a>
</li>
<li {% if view.template_name == "team_tasks.html" %}class="active"{% endif %}>
<a href="{% url "teams:tasks" camp_slug=team.camp.slug team_slug=team.slug %}">
Tasks
</a>
</li>
<li {% if view.template_name == "team_members.html" %}class="active"{% endif %}>
<a href="{% url "teams:members" camp_slug=team.camp.slug team_slug=team.slug %}">
Members
{% if request.user in team.responsible_members.all and team.unapproved_members %}
<span class="label label-danger">Pending</span>
{% endif %}
</a>
</li>
</ul>
<hr />
{% if request.user.is_authenticated %}
{% if request.user in team.members.all %}
<p>Your membership status: <b>{% membershipstatus user team %}</b></p>
{% if request.user in team.responsible_members.all %}
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-cog"></i> Manage Team</a>
{% endif %}
{% else %}
{% if team.needs_members %}
<b>This team is looking for members!</b> <a href="{% url 'teams:join' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-xs btn-success"><i class="fas fa-plus"></i> Join Team</a>
{% endif %}
{% endif %}
{% endif %}
</div>
<div class="col-md-10">
{% block team_content %}{% endblock %}
</div>
</div>
{% endblock %}

View file

@ -1,189 +0,0 @@
{% extends 'base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% load teams_tags %}
{% block title %}
Team: {{ team.name }} | {{ block.super }}
{% endblock %}
{% block content %}
<div class="page-header">
<h1>{{ team.name }} Team Details</h1>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h4>Description</h4>
</div>
<div class="panel-body">
{{ team.description|untrustedcommonmark }}
</div>
</div>
{# Team communications #}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Communication Channels</h4>
</div>
<div class="panel-body">
{{ team.camp.title }} teams primarily use mailing lists and IRC to communicate. The <b>{{ team.name }} team</b> can be contacted in the following ways:</p>
<h5>Mailing List</h5>
{% if team.mailing_list and request.user in team.approved_members.all %}
<p>The {{ team.name }} Team mailinglist is <b>{{ team.mailing_list }}</b>{% if team.mailing_list_archive_public %}, and the archives are publicly available{% endif %}. You should sign up for the list if you haven't already.</p>
{% elif team.mailing_list and team.mailinglist_nonmember_posts %}
<p>The {{ team.name }} Team mailinglist is <b>{{ team.mailing_list }}</b>{% if team.mailing_list_archive_public %}, and the archives are publicly available{% endif %}. You do not need to be a member of the list to post to it.</p>
{% else %}
<p>The {{ team.name }} Team does not have a public mailing list, but it can be contacted through our main email <a href="mailto:info@bornhack.dk">info@bornhack.dk</a>.
{% endif %}
<h5>IRC Channel</h5>
{% if team.public_irc_channel_name %}
<p>The {{ team.name }} Team public IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.public_irc_channel_name }}">{{ team.public_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.
{% else %}
<p>The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ IRCBOT_PUBLIC_CHANNEL }}">{{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.</p>
{% endif %}
{% if request.user in team.approved_members.all and team.private_irc_channel_name %}
<p>The {{ team.name }} Team private IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.private_irc_channel_name }}">{{ team.private_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.</p>
{% endif %}
</div>
</div>
{# Team tasks #}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Tasks</h4>
</div>
<div class="panel-body">
<p>The {{ team.name }} Team is responsible for the following tasks</p>
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Action</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>
<td>
<a href="{% url 'teams:task_detail' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fas fa-search"></i> Details</a>
{% if request.user in team.responsible_members.all %}
<a href="{% url 'teams:task_update' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fas fa-edit"></i> Edit Task</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if request.user in team.responsible_members.all %}
<a href="{% url 'teams:task_create' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-plus"></i> Create Task</a>
{% endif %}
</div>
</div>
{# Team members #}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Members</h4>
</div>
<div class="panel-body">
<p>The following <b>{{ team.approved_members.count }}</b> people {% if team.unapproved_members.count %}(and {{ team.unapproved_members.count }} pending){% endif %} are members of the <b>{{ team.name }} Team</b>:</p>
<table class="table table-hover">
<thead>
<tr>
<th>
Name
</th>
<th>
Status
</th>
</tr>
</thead>
<tbody>
{% for teammember in team.memberships.all %}
<tr>
<td>
{{ teammember.user.profile.get_public_credit_name }} {% if teammember.user == request.user %}(this is you!){% endif %}
</td>
<td>
Team {% if teammember.responsible %}Responsible{% else %}Member{% endif %}
{% if not teammember.approved %}(pending approval){% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p>Your membership status: <b>{% membershipstatus user team %}</b></p>
{% if request.user in team.members.all %}
{% if team.irc_channel and team.irc_channel_managed and request.user.profile.nickserv_username %}
<a href="{% url 'teams:fix_irc_acl' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-wrench"></i> Fix IRC ACL</a>&nbsp;
{% endif %}
<a href="{% url 'teams:leave' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-danger"><i class="fas fa-times"></i> 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 team_slug=team.slug %}" class="btn btn-xs btn-success"><i class="fas fa-plus"></i> Join Team</a>
{% endif %}
{% endif %}
{% if request.user in team.responsible_members.all %}
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-cog"></i> Manage Team</a>
{% endif %}
<hr>
</div>
</div>
{# Team info categories section - only visible for team responsible #}
{% if request.user in team.responsible_members.all and team.info_categories.exists %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Info Categories</h4>
</div>
<div class="panel-body">
{% for info_category in team.info_categories.all %}
<h4>{{ info_category.headline }}</h4>
<table class="table table-hover">
<thead>
<tr>
<th>Item name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for item in info_category.infoitems.all %}
<tr>
<td>{{ item.headline }}</td>
<td>
<a href="{% url 'teams:info_item_update' camp_slug=camp.slug team_slug=team.slug category_anchor=info_category.anchor item_anchor=item.anchor %}"
class="btn btn-primary btn-sm">
<i class="fas fa-edit"></i> Edit
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{% url 'teams:info_item_create' camp_slug=camp.slug team_slug=team.slug category_anchor=info_category.anchor %}" class="btn btn-primary"><i class="fas fa-plus"></i> Create Info Item</a>
<hr />
{% endfor %}
</div>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,48 @@
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% load teams_tags %}
{% block team_content %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Description</h4>
</div>
<div class="panel-body">
{{ team.description|untrustedcommonmark }}
</div>
</div>
{# Team communications #}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Communication Channels</h4>
</div>
<div class="panel-body">
{{ team.camp.title }} teams primarily use mailing lists and IRC to communicate. The <b>{{ team.name }} team</b> can be contacted in the following ways:</p>
<h5>Mailing List</h5>
{% if team.mailing_list and request.user in team.approved_members.all %}
<p>The {{ team.name }} Team mailinglist is <b>{{ team.mailing_list }}</b>{% if team.mailing_list_archive_public %}, and the archives are publicly available{% endif %}. You should sign up for the list if you haven't already.</p>
{% elif team.mailing_list and team.mailinglist_nonmember_posts %}
<p>The {{ team.name }} Team mailinglist is <b>{{ team.mailing_list }}</b>{% if team.mailing_list_archive_public %}, and the archives are publicly available{% endif %}. You do not need to be a member of the list to post to it.</p>
{% else %}
<p>The {{ team.name }} Team does not have a public mailing list, but it can be contacted through our main email <a href="mailto:info@bornhack.dk">info@bornhack.dk</a>.
{% endif %}
<h5>IRC Channel</h5>
{% if team.public_irc_channel_name %}
<p>The {{ team.name }} Team public IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.public_irc_channel_name }}">{{ team.public_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.
{% else %}
<p>The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ IRCBOT_PUBLIC_CHANNEL }}">{{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.</p>
{% endif %}
{% if request.user in team.approved_members.all and team.private_irc_channel_name %}
<p>The {{ team.name }} Team private IRC channel is <a href="irc://{{ IRCBOT_SERVER_HOSTNAME }}/{{ team.private_irc_channel_name }}">{{ team.private_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}</a>.</p>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,52 @@
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% load teams_tags %}
{% block team_content %}
{% if request.user in team.responsible_members.all and team.info_categories.exists %}
<h4> SHOULD NOT HAPPEN !!! </h4>
{% endif %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Info Categories</h4>
</div>
<div class="panel-body">
{% for info_category in team.info_categories.all %}
<h4>{{ info_category.headline }}</h4>
<table class="table table-hover">
<thead>
<tr>
<th>Item name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for item in info_category.infoitems.all %}
<tr>
<td>{{ item.headline }}</td>
<td>
<a href="{% url 'teams:info_item_update' camp_slug=camp.slug team_slug=team.slug category_anchor=info_category.anchor item_anchor=item.anchor %}"
class="btn btn-primary btn-sm">
<i class="fas fa-edit"></i> Edit
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{% url 'teams:info_item_create' camp_slug=camp.slug team_slug=team.slug category_anchor=info_category.anchor %}" class="btn btn-primary"><i class="fas fa-plus"></i> Create Info Item</a>
<hr />
{% endfor %}
</div>
</div>
{% endblock %}

View file

@ -1,11 +1,11 @@
{% extends 'base.html' %}
{% extends 'team_base.html' %}
{% load commonmark %}
{% block title %}
Join Team: {{ team.name }} | {{ block.super }}
{% endblock %}
{% block content %}
{% block team_content %}
<p class="lead">Really join the <b>{{ team.name }}</b> Team for <b>{{ team.camp.title }}</b>?</p>

View file

@ -1,11 +1,11 @@
{% extends 'base.html' %}
{% extends 'team_base.html' %}
{% load commonmark %}
{% block title %}
Leave Team: {{ team.name }} | {{ block.super }}
{% endblock %}
{% block content %}
{% block team_content %}
<h3>Leave {{ team.name }} Team</h3>
<p class="lead">Really leave the <b>{{ team.name }}</b> team?<p>

View file

@ -33,7 +33,9 @@ Teams | {{ block.super }}
{% for team in teams %}
<tr>
<td>
<a href="{% url 'teams:detail' camp_slug=camp.slug team_slug=team.slug %}">
{{ camp.slug}}
{{ team.slug}}
<a href="{% url 'teams:general' camp_slug=camp.slug team_slug=team.slug %}">
{{ team.name }} Team
</a>
</td>
@ -63,7 +65,7 @@ Teams | {{ block.super }}
<td>
<div class="btn-group-vertical">
<a class="btn btn-primary" href="{% url 'teams:detail' camp_slug=camp.slug team_slug=team.slug %}"><i class="fas fa-search"></i> Details</a>
<a class="btn btn-primary" href="{% url 'teams:general' camp_slug=camp.slug team_slug=team.slug %}"><i class="fas fa-search"></i> Details</a>
{% if request.user in team.responsible_members.all %}
<a href="{% url 'teams:manage' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-cog"></i> Manage</a>
{% endif %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %}
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
@ -6,7 +6,7 @@
Manage Team: {{ team.name }} | {{ block.super }}
{% endblock %}
{% block content %}
{% block team_content %}
<div class="panel panel-default">
<div class="panel-heading"><h4>Manage {{ team.name }} Team</h4></div>
<div class="panel-body" style="margin-left: 1em; margin-right: 1em;">
@ -18,81 +18,12 @@ Manage Team: {{ team.name }} | {{ block.super }}
{% buttons %}
<button class="btn btn-success pull-right" type="submit"><i class="fas fa-check"></i> Save Team</button>
<a class="btn btn-primary pull-right" href="{% url 'teams:detail' team_slug=team.slug camp_slug=camp.slug %}"><i class="fas fa-times"></i> Cancel</a>&nbsp;
<a class="btn btn-primary pull-right" href="{% url 'teams:general' team_slug=team.slug camp_slug=camp.slug %}"><i class="fas fa-times"></i> Cancel</a>&nbsp;
{% endbuttons %}
</form>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><h4>Manage {{ team.name }} Team Members</h4></div>
<div class="panel-body" style="margin-left: 1em; margin-right: 1em;">
{% if team.teammember_set.exists %}
<table class="table table-hover">
<thead>
<tr>
<th>
Username
</th>
<th>
Name
</th>
<th>
Email
</th>
<th>
Description
</th>
<th>
Public Credit Name
</th>
<th>
Membership
</th>
<th>
Action
</th>
</tr>
</thead>
<tbody>
{% for membership in team.teammember_set.all %}
<tr>
<td>
{{ membership.user }}
</td>
<td>
{{ membership.user.profile.name }}
</td>
<td>
{{ membership.user.profile.email }}
</td>
<td>
{{ membership.user.profile.description }}
</td>
<td>
{{ membership.user.profile.public_credit_name|default:"N/A" }}
{% if membership.user.profile.public_credit_name and not membership.user.profile.public_credit_name_approved %}<span class="text-warning">(name not approved)</span>{% endif %}
</td>
<td>
{% if membership.approved %}member{% else %}pending{% endif %}
</td>
<td>
<div class="btn-group-vertical">
<a class="btn btn-danger" href="{% url 'teams:teammember_remove' camp_slug=camp.slug pk=membership.id %}"><i class="fas fa-trash-o"></i> Remove Member</a>
{% if not membership.approved %}
<a class="btn btn-success" href="{% url 'teams:teammember_approve' camp_slug=camp.slug pk=membership.id %}"><i class="fas fa-check"></i> Approve Member</a>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No members found!</p>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,84 @@
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% load teams_tags %}
{% block team_content %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Members</h4>
</div>
<div class="panel-body">
<p>The following <b>{{ team.approved_members.count }}</b> people {% if team.unapproved_members.count %}(and {{ team.unapproved_members.count }} pending){% endif %} are members of the <b>{{ team.name }} Team</b>:</p>
<table class="table table-hover">
<thead>
<tr>
<th>
Name
</th>
<th>
Status
</th>
{% if request.user in team.responsible_members.all %}
<th>
Action
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for teammember in team.memberships.all %}
<tr>
<td>
{{ teammember.user.profile.get_public_credit_name }} {% if teammember.user == request.user %}(this is you!){% endif %}
</td>
<td>
Team {% if teammember.responsible %}Responsible{% else %}Member{% endif %}
{% if not teammember.approved %}(pending approval){% endif %}
</td>
{% if request.user in team.responsible_members.all %}
<td>
<div class="btn-group-vertical">
<a class="btn btn-danger"
href="{% url 'teams:teammember_remove' camp_slug=camp.slug team_slug=team.slug pk=teammember.id %}">
<i class="fas fa-trash-o"></i> Remove Member
</a>
{% if not teammember.approved %}
<a class="btn btn-success"
href="{% url 'teams:teammember_approve' camp_slug=camp.slug team_slug=team.slug pk=teammember.id %}">
<i class="fas fa-check"></i> Approve Member
</a>
{% endif %}
</div>
</td>
{% endif %}
</tr>
{% empty %}
<p>No members found!</p>
{% endfor %}
</tbody>
</table>
{% if request.user.authorized %}
<p>Your membership status: <b>{% membershipstatus user team %}</b></p>
{% if request.user in team.members.all %}
{% if team.irc_channel and team.irc_channel_managed and request.user.profile.nickserv_username %}
<a href="{% url 'teams:fix_irc_acl' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-wrench"></i> Fix IRC ACL</a>&nbsp;
{% endif %}
<a href="{% url 'teams:leave' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-danger"><i class="fas fa-times"></i> 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 team_slug=team.slug %}" class="btn btn-xs btn-success"><i class="fas fa-plus"></i> Join Team</a>
{% endif %}
{% endif %}
{% endif %}
<hr>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,42 @@
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% load teams_tags %}
{% block team_content %}
<div class="panel panel-default">
<div class="panel-heading">
<h4>Tasks</h4>
</div>
<div class="panel-body">
<p>The {{ team.name }} Team is responsible for the following tasks</p>
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Action</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>
<td>
<a href="{% url 'teams:task_detail' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fas fa-search"></i> Details</a>
{% if request.user in team.responsible_members.all %}
<a href="{% url 'teams:task_update' camp_slug=camp.slug team_slug=team.slug slug=task.slug %}" class="btn btn-primary btn-sm"><i class="fas fa-edit"></i> Edit Task</a>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if request.user in team.responsible_members.all %}
<a href="{% url 'teams:task_create' camp_slug=camp.slug team_slug=team.slug %}" class="btn btn-primary"><i class="fas fa-plus"></i> Create Task</a>
{% endif %}
</div>
</div>
{% endblock %}

View file

@ -1,11 +1,11 @@
{% extends 'base.html' %}
{% extends 'team_base.html' %}
{% load commonmark %}
{% block title %}
Approve team member {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team
{% endblock %}
{% block content %}
{% block team_content %}
<h3>Approve member {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team</h3>
<p class="lead">Really approve the user <b>{{ teammember.user.profile.name }}</b> for the {{ teammember.team.name }} team? The user will receive an email with a message.<p>
@ -13,6 +13,7 @@ Approve team member {{ teammember.user.profile.name }} for the {{ teammember.tea
{% csrf_token %}
{{ form }}
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Add teammember</button>
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
<a href="{% url 'teams:general' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
</form>
{% endblock %}

View file

@ -1,11 +1,11 @@
{% extends 'base.html' %}
{% extends 'team_base.html' %}
{% load commonmark %}
{% block title %}
Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team
{% endblock %}
{% block content %}
{% block team_content %}
<h3>Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team</h3>
<p class="lead">Really remove the user <b>{{ teammember.user.profile.name }}</b> from the {{ teammember.team.name }} team? The user will receive an email with a message.<p>
@ -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="fas fa-trash-o"></i> Remove teammember</button>
<a href="{% url 'teams:detail' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
<a href="{% url 'teams:general' camp_slug=teammember.team.camp.slug team_slug=teammember.team.slug %}" class="btn btn-default" type="submit"><i class="fas fa-times"></i> Cancel</a>
</form>
{% endblock %}

View file

@ -2,17 +2,27 @@ from django.urls import path, include
from teams.views.base import (
TeamListView,
TeamMemberRemoveView,
TeamMemberApproveView,
TeamDetailView,
TeamJoinView,
TeamLeaveView,
TeamGeneralView,
TeamManageView,
FixIrcAclView,
)
from teams.views.info import InfoItemUpdateView, InfoItemCreateView, InfoItemDeleteView
from teams.views.members import (
TeamMembersView,
TeamMemberRemoveView,
TeamMemberApproveView,
TeamJoinView,
TeamLeaveView,
)
from teams.views.info import (
InfoItemUpdateView,
InfoItemCreateView,
InfoItemDeleteView,
)
from teams.views.tasks import (
TeamTasksView,
TaskCreateView,
TaskDetailView,
TaskUpdateView,
@ -26,26 +36,12 @@ urlpatterns = [
TeamListView.as_view(),
name='list'
),
path(
'members/', include([
path(
'<int:pk>/remove/',
TeamMemberRemoveView.as_view(),
name='teammember_remove',
),
path(
'<int:pk>/approve/',
TeamMemberApproveView.as_view(),
name='teammember_approve',
),
]),
),
path(
'<slug:team_slug>/', include([
path(
'',
TeamDetailView.as_view(),
name='detail'
TeamGeneralView.as_view(),
name='general'
),
path(
'join/',
@ -67,8 +63,32 @@ urlpatterns = [
FixIrcAclView.as_view(),
name='fix_irc_acl',
),
path(
'members/', include([
path(
'',
TeamMembersView.as_view(),
name='members'
),
path(
'<int:pk>/remove/',
TeamMemberRemoveView.as_view(),
name='teammember_remove',
),
path(
'<int:pk>/approve/',
TeamMemberApproveView.as_view(),
name='teammember_approve',
),
]),
),
path(
'tasks/', include([
path(
'',
TeamTasksView.as_view(),
name='tasks',
),
path(
'create/',
TaskCreateView.as_view(),

View file

@ -7,11 +7,8 @@ from django.contrib import messages
from django.urls import reverse_lazy
from django.conf import settings
from profiles.models import Profile
from .mixins import EnsureTeamResponsibleMixin, EnsureTeamMemberResponsibleMixin
from .mixins import EnsureTeamResponsibleMixin
from ..models import Team, TeamMember
from ..email import add_added_membership_email, add_removed_membership_email
import logging
logger = logging.getLogger("bornhack.%s" % __name__)
@ -23,14 +20,14 @@ class TeamListView(CampViewMixin, ListView):
context_object_name = 'teams'
class TeamDetailView(CampViewMixin, DetailView):
template_name = "team_detail.html"
class TeamGeneralView(CampViewMixin, DetailView):
template_name = "team_general.html"
context_object_name = 'team'
model = Team
slug_url_kwarg = 'team_slug'
def get_context_data(self, **kwargs):
context = super(TeamDetailView, self).get_context_data(**kwargs)
context = super().get_context_data(**kwargs)
context['IRCBOT_SERVER_HOSTNAME'] = settings.IRCBOT_SERVER_HOSTNAME
context['IRCBOT_PUBLIC_CHANNEL'] = settings.IRCBOT_PUBLIC_CHANNEL
return context
@ -39,102 +36,20 @@ class TeamDetailView(CampViewMixin, DetailView):
class TeamManageView(CampViewMixin, EnsureTeamResponsibleMixin, UpdateView):
model = Team
template_name = "team_manage.html"
fields = ['description', 'needs_members', 'public_irc_channel_name', 'public_irc_channel_bot', 'public_irc_channel_managed', 'private_irc_channel_name', 'private_irc_channel_bot', 'private_irc_channel_managed']
fields = ['description', 'needs_members', 'public_irc_channel_name',
'public_irc_channel_bot', 'public_irc_channel_managed',
'private_irc_channel_name', 'private_irc_channel_bot',
'private_irc_channel_managed']
slug_url_kwarg = 'team_slug'
def get_success_url(self):
return reverse_lazy('teams:detail', kwargs={'camp_slug': self.camp.slug, 'team_slug': self.get_object().slug})
return reverse_lazy('teams:general', kwargs={'camp_slug': self.camp.slug, 'team_slug': self.get_object().slug})
def form_valid(self, form):
messages.success(self.request, "Team has been saved")
return super().form_valid(form)
class TeamJoinView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "team_join.html"
model = Team
fields = []
slug_url_kwarg = 'team_slug'
def get(self, request, *args, **kwargs):
if not Profile.objects.get(user=request.user).description:
messages.warning(
request,
"Please fill the description in your profile before joining a team"
)
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('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('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('teams:list', camp_slug=self.get_object().camp.slug)
class TeamLeaveView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "team_leave.html"
model = Team
fields = []
slug_url_kwarg = 'team_slug'
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('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('teams:list', camp_slug=self.get_object().camp.slug)
class TeamMemberRemoveView(LoginRequiredMixin, CampViewMixin, EnsureTeamMemberResponsibleMixin, UpdateView):
template_name = "teammember_remove.html"
model = TeamMember
fields = []
def form_valid(self, form):
form.instance.delete()
if add_removed_membership_email(form.instance):
messages.success(self.request, "Team member removed")
else:
messages.success(self.request, "Team member removed (unable to add email to outgoing queue).")
logger.error(
'Unable to add removed email to outgoing queue for teammember: {}'.format(form.instance)
)
return redirect('teams:detail', camp_slug=self.camp.slug, team_slug=form.instance.team.slug)
class TeamMemberApproveView(LoginRequiredMixin, CampViewMixin, EnsureTeamMemberResponsibleMixin, UpdateView):
template_name = "teammember_approve.html"
model = TeamMember
fields = []
def form_valid(self, form):
form.instance.approved = True
form.instance.save()
if add_added_membership_email(form.instance):
messages.success(self.request, "Team member approved")
else:
messages.success(self.request, "Team member removed (unable to add email to outgoing queue).")
logger.error(
'Unable to add approved email to outgoing queue for teammember: {}'.format(form.instance)
)
return redirect('teams:detail', camp_slug=self.camp.slug, team_slug=form.instance.team.slug)
class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "fix_irc_acl.html"
model = Team
@ -151,17 +66,17 @@ class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
# check if the logged in user has an approved membership of this team
if request.user not in self.get_object().approved_members.all():
messages.error(request, 'No thanks')
return redirect('teams:detail', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
return redirect('teams:general', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
# check if we manage the channel for this team
if not self.get_object().irc_channel or not self.get_object().irc_channel_managed:
messages.error(request, 'IRC functionality is disabled for this team, or the team channel is not managed by the bot')
return redirect('teams:detail', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
return redirect('teams:general', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
# check if user has a nickserv username
if not request.user.profile.nickserv_username:
messages.error(request, 'Please go to your profile and set your NickServ username first. Make sure the account is registered with NickServ first!')
return redirect('teams:detail', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
return redirect('teams:general', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
return response
@ -177,7 +92,7 @@ class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
except TeamMember.DoesNotExist:
# this membership is already marked as membership.irc_channel_acl_ok=False, no need to do anything
messages.error(request, 'No need, this membership is already marked as irc_channel_acl_ok=False, so the bot will fix the ACL soon')
return redirect('teams:detail', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
return redirect('teams:general', camp_slug=self.get_object().camp.slug, team_slug=self.get_object().slug)
return super().get(
request, *args, **kwargs
@ -195,5 +110,5 @@ class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
membership.irc_channel_acl_ok = False
membership.save()
messages.success(self.request, "OK, hang on while we fix the permissions for your NickServ user '%s' for IRC channel '%s'" % (self.request.user.profile.nickserv_username, form.instance.irc_channel_name))
return redirect('teams:detail', camp_slug=form.instance.camp.slug, team_slug=form.instance.slug)
return redirect('teams:general', camp_slug=form.instance.camp.slug, team_slug=form.instance.slug)

117
src/teams/views/members.py Normal file
View file

@ -0,0 +1,117 @@
import logging
from django.views.generic import DetailView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.shortcuts import redirect
from ..models import Team, TeamMember
from profiles.models import Profile
from camps.mixins import CampViewMixin
from .mixins import EnsureTeamMemberResponsibleMixin
from ..email import add_added_membership_email, add_removed_membership_email
logger = logging.getLogger("bornhack.%s" % __name__)
class TeamMembersView(CampViewMixin, DetailView):
template_name = "team_members.html"
context_object_name = 'team'
model = Team
slug_url_kwarg = 'team_slug'
class TeamJoinView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "team_join.html"
model = Team
fields = []
slug_url_kwarg = 'team_slug'
def get(self, request, *args, **kwargs):
if not Profile.objects.get(user=request.user).description:
messages.warning(
request,
"Please fill the description in your profile before joining a team"
)
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('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('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('teams:list', camp_slug=self.get_object().camp.slug)
class TeamLeaveView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "team_leave.html"
model = Team
fields = []
slug_url_kwarg = 'team_slug'
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('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('teams:list', camp_slug=self.get_object().camp.slug)
class TeamMemberRemoveView(LoginRequiredMixin, CampViewMixin, EnsureTeamMemberResponsibleMixin, UpdateView):
template_name = "teammember_remove.html"
model = TeamMember
fields = []
def form_valid(self, form):
form.instance.delete()
if add_removed_membership_email(form.instance):
messages.success(self.request, "Team member removed")
else:
messages.success(self.request, "Team member removed (unable to add email to outgoing queue).")
logger.error(
'Unable to add removed email to outgoing queue for teammember: {}'.format(form.instance)
)
return redirect('teams:general', camp_slug=self.camp.slug, team_slug=form.instance.team.slug)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['team'] = self.get_object().team
return context
class TeamMemberApproveView(LoginRequiredMixin, CampViewMixin, EnsureTeamMemberResponsibleMixin, UpdateView):
template_name = "teammember_approve.html"
model = TeamMember
fields = []
def form_valid(self, form):
form.instance.approved = True
form.instance.save()
if add_added_membership_email(form.instance):
messages.success(self.request, "Team member approved")
else:
messages.success(self.request, "Team member removed (unable to add email to outgoing queue).")
logger.error(
'Unable to add approved email to outgoing queue for teammember: {}'.format(form.instance)
)
return redirect('teams:general', camp_slug=self.camp.slug, team_slug=form.instance.team.slug)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['team'] = self.get_object().team
return context

View file

@ -13,7 +13,7 @@ class EnsureTeamResponsibleMixin(object):
self.team = Team.objects.get(slug=kwargs['team_slug'], camp=self.camp)
if request.user not in self.team.responsible_members.all():
messages.error(request, 'No thanks')
return redirect('teams:detail', camp_slug=self.camp.slug, team_slug=self.team.slug)
return redirect('teams:general', camp_slug=self.camp.slug, team_slug=self.team.slug)
return super().dispatch(
request, *args, **kwargs
@ -29,7 +29,7 @@ class EnsureTeamMemberResponsibleMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs):
if request.user not in self.get_object().team.responsible_members.all():
messages.error(request, 'No thanks')
return redirect('teams:detail', camp_slug=self.get_object().team.camp.slug, team_slug=self.get_object().team.slug)
return redirect('teams:general', camp_slug=self.get_object().team.camp.slug, team_slug=self.get_object().team.slug)
return super().dispatch(
request, *args, **kwargs

View file

@ -3,15 +3,27 @@ from django.http import HttpResponseRedirect
from django.views.generic import DetailView, CreateView, UpdateView
from camps.mixins import CampViewMixin
from ..models import TeamTask
from ..models import Team, TeamTask
from .mixins import EnsureTeamResponsibleMixin
class TeamTasksView(CampViewMixin, DetailView):
template_name = "team_tasks.html"
context_object_name = 'team'
model = Team
slug_url_kwarg = 'team_slug'
class TaskDetailView(CampViewMixin, DetailView):
template_name = "task_detail.html"
context_object_name = "task"
model = TeamTask
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['team'] = self.object.team
return context
class TaskCreateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibleMixin, CreateView):
model = TeamTask

View file

@ -58,7 +58,7 @@
</a>
{% endif %}
</div>
<div id="navbar" class="navbar-collapse collapse">
<div id="top-navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a href="{% url 'news:index' %}">News</a></li>
<li><a href="{% url 'shop:index' %}">Shop</a></li>