From 1c509cd64c203913c0fed6b42d4e5f0451a8c84c Mon Sep 17 00:00:00 2001 From: Russ Housley Date: Sat, 2 Apr 2016 15:10:10 +0000 Subject: [PATCH] The secretariat and the Team Chair can now edit team groups. In addition, if the team in within the IETF, Area Directors can edit it. And, if the team is within the IRTF, the IRTF Chair can edit it. Fixes #1915. - Legacy-Id: 11064 --- ietf/group/edit.py | 17 +++++++++++------ ietf/group/info.py | 6 ++++-- ietf/group/models.py | 8 ++++++++ ietf/group/tests_info.py | 23 ++++++++++++++++++++++- ietf/group/utils.py | 9 +++++++++ 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/ietf/group/edit.py b/ietf/group/edit.py index b2773d54c..084b9540b 100644 --- a/ietf/group/edit.py +++ b/ietf/group/edit.py @@ -16,7 +16,7 @@ from ietf.doc.utils import get_tags_for_stream_id from ietf.doc.utils_charter import charter_name_for_group from ietf.group.models import ( Group, Role, GroupEvent, GroupHistory, GroupStateName, GroupStateTransitions, GroupTypeName, GroupURL, ChangeStateGroupEvent ) -from ietf.group.utils import save_group_in_history, can_manage_group_type +from ietf.group.utils import save_group_in_history, can_manage_group_type, can_manage_team_group from ietf.group.utils import get_group_or_404 from ietf.ietfauth.utils import has_role from ietf.person.fields import SearchableEmailsField @@ -199,11 +199,19 @@ def submit_initial_charter(request, group_type=None, acronym=None): def edit(request, group_type=None, acronym=None, action="edit"): """Edit or create a group, notifying parties as necessary and logging changes as group events.""" - if not can_manage_group_type(request.user, group_type): + group = get_group_or_404(acronym, group_type) + if not group_type and group: + group_type = group.type_id + + if group_type == "team": + can_edit = can_manage_team_group(request.user, group) or group.has_role(request.user, "chair") + else: + can_edit = can_manage_group_type(request.user, group_type) + + if not can_edit: return HttpResponseForbidden("You don't have permission to access this view") if action == "edit": - group = get_object_or_404(Group, acronym=acronym) new_group = False elif action in ("create","charter"): group = None @@ -211,9 +219,6 @@ def edit(request, group_type=None, acronym=None, action="edit"): else: raise Http404 - if not group_type and group: - group_type = group.type_id - if request.method == 'POST': form = GroupForm(request.POST, group=group, group_type=group_type) if form.is_valid(): diff --git a/ietf/group/info.py b/ietf/group/info.py index c0a82a19d..94ed3b2ec 100644 --- a/ietf/group/info.py +++ b/ietf/group/info.py @@ -55,7 +55,7 @@ from ietf.doc.utils import get_chartering_type from ietf.doc.templatetags.ietf_filters import clean_whitespace from ietf.group.models import Group, Role, ChangeStateGroupEvent from ietf.name.models import GroupTypeName -from ietf.group.utils import get_charter_text, can_manage_group_type, milestone_reviewer_for_group_type, can_provide_status_update +from ietf.group.utils import get_charter_text, can_manage_group_type, can_manage_team_group, milestone_reviewer_for_group_type, can_provide_status_update from ietf.group.utils import can_manage_materials, get_group_or_404 from ietf.utils.pipe import pipe from ietf.utils.textupload import get_cleaned_text_file_content @@ -366,6 +366,8 @@ def construct_group_menu_context(request, group, selected, group_type, others): is_chair = group.has_role(request.user, "chair") can_manage = can_manage_group_type(request.user, group.type_id) + if group.type_id == "team": + can_manage = can_manage_team_group(request.user, group) if group.features.has_milestones: if group.state_id != "proposed" and (is_chair or can_manage): @@ -374,7 +376,7 @@ def construct_group_menu_context(request, group, selected, group_type, others): if group.features.has_materials and can_manage_materials(request.user, group): actions.append((u"Upload material", urlreverse("ietf.doc.views_material.choose_material_type", kwargs=kwargs))) - if group.type_id in ("rg", "wg") and group.state_id != "conclude" and can_manage: + if group.state_id != "conclude" and (is_chair or can_manage): actions.append((u"Edit group", urlreverse("group_edit", kwargs=kwargs))) if group.features.customize_workflow and (is_chair or can_manage): diff --git a/ietf/group/models.py b/ietf/group/models.py index 49fd9569e..68f4e2baf 100644 --- a/ietf/group/models.py +++ b/ietf/group/models.py @@ -80,6 +80,14 @@ class Group(GroupInfo): role_names = [role_names] return user.is_authenticated() and self.role_set.filter(name__in=role_names, person__user=user).exists() + def is_decendant_of(self, sought_parent): + p = self.parent + while (p != None): + if p.acronym == sought_parent: + return True + p = p.parent + return False + def is_bof(self): return (self.state.slug in ["bof", "bof-conc"]) diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py index d07053f18..8f87e18a9 100644 --- a/ietf/group/tests_info.py +++ b/ietf/group/tests_info.py @@ -25,7 +25,7 @@ from ietf.name.models import DocTagName, GroupStateName, GroupTypeName from ietf.person.models import Person, Email from ietf.utils.test_utils import TestCase, unicontent from ietf.utils.mail import outbox, empty_outbox -from ietf.utils.test_data import make_test_data +from ietf.utils.test_data import make_test_data, create_person from ietf.utils.test_utils import login_testing_unauthorized from ietf.group.factories import GroupFactory, RoleFactory, GroupEventFactory from ietf.meeting.factories import SessionFactory @@ -240,6 +240,17 @@ class GroupPagesTests(TestCase): self.assertTrue(milestone.docs.all()[0].name in unicontent(r)) def test_group_about(self): + + def verify_cannot_edit_group(username): + self.client.login(username=username, password=username+"+password") + r = self.client.get(url) + self.assertEqual(r.status_code, 403) + + def verify_can_edit_group(username): + self.client.login(username=username, password=username+"+password") + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + make_test_data() group = Group.objects.create( type_id="team", @@ -247,7 +258,9 @@ class GroupPagesTests(TestCase): name="Test Team", description="The test team is testing.", state_id="active", + parent = Group.objects.get(acronym="farfut"), ) + create_person(group, "chair", name="Testteam Chairman", username="teamchairman") for url in [group.about_url(), urlreverse('ietf.group.info.group_about',kwargs=dict(acronym=group.acronym)), @@ -260,6 +273,14 @@ class GroupPagesTests(TestCase): self.assertTrue(group.acronym in unicontent(r)) self.assertTrue(group.description in unicontent(r)) + url = urlreverse('ietf.group.edit.edit', kwargs=dict(acronym=group.acronym)) + + for username in ['plain','iana','iab chair','irtf chair','marschairman']: + verify_cannot_edit_group(username) + + for username in ['secretary','teamchairman','ad']: + verify_can_edit_group(username) + def test_materials(self): make_test_data() group = Group.objects.create(type_id="team", acronym="testteam", name="Test Team", state_id="active") diff --git a/ietf/group/utils.py b/ietf/group/utils.py index 9d56c70ec..6d41fb95b 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -91,6 +91,15 @@ def can_manage_group_type(user, group_type): return has_role(user, 'Secretariat') +def can_manage_team_group(user, group): + if group.type_id != "team": + return False + elif group.is_decendant_of("ietf") and has_role(user, ('Area Director', 'Secretariat')): + return True + elif group.is_decendant_of("irtf") and has_role(user, ('IRTF Chair', 'Secretariat')): + return True + return has_role(user, ('Secretariat')) + def milestone_reviewer_for_group_type(group_type): if group_type == "rg": return "IRTF Chair"