Merge pull request #257 from bornhack/feature/team_refactor

Team site refactor
This commit is contained in:
Víðir Valberg Guðmundsson 2018-08-05 11:18:03 +02:00 committed by GitHub
commit 2ad0568f64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 615 additions and 442 deletions

View file

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

View file

@ -111,7 +111,7 @@ class Team(CampRelatedModel):
return '{} ({})'.format(self.name, self.camp) return '{} ({})'.format(self.name, self.camp)
def get_absolute_url(self): 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): def save(self, **kwargs):
# generate slug if needed # generate slug if needed
@ -216,6 +216,7 @@ class Team(CampRelatedModel):
class TeamMember(CampRelatedModel): class TeamMember(CampRelatedModel):
user = models.ForeignKey( user = models.ForeignKey(
'auth.User', 'auth.User',
on_delete=models.PROTECT, on_delete=models.PROTECT,

View file

@ -13,6 +13,6 @@ Fix IRC permissions for NickServ user {{ request.user.profile.nickserv_username
{% csrf_token %} {% csrf_token %}
{{ form }} {{ form }}
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Yes Please</button> <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> </form>
{% endblock %} {% endblock %}

View file

@ -1,11 +1,11 @@
{% extends 'base.html' %} {% extends 'team_base.html' %}
{% load commonmark %} {% load commonmark %}
{% block title %} {% block title %}
{{ task.name }} {{ task.name }}
{% endblock %} {% endblock %}
{% block content %} {% block team_content %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><h4>Task: {{ task.name }} ({% if not task.completed %}Not {% endif %}Completed)</h4></div> <div class="panel-heading"><h4>Task: {{ task.name }} ({% if not task.completed %}Not {% endif %}Completed)</h4></div>
<div class="panel-body"> <div class="panel-body">

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %} {% extends 'team_base.html' %}
{% load commonmark %} {% load commonmark %}
{% load bootstrap3 %} {% load bootstrap3 %}
@ -11,7 +11,7 @@ Create Task
for {{ team.name }} Team for {{ team.name }} Team
{% endblock %} {% endblock %}
{% block content %} {% block team_content %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4> <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> <button type="submit" class="btn btn-primary">{% if form.instance.id %}Save{% else %}Create{% endif %}</button>
</form> </form>
</div> </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> </div>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,77 @@
{% 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.active_menu == "general" %}class="active"{% endif %}>
<a href="{% url "teams:general" camp_slug=team.camp.slug team_slug=team.slug %}">
General
</a>
</li>
<li {% if view.active_menu == "members" %}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>
<li {% if view.active_menu == "tasks" %}class="active"{% endif %}>
<a href="{% url "teams:tasks" camp_slug=team.camp.slug team_slug=team.slug %}">
Tasks
</a>
</li>
{% if request.user in team.responsible_members.all %}
<li {% if view.active_menu == "info_categories" %}class="active"{% endif %}>
<a href="{% url "teams:info_categories" camp_slug=team.camp.slug team_slug=team.slug %}">
Info categories
</a>
</li>
{% endif %}
</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,155 +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>
{% include 'includes/team_tasks.html' %}
{# 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,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>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,4 +1,4 @@
{% extends 'base.html' %} {% extends 'team_base.html' %}
{% load commonmark %} {% load commonmark %}
{% load bootstrap3 %} {% load bootstrap3 %}
@ -11,7 +11,7 @@ Create Info item
in {{ form.instance.category.headline }} in {{ form.instance.category.headline }}
{% endblock %} {% endblock %}
{% block content %} {% block team_content %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4> <h4>

View file

@ -1,26 +1,28 @@
{% extends 'base.html' %} {% extends 'team_base.html' %}
{% load commonmark %} {% load commonmark %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% block title %} {% block title %}
{% if object %} {% if object %}
Editing "{{ object.headline }}" Editing "{{ object.headline }}"
in "{{ form.instance.category.headline }}"
{% else %} {% else %}
Create Info item Create Info item
in "{{ category.headline }}"
{% endif %} {% endif %}
in "{{ form.instance.category.headline }}"
{% endblock %} {% endblock %}
{% block content %} {% block team_content %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4> <h4>
{% if object %} {% if object %}
Editing "{{ object.headline }}" Editing "{{ object.headline }}"
in "{{ object.category.headline }}"
{% else %} {% else %}
Create Info Item Create Info Item
in "{{ category.headline }}"
{% endif %} {% endif %}
in "{{ object.category.headline }}"
</h4> </h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
@ -36,6 +38,6 @@ in "{{ form.instance.category.headline }}"
{% endif %} {% endif %}
</form> </form>
</div> </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> </div>
{% endblock %} {% endblock %}

View file

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

View file

@ -33,7 +33,7 @@ Teams | {{ block.super }}
{% for team in teams %} {% for team in teams %}
<tr> <tr>
<td> <td>
<a href="{% url 'teams:detail' camp_slug=camp.slug team_slug=team.slug %}"> <a href="{% url 'teams:general' camp_slug=camp.slug team_slug=team.slug %}">
{{ team.name }} Team {{ team.name }} Team
</a> </a>
</td> </td>
@ -63,7 +63,7 @@ Teams | {{ block.super }}
<td> <td>
<div class="btn-group-vertical"> <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 %} {% 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> <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 %} {% endif %}

View file

@ -1,4 +1,4 @@
{% extends 'base.html' %} {% extends 'team_base.html' %}
{% load commonmark %} {% load commonmark %}
{% load bootstrap3 %} {% load bootstrap3 %}
@ -6,7 +6,7 @@
Manage Team: {{ team.name }} | {{ block.super }} Manage Team: {{ team.name }} | {{ block.super }}
{% endblock %} {% endblock %}
{% block content %} {% block team_content %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"><h4>Manage {{ team.name }} Team</h4></div> <div class="panel-heading"><h4>Manage {{ team.name }} Team</h4></div>
<div class="panel-body" style="margin-left: 1em; margin-right: 1em;"> <div class="panel-body" style="margin-left: 1em; margin-right: 1em;">
@ -18,85 +18,11 @@ Manage Team: {{ team.name }} | {{ block.super }}
{% buttons %} {% buttons %}
<button class="btn btn-success pull-right" type="submit"><i class="fas fa-check"></i> Save Team</button> <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 %} {% endbuttons %}
</form> </form>
</div> </div>
</div> </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>
{% include 'includes/team_tasks.html' %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,86 @@
{% 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 member in team.memberships.all %}
{% if member.approved or not member.approved and request.user in team.responsible_members.all %}
<tr>
<td>
{{ member.user.profile.get_public_credit_name }} {% if member.user == request.user %}(this is you!){% endif %}
</td>
<td>
Team {% if member.responsible %}Responsible{% else %}Member{% endif %}
{% if not member.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:member_remove' camp_slug=camp.slug team_slug=team.slug pk=member.id %}">
<i class="fas fa-trash"></i> Remove
</a>
{% if not member.approved %}
<a class="btn btn-success"
href="{% url 'teams:member_approve' camp_slug=camp.slug team_slug=team.slug pk=member.id %}">
<i class="fas fa-check"></i> Approve
</a>
{% endif %}
</div>
</td>
{% endif %}
</tr>
{% endif %}
{% 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

@ -1,3 +1,10 @@
{% extends 'team_base.html' %}
{% load commonmark %}
{% load bootstrap3 %}
{% load teams_tags %}
{% block team_content %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<h4>Tasks</h4> <h4>Tasks</h4>
@ -10,7 +17,7 @@
<th>Name</th> <th>Name</th>
<th>Description</th> <th>Description</th>
<th>When</th> <th>When</th>
<th>Completed</th> <th>Completed?</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
@ -41,4 +48,4 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endblock %}

View file

@ -1,11 +1,11 @@
{% extends 'base.html' %} {% extends 'team_base.html' %}
{% load commonmark %} {% load commonmark %}
{% block title %} {% block title %}
Approve team member {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team Approve team member {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team
{% endblock %} {% endblock %}
{% block content %} {% block team_content %}
<h3>Approve member {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team</h3> <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> <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 %} {% csrf_token %}
{{ form }} {{ form }}
<button class="btn btn-success" type="submit"><i class="fas fa-check"></i> Add teammember</button> <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> </form>
{% endblock %} {% endblock %}

View file

@ -1,11 +1,11 @@
{% extends 'base.html' %} {% extends 'team_base.html' %}
{% load commonmark %} {% load commonmark %}
{% block title %} {% block title %}
Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team
{% endblock %} {% endblock %}
{% block content %} {% block team_content %}
<h3>Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team</h3> <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> <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 %} {% csrf_token %}
{{ form }} {{ form }}
<button class="btn btn-danger" type="submit"><i class="fas fa-trash-o"></i> Remove teammember</button> <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> </form>
{% endblock %} {% endblock %}

View file

@ -2,17 +2,28 @@ from django.urls import path, include
from teams.views.base import ( from teams.views.base import (
TeamListView, TeamListView,
TeamMemberRemoveView, TeamGeneralView,
TeamMemberApproveView,
TeamDetailView,
TeamJoinView,
TeamLeaveView,
TeamManageView, TeamManageView,
FixIrcAclView, FixIrcAclView,
) )
from teams.views.info import InfoItemUpdateView, InfoItemCreateView, InfoItemDeleteView
from teams.views.members import (
TeamMembersView,
TeamMemberRemoveView,
TeamMemberApproveView,
TeamJoinView,
TeamLeaveView,
)
from teams.views.info import (
InfoCategoriesListView,
InfoItemUpdateView,
InfoItemCreateView,
InfoItemDeleteView,
)
from teams.views.tasks import ( from teams.views.tasks import (
TeamTasksView,
TaskCreateView, TaskCreateView,
TaskDetailView, TaskDetailView,
TaskUpdateView, TaskUpdateView,
@ -26,26 +37,12 @@ urlpatterns = [
TeamListView.as_view(), TeamListView.as_view(),
name='list' 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( path(
'<slug:team_slug>/', include([ '<slug:team_slug>/', include([
path( path(
'', '',
TeamDetailView.as_view(), TeamGeneralView.as_view(),
name='detail' name='general'
), ),
path( path(
'join/', 'join/',
@ -67,8 +64,32 @@ urlpatterns = [
FixIrcAclView.as_view(), FixIrcAclView.as_view(),
name='fix_irc_acl', name='fix_irc_acl',
), ),
path(
'members/', include([
path(
'',
TeamMembersView.as_view(),
name='members'
),
path(
'<int:pk>/remove/',
TeamMemberRemoveView.as_view(),
name='member_remove',
),
path(
'<int:pk>/approve/',
TeamMemberApproveView.as_view(),
name='member_approve',
),
]),
),
path( path(
'tasks/', include([ 'tasks/', include([
path(
'',
TeamTasksView.as_view(),
name='tasks',
),
path( path(
'create/', 'create/',
TaskCreateView.as_view(), TaskCreateView.as_view(),
@ -92,26 +113,36 @@ urlpatterns = [
]), ]),
), ),
path( path(
'info/<slug:category_anchor>/', include([ 'info/',
include([
path( path(
'create/', '',
InfoItemCreateView.as_view(), InfoCategoriesListView.as_view(),
name='info_item_create', name='info_categories'
), ),
path( path(
'<slug:item_anchor>/', include([ '<slug:category_anchor>/', include([
path( path(
'update/', 'create/',
InfoItemUpdateView.as_view(), InfoItemCreateView.as_view(),
name='info_item_update', name='info_item_create',
), ),
path( path(
'delete/', '<slug:item_anchor>/', include([
InfoItemDeleteView.as_view(), path(
name='info_item_delete', 'update/',
InfoItemUpdateView.as_view(),
name='info_item_update',
),
path(
'delete/',
InfoItemDeleteView.as_view(),
name='info_item_delete',
),
]),
), ),
]), ])
), )
]) ])
) )
]), ]),

View file

@ -7,11 +7,8 @@ from django.contrib import messages
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.conf import settings from django.conf import settings
from profiles.models import Profile from .mixins import EnsureTeamResponsibleMixin
from .mixins import EnsureTeamResponsibleMixin, EnsureTeamMemberResponsibleMixin
from ..models import Team, TeamMember from ..models import Team, TeamMember
from ..email import add_added_membership_email, add_removed_membership_email
import logging import logging
logger = logging.getLogger("bornhack.%s" % __name__) logger = logging.getLogger("bornhack.%s" % __name__)
@ -23,14 +20,15 @@ class TeamListView(CampViewMixin, ListView):
context_object_name = 'teams' context_object_name = 'teams'
class TeamDetailView(CampViewMixin, DetailView): class TeamGeneralView(CampViewMixin, DetailView):
template_name = "team_detail.html" template_name = "team_general.html"
context_object_name = 'team' context_object_name = 'team'
model = Team model = Team
slug_url_kwarg = 'team_slug' slug_url_kwarg = 'team_slug'
active_menu = 'general'
def get_context_data(self, **kwargs): 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_SERVER_HOSTNAME'] = settings.IRCBOT_SERVER_HOSTNAME
context['IRCBOT_PUBLIC_CHANNEL'] = settings.IRCBOT_PUBLIC_CHANNEL context['IRCBOT_PUBLIC_CHANNEL'] = settings.IRCBOT_PUBLIC_CHANNEL
return context return context
@ -39,102 +37,20 @@ class TeamDetailView(CampViewMixin, DetailView):
class TeamManageView(CampViewMixin, EnsureTeamResponsibleMixin, UpdateView): class TeamManageView(CampViewMixin, EnsureTeamResponsibleMixin, UpdateView):
model = Team model = Team
template_name = "team_manage.html" 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' slug_url_kwarg = 'team_slug'
def get_success_url(self): 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): def form_valid(self, form):
messages.success(self.request, "Team has been saved") messages.success(self.request, "Team has been saved")
return super().form_valid(form) 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): class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "fix_irc_acl.html" template_name = "fix_irc_acl.html"
model = Team model = Team
@ -151,17 +67,17 @@ class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
# check if the logged in user has an approved membership of this team # check if the logged in user has an approved membership of this team
if request.user not in self.get_object().approved_members.all(): if request.user not in self.get_object().approved_members.all():
messages.error(request, 'No thanks') 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 # check if we manage the channel for this team
if not self.get_object().irc_channel or not self.get_object().irc_channel_managed: 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') 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 # check if user has a nickserv username
if not request.user.profile.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!') 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 return response
@ -177,7 +93,7 @@ class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
except TeamMember.DoesNotExist: except TeamMember.DoesNotExist:
# this membership is already marked as membership.irc_channel_acl_ok=False, no need to do anything # 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') 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( return super().get(
request, *args, **kwargs request, *args, **kwargs
@ -195,5 +111,5 @@ class FixIrcAclView(LoginRequiredMixin, CampViewMixin, UpdateView):
membership.irc_channel_acl_ok = False membership.irc_channel_acl_ok = False
membership.save() 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)) 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)

View file

@ -1,23 +1,39 @@
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic import CreateView, UpdateView, DeleteView from django.views.generic import CreateView, UpdateView, DeleteView, ListView
from reversion.views import RevisionMixin from reversion.views import RevisionMixin
from camps.mixins import CampViewMixin from camps.mixins import CampViewMixin
from info.models import InfoItem, InfoCategory from info.models import InfoItem, InfoCategory
from teams.views.mixins import EnsureTeamResponsibleMixin from ..models import Team
from .mixins import EnsureTeamResponsibleMixin, TeamViewMixin
class InfoItemCreateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibleMixin, CreateView): class InfoCategoriesListView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, ListView):
model = InfoCategory
template_name = "team_info_categories.html"
slug_field = 'anchor'
active_menu = 'info_categories'
def get_team(self):
return Team.objects.get(
camp__slug=self.kwargs['camp_slug'],
slug=self.kwargs['team_slug']
)
class InfoItemCreateView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, CreateView):
model = InfoItem model = InfoItem
template_name = "info_item_form.html" template_name = "team_info_item_form.html"
fields = ['headline', 'body', 'anchor', 'weight'] fields = ['headline', 'body', 'anchor', 'weight']
slug_field = 'anchor' slug_field = 'anchor'
active_menu = 'info_categories'
def get_context_data(self, *args, **kwargs): def get_team(self):
context = super().get_context_data(**kwargs) return Team.objects.get(
context['team'] = self.team camp__slug=self.kwargs['camp_slug'],
return context slug=self.kwargs['team_slug']
)
def form_valid(self, form): def form_valid(self, form):
info_item = form.save(commit=False) info_item = form.save(commit=False)
@ -29,18 +45,28 @@ class InfoItemCreateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibl
def get_success_url(self): def get_success_url(self):
return self.team.get_absolute_url() return self.team.get_absolute_url()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['category'] = InfoCategory.objects.get(
team__camp__slug=self.kwargs['camp_slug'],
anchor=self.kwargs['category_anchor']
)
return context
class InfoItemUpdateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibleMixin, RevisionMixin, UpdateView):
class InfoItemUpdateView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, RevisionMixin, UpdateView):
model = InfoItem model = InfoItem
template_name = "info_item_form.html" template_name = "team_info_item_form.html"
fields = ['headline', 'body', 'anchor', 'weight'] fields = ['headline', 'body', 'anchor', 'weight']
slug_field = 'anchor' slug_field = 'anchor'
slug_url_kwarg = 'item_anchor' slug_url_kwarg = 'item_anchor'
active_menu = 'info_categories'
def get_context_data(self, *args, **kwargs): def get_team(self):
context = super().get_context_data(**kwargs) return Team.objects.get(
context['team'] = self.team camp__slug=self.kwargs['camp_slug'],
return context slug=self.kwargs['team_slug']
)
def get_success_url(self): def get_success_url(self):
next = self.request.GET.get('next') next = self.request.GET.get('next')
@ -49,11 +75,18 @@ class InfoItemUpdateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibl
return self.team.get_absolute_url() return self.team.get_absolute_url()
class InfoItemDeleteView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibleMixin, RevisionMixin, DeleteView): class InfoItemDeleteView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, RevisionMixin, DeleteView):
model = InfoItem model = InfoItem
template_name = "info_item_delete_confirm.html" template_name = "team_info_item_delete_confirm.html"
slug_field = 'anchor' slug_field = 'anchor'
slug_url_kwarg = 'item_anchor' slug_url_kwarg = 'item_anchor'
active_menu = 'info_categories'
def get_team(self):
return Team.objects.get(
camp__slug=self.kwargs['camp_slug'],
slug=self.kwargs['team_slug']
)
def get_success_url(self): def get_success_url(self):
next = self.request.GET.get('next') next = self.request.GET.get('next')

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

@ -0,0 +1,111 @@
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, TeamViewMixin
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'
active_menu = 'members'
class TeamJoinView(LoginRequiredMixin, CampViewMixin, UpdateView):
template_name = "team_join.html"
model = Team
fields = []
slug_url_kwarg = 'team_slug'
active_menu = 'members'
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'
active_menu = 'members'
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, TeamViewMixin, EnsureTeamMemberResponsibleMixin, UpdateView):
template_name = "teammember_remove.html"
model = TeamMember
fields = []
active_menu = 'members'
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)
class TeamMemberApproveView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamMemberResponsibleMixin, UpdateView):
template_name = "teammember_approve.html"
model = TeamMember
fields = []
active_menu = 'members'
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)

View file

@ -13,7 +13,7 @@ class EnsureTeamResponsibleMixin(object):
self.team = Team.objects.get(slug=kwargs['team_slug'], camp=self.camp) self.team = Team.objects.get(slug=kwargs['team_slug'], camp=self.camp)
if request.user not in self.team.responsible_members.all(): if request.user not in self.team.responsible_members.all():
messages.error(request, 'No thanks') 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( return super().dispatch(
request, *args, **kwargs request, *args, **kwargs
@ -29,8 +29,19 @@ class EnsureTeamMemberResponsibleMixin(SingleObjectMixin):
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
if request.user not in self.get_object().team.responsible_members.all(): if request.user not in self.get_object().team.responsible_members.all():
messages.error(request, 'No thanks') 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( return super().dispatch(
request, *args, **kwargs request, *args, **kwargs
) )
class TeamViewMixin:
def get_team(self):
return self.get_object().team
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs)
context['team'] = self.get_team()
return context

View file

@ -1,44 +1,74 @@
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.views.generic import DetailView, CreateView, UpdateView from django.views.generic import DetailView, CreateView, UpdateView
from django import forms
from camps.mixins import CampViewMixin from camps.mixins import CampViewMixin
from ..models import TeamTask from ..models import Team, TeamTask
from .mixins import EnsureTeamResponsibleMixin from .mixins import EnsureTeamResponsibleMixin, TeamViewMixin
class TaskDetailView(CampViewMixin, DetailView): class TeamTasksView(CampViewMixin, DetailView):
template_name = "team_tasks.html"
context_object_name = 'team'
model = Team
slug_url_kwarg = 'team_slug'
active_menu = 'tasks'
class TaskDetailView(CampViewMixin, TeamViewMixin, DetailView):
template_name = "task_detail.html" template_name = "task_detail.html"
context_object_name = "task" context_object_name = "task"
model = TeamTask model = TeamTask
active_menu = 'tasks'
class TaskCreateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibleMixin, CreateView): class TaskForm(forms.ModelForm):
class Meta:
model = TeamTask
fields = ['name', 'description', 'when', 'completed']
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.fields['when'].widget.widgets = [
forms.DateTimeInput(
attrs={"placeholder": "Start"}
),
forms.DateTimeInput(
attrs={"placeholder": "End"}
)
]
class TaskCreateView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, CreateView):
model = TeamTask model = TeamTask
template_name = "task_form.html" template_name = "task_form.html"
fields = ['name', 'description', 'when', 'completed'] form_class = TaskForm
active_menu = 'tasks'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs) def get_team(self):
context['team'] = self.team return Team.objects.get(
return context camp__slug=self.kwargs['camp_slug'],
slug=self.kwargs['team_slug']
def form_valid(self, form): )
task = form.save(commit=False)
task.team = self.team def form_valid(self, form):
if not task.name: task = form.save(commit=False)
task.name = "noname" task.team = self.team
task.save() if not task.name:
return HttpResponseRedirect(task.get_absolute_url()) task.name = "noname"
task.save()
def get_success_url(self): return HttpResponseRedirect(task.get_absolute_url())
return self.get_object().get_absolute_url()
def get_success_url(self):
return self.get_object().get_absolute_url()
class TaskUpdateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibleMixin, UpdateView):
model = TeamTask
template_name = "task_form.html" class TaskUpdateView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, UpdateView):
fields = ['name', 'description', 'when', 'completed'] model = TeamTask
template_name = "task_form.html"
form_class = TaskForm
active_menu = 'tasks'
def get_context_data(self, *args, **kwargs): def get_context_data(self, *args, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)

View file

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