diff --git a/ietf/group/forms.py b/ietf/group/forms.py index c93ca1d63..7e0042927 100644 --- a/ietf/group/forms.py +++ b/ietf/group/forms.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2017-2020, All Rights Reserved +# Copyright The IETF Trust 2017-2023, All Rights Reserved # -*- coding: utf-8 -*- @@ -103,6 +103,8 @@ class GroupForm(forms.Form): else: field = None + self.hide_parent = kwargs.pop('hide_parent', False) + super(self.__class__, self).__init__(*args, **kwargs) if not group_features or group_features.has_chartering_process: @@ -138,18 +140,21 @@ class GroupForm(forms.Form): self.fields['acronym'].widget.attrs['readonly'] = "" # Sort out parent options - self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type__in=parent_types) - if need_parent: - self.fields['parent'].required = True - self.fields['parent'].empty_label = None - # if this is a new group, fill in the default parent, if any - if self.group is None or (not hasattr(self.group, 'pk')): - self.fields['parent'].initial = self.fields['parent'].queryset.filter( - acronym=default_parent - ).first() - # label the parent field as 'IETF Area' if appropriate, for consistency with past behavior - if parent_types.count() == 1 and parent_types.first().pk == 'area': - self.fields['parent'].label = "IETF Area" + if self.hide_parent: + self.fields.pop('parent') + else: + self.fields['parent'].queryset = self.fields['parent'].queryset.filter(type__in=parent_types) + if need_parent: + self.fields['parent'].required = True + self.fields['parent'].empty_label = None + # if this is a new group, fill in the default parent, if any + if self.group is None or (not hasattr(self.group, 'pk')): + self.fields['parent'].initial = self.fields['parent'].queryset.filter( + acronym=default_parent + ).first() + # label the parent field as 'IETF Area' if appropriate, for consistency with past behavior + if parent_types.count() == 1 and parent_types.first().pk == 'area': + self.fields['parent'].label = "IETF Area" if field: keys = list(self.fields.keys()) diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py index e0a738d07..136e19549 100644 --- a/ietf/group/tests_info.py +++ b/ietf/group/tests_info.py @@ -1,4 +1,4 @@ -# Copyright The IETF Trust 2009-2022, All Rights Reserved +# Copyright The IETF Trust 2009-2023, All Rights Reserved # -*- coding: utf-8 -*- @@ -946,10 +946,88 @@ class GroupEditTests(TestCase): r = self.client.post(url, { 'description': 'Ignored description', }) - self.assertEqual(r.status_code, 302) + self.assertEqual(r.status_code, 403) group = Group.objects.get(pk=group.pk) # refresh self.assertEqual(group.description, 'Updated description') + def test_edit_parent(self): + group = GroupFactory.create(type_id='wg', parent=GroupFactory.create(type_id='area')) + chair = RoleFactory(group=group, name_id='chair').person + url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action='edit')) + + # parent is not shown to group chair + login_testing_unauthorized(self, chair.user.username, url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('form select[name=parent]')), 0) + + # view ignores attempt to change parent + old_parent = group.parent + new_parent = GroupFactory(type_id='area') + self.assertNotEqual(new_parent.acronym, group.parent.acronym) + r = self.client.post(url, dict( + name=group.name, + acronym=group.acronym, + state=group.state_id, + parent=new_parent.pk)) + self.assertEqual(r.status_code, 302) + group = Group.objects.get(pk=group.pk) + self.assertNotEqual(group.parent, new_parent) + self.assertEqual(group.parent, old_parent) + + # parent is shown to AD and Secretariat + for priv_user in ('ad', 'secretary'): + self.client.logout() + login_testing_unauthorized(self, priv_user, url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('form select[name=parent]')), 1) + + new_parent = GroupFactory(type_id='area') + self.assertNotEqual(new_parent.acronym, group.parent.acronym) + r = self.client.post(url, dict( + name=group.name, + acronym=group.acronym, + state=group.state_id, + parent=new_parent.pk)) + self.assertEqual(r.status_code, 302) + group = Group.objects.get(pk=group.pk) + self.assertEqual(group.parent, new_parent) + + def test_edit_parent_field(self): + group = GroupFactory.create(type_id='wg', parent=GroupFactory.create(type_id='area')) + chair = RoleFactory(group=group, name_id='chair').person + url = urlreverse('ietf.group.views.edit', kwargs=dict(group_type=group.type_id, acronym=group.acronym, action='edit', field='parent')) + + # parent is not shown to group chair + login_testing_unauthorized(self, chair.user.username, url) + r = self.client.get(url) + self.assertEqual(r.status_code, 403) + + # chair is not allowed to change parent + new_parent = GroupFactory(type_id='area') + self.assertNotEqual(new_parent.acronym, group.parent.acronym) + r = self.client.post(url, dict(parent=new_parent.pk)) + self.assertEqual(r.status_code, 403) + + # parent is shown to AD and Secretariat + for priv_user in ('ad', 'secretary'): + self.client.logout() + login_testing_unauthorized(self, priv_user, url) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + q = PyQuery(r.content) + self.assertEqual(len(q('form select[name=parent]')), 1) + + new_parent = GroupFactory(type_id='area') + self.assertNotEqual(new_parent.acronym, group.parent.acronym) + r = self.client.post(url, dict(parent=new_parent.pk)) + self.assertEqual(r.status_code, 302) + group = Group.objects.get(pk=group.pk) + self.assertEqual(group.parent, new_parent) + def test_conclude(self): group = GroupFactory(acronym="mars") diff --git a/ietf/group/views.py b/ietf/group/views.py index 2b93e0760..593c649bb 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -945,14 +945,17 @@ def edit(request, group_type=None, acronym=None, action="edit", field=None): if not (can_manage_group(request.user, group) or group.has_role(request.user, group.features.groupman_roles)): permission_denied(request, "You don't have permission to access this view") + hide_parent = not has_role(request.user, ("Secretariat", "Area Director", "IRTF Chair")) else: # This allows ADs to create RG and the IRTF Chair to create WG, but we trust them not to if not has_role(request.user, ("Secretariat", "Area Director", "IRTF Chair")): permission_denied(request, "You don't have permission to access this view") - + hide_parent = False if request.method == 'POST': - form = GroupForm(request.POST, group=group, group_type=group_type, field=field) + form = GroupForm(request.POST, group=group, group_type=group_type, field=field, hide_parent=hide_parent) + if field and not form.fields: + permission_denied(request, "You don't have permission to edit this field") if form.is_valid(): clean = form.cleaned_data if new_group: @@ -1114,7 +1117,9 @@ def edit(request, group_type=None, acronym=None, action="edit", field=None): else: init = dict() - form = GroupForm(initial=init, group=group, group_type=group_type, field=field) + form = GroupForm(initial=init, group=group, group_type=group_type, field=field, hide_parent=hide_parent) + if field and not form.fields: + permission_denied(request, "You don't have permission to edit this field") return render(request, 'group/edit.html', dict(group=group, diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index d105e8c61..4dfe1574a 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -3034,7 +3034,9 @@ "material_types": "[\n \"slides\"\n]", "matman_roles": "[\n \"chair\"\n]", "need_parent": false, - "parent_types": [], + "parent_types": [ + "rfcedtyp" + ], "req_subm_approval": true, "role_order": "[\n \"chair\"\n]", "session_purposes": "[\n \"regular\"\n]",