diff --git a/src/static_src/css/bornhack.css b/src/static_src/css/bornhack.css index f19fc65b..d21c6120 100644 --- a/src/static_src/css/bornhack.css +++ b/src/static_src/css/bornhack.css @@ -62,7 +62,7 @@ a, a:active, a:focus { margin-top: 6px; } -.nav li a { +#top-navbar > .nav li a { padding: 30px 7px; } diff --git a/src/teams/models.py b/src/teams/models.py index e51320b5..83d9a043 100644 --- a/src/teams/models.py +++ b/src/teams/models.py @@ -111,7 +111,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 @@ -216,6 +216,7 @@ class Team(CampRelatedModel): class TeamMember(CampRelatedModel): + user = models.ForeignKey( 'auth.User', on_delete=models.PROTECT, diff --git a/src/teams/templates/fix_irc_acl.html b/src/teams/templates/fix_irc_acl.html index 4ee75272..8a8887e6 100644 --- a/src/teams/templates/fix_irc_acl.html +++ b/src/teams/templates/fix_irc_acl.html @@ -13,6 +13,6 @@ Fix IRC permissions for NickServ user {{ request.user.profile.nickserv_username {% csrf_token %} {{ form }} - Cancel + Cancel {% endblock %} diff --git a/src/teams/templates/task_detail.html b/src/teams/templates/task_detail.html index d919d3d0..f2938c71 100644 --- a/src/teams/templates/task_detail.html +++ b/src/teams/templates/task_detail.html @@ -1,11 +1,11 @@ -{% extends 'base.html' %} +{% extends 'team_base.html' %} {% load commonmark %} {% block title %} {{ task.name }} {% endblock %} -{% block content %} +{% block team_content %}

Task: {{ task.name }} ({% if not task.completed %}Not {% endif %}Completed)

diff --git a/src/teams/templates/task_form.html b/src/teams/templates/task_form.html index c03e64c6..69d7e603 100644 --- a/src/teams/templates/task_form.html +++ b/src/teams/templates/task_form.html @@ -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 %}

@@ -30,7 +30,7 @@ for {{ team.name }} Team

- +
{% endblock %} diff --git a/src/teams/templates/team_base.html b/src/teams/templates/team_base.html new file mode 100644 index 00000000..8ef87216 --- /dev/null +++ b/src/teams/templates/team_base.html @@ -0,0 +1,77 @@ +{% extends 'base.html' %} +{% load commonmark %} +{% load bootstrap3 %} +{% load teams_tags %} + +{% block title %} +Team: {{ team.name }} | {{ block.super }} +{% endblock %} + +{% block content %} + + + +
+
+ + +
+ + {% if request.user.is_authenticated %} + + {% if request.user in team.members.all %} +

Your membership status: {% membershipstatus user team %}

+ + {% if request.user in team.responsible_members.all %} + Manage Team + {% endif %} + + {% else %} + {% if team.needs_members %} + This team is looking for members! Join Team + {% endif %} + {% endif %} + + {% endif %} +
+ +
+ + {% block team_content %}{% endblock %} + +
+
+ +{% endblock %} diff --git a/src/teams/templates/team_detail.html b/src/teams/templates/team_detail.html deleted file mode 100644 index 2caa8544..00000000 --- a/src/teams/templates/team_detail.html +++ /dev/null @@ -1,155 +0,0 @@ -{% extends 'base.html' %} -{% load commonmark %} -{% load bootstrap3 %} -{% load teams_tags %} - -{% block title %} -Team: {{ team.name }} | {{ block.super }} -{% endblock %} - -{% block content %} - - - -
-
-

Description

-
-
- {{ team.description|untrustedcommonmark }} -
-
- -{# Team communications #} -
-
-

Communication Channels

-
-
- {{ team.camp.title }} teams primarily use mailing lists and IRC to communicate. The {{ team.name }} team can be contacted in the following ways:

- -
Mailing List
- {% if team.mailing_list and request.user in team.approved_members.all %} -

The {{ team.name }} Team mailinglist is {{ team.mailing_list }}{% 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.

- {% elif team.mailing_list and team.mailinglist_nonmember_posts %} -

The {{ team.name }} Team mailinglist is {{ team.mailing_list }}{% 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.

- {% else %} -

The {{ team.name }} Team does not have a public mailing list, but it can be contacted through our main email info@bornhack.dk. - {% endif %} - -

IRC Channel
- {% if team.public_irc_channel_name %} -

The {{ team.name }} Team public IRC channel is {{ team.public_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}. - {% else %} -

The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel {{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}.

- {% endif %} - - {% if request.user in team.approved_members.all and team.private_irc_channel_name %} -

The {{ team.name }} Team private IRC channel is {{ team.private_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}.

- {% endif %} - -
-
- -{% include 'includes/team_tasks.html' %} - -{# Team members #} -
-
-

Members

-
-
-

The following {{ team.approved_members.count }} people {% if team.unapproved_members.count %}(and {{ team.unapproved_members.count }} pending){% endif %} are members of the {{ team.name }} Team:

- - - - - - - - - {% for teammember in team.memberships.all %} - - - - - {% endfor %} - -
- Name - - Status -
- {{ teammember.user.profile.get_public_credit_name }} {% if teammember.user == request.user %}(this is you!){% endif %} - - Team {% if teammember.responsible %}Responsible{% else %}Member{% endif %} - {% if not teammember.approved %}(pending approval){% endif %} -
- -

Your membership status: {% membershipstatus user team %}

- - {% if request.user in team.members.all %} - {% if team.irc_channel and team.irc_channel_managed and request.user.profile.nickserv_username %} - Fix IRC ACL  - {% endif %} - Leave Team - {% else %} - {% if team.needs_members %} - This team is looking for members! Join Team - {% endif %} - {% endif %} - - {% if request.user in team.responsible_members.all %} - Manage Team - {% endif %} - -
-
-
- -{# Team info categories section - only visible for team responsible #} -{% if request.user in team.responsible_members.all and team.info_categories.exists %} -
-
-

Info Categories

-
-
- - {% for info_category in team.info_categories.all %} - -

{{ info_category.headline }}

- - - - - - - - - {% for item in info_category.infoitems.all %} - - - - - {% endfor %} - -
Item nameAction
{{ item.headline }} - - Edit - -
- - Create Info Item - -
- - {% endfor %} -
-
- -{% endif %} - -{% endblock %} diff --git a/src/teams/templates/team_general.html b/src/teams/templates/team_general.html new file mode 100644 index 00000000..2c5bfc91 --- /dev/null +++ b/src/teams/templates/team_general.html @@ -0,0 +1,48 @@ +{% extends 'team_base.html' %} +{% load commonmark %} +{% load bootstrap3 %} +{% load teams_tags %} + + +{% block team_content %} + +
+
+

Description

+
+
+ {{ team.description|untrustedcommonmark }} +
+
+ +{# Team communications #} +
+
+

Communication Channels

+
+
+ {{ team.camp.title }} teams primarily use mailing lists and IRC to communicate. The {{ team.name }} team can be contacted in the following ways:

+ +
Mailing List
+ {% if team.mailing_list and request.user in team.approved_members.all %} +

The {{ team.name }} Team mailinglist is {{ team.mailing_list }}{% 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.

+ {% elif team.mailing_list and team.mailinglist_nonmember_posts %} +

The {{ team.name }} Team mailinglist is {{ team.mailing_list }}{% 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.

+ {% else %} +

The {{ team.name }} Team does not have a public mailing list, but it can be contacted through our main email info@bornhack.dk. + {% endif %} + +

IRC Channel
+ {% if team.public_irc_channel_name %} +

The {{ team.name }} Team public IRC channel is {{ team.public_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}. + {% else %} +

The {{ team.name }} Team does not have a public IRC channel, but it can be reached through our main IRC channel {{ IRCBOT_PUBLIC_CHANNEL }} on {{ IRCBOT_SERVER_HOSTNAME }}.

+ {% endif %} + + {% if request.user in team.approved_members.all and team.private_irc_channel_name %} +

The {{ team.name }} Team private IRC channel is {{ team.private_irc_channel_name }} on {{ IRCBOT_SERVER_HOSTNAME }}.

+ {% endif %} + +
+
+{% endblock %} diff --git a/src/teams/templates/team_info_categories.html b/src/teams/templates/team_info_categories.html new file mode 100644 index 00000000..15f0900c --- /dev/null +++ b/src/teams/templates/team_info_categories.html @@ -0,0 +1,48 @@ +{% extends 'team_base.html' %} +{% load commonmark %} +{% load bootstrap3 %} +{% load teams_tags %} + + +{% block team_content %} + +
+
+

Info Categories

+
+
+ + {% for info_category in team.info_categories.all %} + +

{{ info_category.headline }}

+ + + + + + + + + {% for item in info_category.infoitems.all %} + + + + + {% endfor %} + +
Item nameAction
{{ item.headline }} + + Edit + +
+ + Create Info Item + +
+ + {% endfor %} +
+
+ +{% endblock %} diff --git a/src/teams/templates/info_item_delete_confirm.html b/src/teams/templates/team_info_item_delete_confirm.html similarity index 93% rename from src/teams/templates/info_item_delete_confirm.html rename to src/teams/templates/team_info_item_delete_confirm.html index fe9f2403..18e6e28b 100644 --- a/src/teams/templates/info_item_delete_confirm.html +++ b/src/teams/templates/team_info_item_delete_confirm.html @@ -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 %}

diff --git a/src/teams/templates/info_item_form.html b/src/teams/templates/team_info_item_form.html similarity index 83% rename from src/teams/templates/info_item_form.html rename to src/teams/templates/team_info_item_form.html index 03d56ddf..e062ae9d 100644 --- a/src/teams/templates/info_item_form.html +++ b/src/teams/templates/team_info_item_form.html @@ -1,26 +1,28 @@ -{% extends 'base.html' %} +{% extends 'team_base.html' %} {% load commonmark %} {% load bootstrap3 %} {% block title %} {% if object %} Editing "{{ object.headline }}" +in "{{ form.instance.category.headline }}" {% else %} Create Info item +in "{{ category.headline }}" {% endif %} -in "{{ form.instance.category.headline }}" {% endblock %} -{% block content %} +{% block team_content %}

{% if object %} Editing "{{ object.headline }}" + in "{{ object.category.headline }}" {% else %} Create Info Item + in "{{ category.headline }}" {% endif %} - in "{{ object.category.headline }}"

@@ -36,6 +38,6 @@ in "{{ form.instance.category.headline }}" {% endif %}
- +
{% endblock %} diff --git a/src/teams/templates/team_join.html b/src/teams/templates/team_join.html index 5b83f7e6..f231b7a3 100644 --- a/src/teams/templates/team_join.html +++ b/src/teams/templates/team_join.html @@ -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 %}

Really join the {{ team.name }} Team for {{ team.camp.title }}?

diff --git a/src/teams/templates/team_leave.html b/src/teams/templates/team_leave.html index 8cbc37e1..7e345e3d 100644 --- a/src/teams/templates/team_leave.html +++ b/src/teams/templates/team_leave.html @@ -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 %}

Leave {{ team.name }} Team

Really leave the {{ team.name }} team?

diff --git a/src/teams/templates/team_list.html b/src/teams/templates/team_list.html index d09f8217..c0a314da 100644 --- a/src/teams/templates/team_list.html +++ b/src/teams/templates/team_list.html @@ -33,7 +33,7 @@ Teams | {{ block.super }} {% for team in teams %} - + {{ team.name }} Team @@ -63,7 +63,7 @@ Teams | {{ block.super }}

- Details + Details {% if request.user in team.responsible_members.all %} Manage {% endif %} diff --git a/src/teams/templates/team_manage.html b/src/teams/templates/team_manage.html index 5e9b701b..643f95bc 100644 --- a/src/teams/templates/team_manage.html +++ b/src/teams/templates/team_manage.html @@ -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 %}

Manage {{ team.name }} Team

@@ -18,85 +18,11 @@ Manage Team: {{ team.name }} | {{ block.super }} {% buttons %} - Cancel  + Cancel  {% endbuttons %}
-
-

Manage {{ team.name }} Team Members

-
- {% if team.teammember_set.exists %} - - - - - - - - - - - - - - {% for membership in team.teammember_set.all %} - - - - - - - - - - {% endfor %} - -
- Username - - Name - - Email - - Description - - Public Credit Name - - Membership - - Action -
- {{ membership.user }} - - {{ membership.user.profile.name }} - - {{ membership.user.profile.email }} - - {{ membership.user.profile.description }} - - {{ 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 %}(name not approved){% endif %} - - {% if membership.approved %}member{% else %}pending{% endif %} - -
- Remove Member - {% if not membership.approved %} - Approve Member - {% endif %} -
-
- {% else %} -

No members found!

- {% endif %} -
-
- -{% include 'includes/team_tasks.html' %} - {% endblock %} - - diff --git a/src/teams/templates/team_members.html b/src/teams/templates/team_members.html new file mode 100644 index 00000000..b3fef15a --- /dev/null +++ b/src/teams/templates/team_members.html @@ -0,0 +1,86 @@ +{% extends 'team_base.html' %} +{% load commonmark %} +{% load bootstrap3 %} +{% load teams_tags %} + + +{% block team_content %} + +
+
+

Members

+
+
+

The following {{ team.approved_members.count }} people {% if team.unapproved_members.count %}(and {{ team.unapproved_members.count }} pending){% endif %} are members of the {{ team.name }} Team:

+ + + + + + {% if request.user in team.responsible_members.all %} + + {% endif %} + + + + {% for member in team.memberships.all %} + {% if member.approved or not member.approved and request.user in team.responsible_members.all %} + + + + {% if request.user in team.responsible_members.all %} + + {% endif %} + + {% endif %} + {% empty %} +

No members found!

+ {% endfor %} + +
+ Name + + Status + + Action +
+ {{ member.user.profile.get_public_credit_name }} {% if member.user == request.user %}(this is you!){% endif %} + + Team {% if member.responsible %}Responsible{% else %}Member{% endif %} + {% if not member.approved %}(pending approval){% endif %} + +
+ + Remove + + {% if not member.approved %} + + Approve + + {% endif %} +
+
+ + {% if request.user.authorized %} + +

Your membership status: {% membershipstatus user team %}

+ + {% if request.user in team.members.all %} + {% if team.irc_channel and team.irc_channel_managed and request.user.profile.nickserv_username %} + Fix IRC ACL  + {% endif %} + Leave Team + {% else %} + {% if team.needs_members %} + This team is looking for members! Join Team + {% endif %} + {% endif %} + {% endif %} + +
+
+
+ +{% endblock %} diff --git a/src/teams/templates/includes/team_tasks.html b/src/teams/templates/team_tasks.html similarity index 91% rename from src/teams/templates/includes/team_tasks.html rename to src/teams/templates/team_tasks.html index f285eef4..30acae88 100644 --- a/src/teams/templates/includes/team_tasks.html +++ b/src/teams/templates/team_tasks.html @@ -1,3 +1,10 @@ +{% extends 'team_base.html' %} +{% load commonmark %} +{% load bootstrap3 %} +{% load teams_tags %} + +{% block team_content %} +

Tasks

@@ -10,7 +17,7 @@ Name Description When - Completed + Completed? Action @@ -41,4 +48,4 @@ {% endif %}
- +{% endblock %} diff --git a/src/teams/templates/teammember_approve.html b/src/teams/templates/teammember_approve.html index 92afa84a..22ad3844 100644 --- a/src/teams/templates/teammember_approve.html +++ b/src/teams/templates/teammember_approve.html @@ -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 %}

Approve member {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team

Really approve the user {{ teammember.user.profile.name }} for the {{ teammember.team.name }} team? The user will receive an email with a message.

@@ -13,6 +13,7 @@ Approve team member {{ teammember.user.profile.name }} for the {{ teammember.tea {% csrf_token %} {{ form }} - Cancel + Cancel + {% endblock %} diff --git a/src/teams/templates/teammember_remove.html b/src/teams/templates/teammember_remove.html index 775e2577..222c9083 100644 --- a/src/teams/templates/teammember_remove.html +++ b/src/teams/templates/teammember_remove.html @@ -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 %}

Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team

Really remove the user {{ teammember.user.profile.name }} from the {{ teammember.team.name }} team? The user will receive an email with a message.

@@ -13,6 +13,6 @@ Remove member {{ teammember.user.profile.name }} from the {{ teammember.team.nam {% csrf_token %} {{ form }} - Cancel + Cancel {% endblock %} diff --git a/src/teams/urls.py b/src/teams/urls.py index 330adfb9..b4def544 100644 --- a/src/teams/urls.py +++ b/src/teams/urls.py @@ -2,17 +2,28 @@ 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 ( + InfoCategoriesListView, + InfoItemUpdateView, + InfoItemCreateView, + InfoItemDeleteView, +) from teams.views.tasks import ( + TeamTasksView, TaskCreateView, TaskDetailView, TaskUpdateView, @@ -26,26 +37,12 @@ urlpatterns = [ TeamListView.as_view(), name='list' ), - path( - 'members/', include([ - path( - '/remove/', - TeamMemberRemoveView.as_view(), - name='teammember_remove', - ), - path( - '/approve/', - TeamMemberApproveView.as_view(), - name='teammember_approve', - ), - ]), - ), path( '/', include([ path( '', - TeamDetailView.as_view(), - name='detail' + TeamGeneralView.as_view(), + name='general' ), path( 'join/', @@ -67,8 +64,32 @@ urlpatterns = [ FixIrcAclView.as_view(), name='fix_irc_acl', ), + path( + 'members/', include([ + path( + '', + TeamMembersView.as_view(), + name='members' + ), + path( + '/remove/', + TeamMemberRemoveView.as_view(), + name='member_remove', + ), + path( + '/approve/', + TeamMemberApproveView.as_view(), + name='member_approve', + ), + ]), + ), path( 'tasks/', include([ + path( + '', + TeamTasksView.as_view(), + name='tasks', + ), path( 'create/', TaskCreateView.as_view(), @@ -92,26 +113,36 @@ urlpatterns = [ ]), ), path( - 'info//', include([ + 'info/', + include([ path( - 'create/', - InfoItemCreateView.as_view(), - name='info_item_create', + '', + InfoCategoriesListView.as_view(), + name='info_categories' ), path( - '/', include([ + '/', include([ path( - 'update/', - InfoItemUpdateView.as_view(), - name='info_item_update', + 'create/', + InfoItemCreateView.as_view(), + name='info_item_create', ), path( - 'delete/', - InfoItemDeleteView.as_view(), - name='info_item_delete', + '/', include([ + path( + 'update/', + InfoItemUpdateView.as_view(), + name='info_item_update', + ), + path( + 'delete/', + InfoItemDeleteView.as_view(), + name='info_item_delete', + ), + ]), ), - ]), - ), + ]) + ) ]) ) ]), diff --git a/src/teams/views/base.py b/src/teams/views/base.py index 4ede5bd3..cce95e70 100644 --- a/src/teams/views/base.py +++ b/src/teams/views/base.py @@ -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,15 @@ 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' + active_menu = 'general' 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 +37,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 +67,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 +93,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 +111,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) diff --git a/src/teams/views/info.py b/src/teams/views/info.py index 5a6efaaa..0d49162d 100644 --- a/src/teams/views/info.py +++ b/src/teams/views/info.py @@ -1,23 +1,39 @@ from django.contrib.auth.mixins import LoginRequiredMixin 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 camps.mixins import CampViewMixin 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 - template_name = "info_item_form.html" + template_name = "team_info_item_form.html" fields = ['headline', 'body', 'anchor', 'weight'] slug_field = 'anchor' + active_menu = 'info_categories' - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(**kwargs) - context['team'] = self.team - return context + def get_team(self): + return Team.objects.get( + camp__slug=self.kwargs['camp_slug'], + slug=self.kwargs['team_slug'] + ) def form_valid(self, form): info_item = form.save(commit=False) @@ -29,18 +45,28 @@ class InfoItemCreateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibl def get_success_url(self): 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 - template_name = "info_item_form.html" + template_name = "team_info_item_form.html" fields = ['headline', 'body', 'anchor', 'weight'] slug_field = 'anchor' slug_url_kwarg = 'item_anchor' + active_menu = 'info_categories' - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(**kwargs) - context['team'] = self.team - return context + def get_team(self): + return Team.objects.get( + camp__slug=self.kwargs['camp_slug'], + slug=self.kwargs['team_slug'] + ) def get_success_url(self): next = self.request.GET.get('next') @@ -49,11 +75,18 @@ class InfoItemUpdateView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibl return self.team.get_absolute_url() -class InfoItemDeleteView(LoginRequiredMixin, CampViewMixin, EnsureTeamResponsibleMixin, RevisionMixin, DeleteView): +class InfoItemDeleteView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, RevisionMixin, DeleteView): model = InfoItem - template_name = "info_item_delete_confirm.html" + template_name = "team_info_item_delete_confirm.html" slug_field = '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): next = self.request.GET.get('next') diff --git a/src/teams/views/members.py b/src/teams/views/members.py new file mode 100644 index 00000000..8d050504 --- /dev/null +++ b/src/teams/views/members.py @@ -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) diff --git a/src/teams/views/mixins.py b/src/teams/views/mixins.py index db937891..34608b0e 100644 --- a/src/teams/views/mixins.py +++ b/src/teams/views/mixins.py @@ -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,8 +29,19 @@ 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 - ) \ No newline at end of file + ) + + +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 diff --git a/src/teams/views/tasks.py b/src/teams/views/tasks.py index ebd61398..aaaed31b 100644 --- a/src/teams/views/tasks.py +++ b/src/teams/views/tasks.py @@ -1,44 +1,74 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.http import HttpResponseRedirect from django.views.generic import DetailView, CreateView, UpdateView +from django import forms from camps.mixins import CampViewMixin -from ..models import TeamTask -from .mixins import EnsureTeamResponsibleMixin +from ..models import Team, TeamTask +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" context_object_name = "task" 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 template_name = "task_form.html" - fields = ['name', 'description', 'when', 'completed'] - - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(**kwargs) - context['team'] = self.team - return context - - def form_valid(self, form): - task = form.save(commit=False) - task.team = self.team - if not task.name: - task.name = "noname" - task.save() - return HttpResponseRedirect(task.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" - fields = ['name', 'description', 'when', 'completed'] + form_class = TaskForm + active_menu = 'tasks' + + def get_team(self): + return Team.objects.get( + 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 + if not task.name: + task.name = "noname" + task.save() + return HttpResponseRedirect(task.get_absolute_url()) + + def get_success_url(self): + return self.get_object().get_absolute_url() + + +class TaskUpdateView(LoginRequiredMixin, CampViewMixin, TeamViewMixin, EnsureTeamResponsibleMixin, UpdateView): + model = TeamTask + template_name = "task_form.html" + form_class = TaskForm + active_menu = 'tasks' def get_context_data(self, *args, **kwargs): context = super().get_context_data(**kwargs) diff --git a/src/templates/base.html b/src/templates/base.html index 4766b709..e3b2c5eb 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -58,7 +58,7 @@ {% endif %}

-