Added 9 new group features, and changed list-like char fields to json fields, to get better support for using the values as lists. Modified code to use the group features instead of explicit lists of group types in many places in the code.

- Legacy-Id: 15908
This commit is contained in:
Henrik Levkowetz 2019-01-22 18:11:46 +00:00
parent d59eb23fd4
commit 57a4c9f41f
31 changed files with 831 additions and 186 deletions

View file

@ -47,12 +47,8 @@ def can_manage_community_list(user, clist):
if has_role(user, 'Secretariat'):
return True
if clist.group.type_id == 'area':
return Role.objects.filter(name__slug='ad', person__user=user, group=clist.group).exists()
elif clist.group.type_id in ('wg', 'rg', 'ag'):
return Role.objects.filter(name__slug='chair', person__user=user, group=clist.group).exists()
elif clist.group.type_id in ('program'):
return Role.objects.filter(name__slug='lead', person__user=user, group=clist.group).exists()
if clist.group.type_id in ['area', 'wg', 'rg', 'ag', 'program', ]:
return Role.objects.filter(name__slug__in=clist.group.features.admin_roles, person__user=user, group=clist.group).exists()
return False

View file

@ -269,7 +269,9 @@ def generate_approval_mail_rfc_editor(request, doc):
def generate_publication_request(request, doc):
group_description = ""
if doc.group and doc.group.acronym != "none":
group_description = doc.group.name_with_acronym()
group_description = doc.group.name
if doc.group.type_id not in ("ietf", "irtf", "iab",):
group_description += " %s (%s)" % (doc.group.type, doc.group.acronym)
e = doc.latest_event(ConsensusDocEvent, type="changed_consensus")
consensus = e.consensus if e else None

View file

@ -1,6 +1,6 @@
from django import template
from ietf.group.models import Group
from ietf.group.models import Group, Role
register = template.Library()
@ -9,18 +9,11 @@ def managed_groups(user):
if not (user and hasattr(user, "is_authenticated") and user.is_authenticated):
return []
groups = []
# groups.extend(Group.objects.filter(
# role__name__slug='ad',
# role__person__user=user,
# type__slug='area',
# state__slug='active').select_related("type"))
groups.extend(Group.objects.filter(
role__name__slug__in=['chair', 'delegate', 'ad', ],
role__person__user=user,
type__slug__in=('rg', 'wg', 'ag', 'ietf'),
state__slug__in=('active', 'bof')).select_related("type"))
groups = [ g for g in Group.objects.filter(
role__person__user=user,
type__features__has_session_materials=True,
state__slug__in=('active', 'bof')).select_related("type")
if Role.objects.filter(group=g, person__user=user, name__slug__in=g.type.features.matman_roles) ]
return groups

View file

@ -887,17 +887,17 @@ class DocTestCase(TestCase):
self.client.login(username='iab-chair', password='iab-chair+password')
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
self.assertTrue("Request publication" not in unicontent(r))
self.assertNotIn("Request publication", unicontent(r))
Document.objects.filter(pk=doc.pk).update(stream='iab')
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
self.assertTrue("Request publication" in unicontent(r))
self.assertIn("Request publication", unicontent(r))
doc.states.add(State.objects.get(type_id='draft-stream-iab',slug='rfc-edit'))
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=doc.name)))
self.assertEqual(r.status_code, 200)
self.assertTrue("Request publication" not in unicontent(r))
self.assertNotIn("Request publication", unicontent(r))
def test_document_bibtex(self):

View file

@ -1,4 +1,5 @@
# Copyright The IETF Trust 2016, All Rights Reserved
# Copyright The IETF Trust 2016-2018, All Rights Reserved
# Parts Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
@ -160,11 +161,14 @@ def document_main(request, name, rev=None):
iesg_state_summary = doc.friendly_state()
can_edit = has_role(request.user, ("Area Director", "Secretariat"))
stream_slugs = StreamName.objects.values_list("slug", flat=True)
can_change_stream = bool(can_edit or (
request.user.is_authenticated and
Role.objects.filter(name__in=("chair", "secr", "auth", "delegate"),
group__acronym__in=stream_slugs,
person__user=request.user)))
# For some reason, AnonymousUser has __iter__, but is not iterable,
# which causes problems in the filter() below. Work around this:
if request.user.is_authenticated:
roles = [ r for r in Role.objects.filter(group__acronym__in=stream_slugs, person__user=request.user)
if r.name.slug in r.group.type.features.matman_roles ]
else:
roles = []
can_change_stream = bool(can_edit or roles)
can_edit_iana_state = has_role(request.user, ("Secretariat", "IANA"))
can_edit_replaces = has_role(request.user, ("Area Director", "Secretariat", "IRTF Chair", "WG Chair", "RG Chair", "WG Secretary", "RG Secretary"))

View file

@ -1,3 +1,5 @@
# Copyright The IETF Trust 2010-2019, All Rights Reserved
# changing state and metadata on Internet Drafts
import datetime
@ -31,7 +33,7 @@ from ietf.doc.utils import ( add_state_change_event, can_adopt_draft, can_unadop
set_replaces_for_document, default_consensus, tags_suffix, )
from ietf.doc.lastcall import request_last_call
from ietf.doc.fields import SearchableDocAliasesField
from ietf.group.models import Group, Role
from ietf.group.models import Group, Role, GroupFeatures
from ietf.iesg.models import TelechatDate
from ietf.ietfauth.utils import has_role, is_authorized_in_doc_stream, user_is_person, is_individual_draft_author
from ietf.ietfauth.utils import role_required
@ -1294,13 +1296,15 @@ def request_publication(request, name):
)
class AdoptDraftForm(forms.Form):
group = forms.ModelChoiceField(queryset=Group.objects.filter(type__in=["wg", "rg"], state="active").order_by("-type", "acronym"), required=True, empty_label=None)
group = forms.ModelChoiceField(queryset=Group.objects.filter(type__features__acts_like_wg=True, state="active").order_by("-type", "acronym"), required=True, empty_label=None)
newstate = forms.ModelChoiceField(queryset=State.objects.filter(type__in=['draft-stream-ietf','draft-stream-irtf'], used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING), required=True, label="State")
comment = forms.CharField(widget=forms.Textarea, required=False, label="Comment", help_text="Optional comment explaining the reasons for the adoption.", strip=False)
weeks = forms.IntegerField(required=False, label="Expected weeks in adoption state")
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
rg_features = GroupFeatures.objects.get(type_id='rg')
wg_features = GroupFeatures.objects.get(type_id='wg')
super(AdoptDraftForm, self).__init__(*args, **kwargs)
@ -1308,17 +1312,21 @@ class AdoptDraftForm(forms.Form):
if has_role(user, "Secretariat"):
state_types.update(['draft-stream-ietf','draft-stream-irtf'])
else:
if has_role(user, "IRTF Chair") or Group.objects.filter(type="rg", state="active", role__person__user=user, role__name__in=("chair", "delegate", "secr")).exists():
if has_role(user, "IRTF Chair") or Group.objects.filter(type="rg", state="active", role__person__user=user, role__name__in=rg_features.matman_roles).exists():
state_types.add('draft-stream-irtf')
if Group.objects.filter(type="wg", state="active", role__person__user=user, role__name__in=("chair", "delegate", "secr")).exists():
if Group.objects.filter(type="wg", state="active", role__person__user=user, role__name__in=wg_features.matman_roles).exists():
state_types.add('draft-stream-ietf')
state_choices = State.objects.filter(type__in=state_types, used=True).exclude(slug__in=settings.GROUP_STATES_WITH_EXTRA_PROCESSING)
if not has_role(user, "Secretariat"):
if has_role(user, "IRTF Chair"):
group_queryset = self.fields["group"].queryset.filter(Q(role__person__user=user, role__name__in=("chair", "delegate", "secr"))|Q(type="rg", state="active")).distinct()
group_queryset = self.fields["group"].queryset.filter(Q(role__person__user=user, role__name__in=rg_features.matman_roles)|Q(type="rg", state="active")).distinct()
else:
group_queryset = self.fields["group"].queryset.filter(role__person__user=user, role__name__in=("chair", "delegate", "secr")).distinct()
group_queryset = self.fields["group"].queryset.filter(role__person__user=user, role__name__in=wg_features.matman_roles).distinct()
self.fields["group"].queryset = group_queryset
self.fields['group'].choices = [(g.pk, '%s - %s' % (g.acronym, g.name)) for g in self.fields["group"].queryset]

View file

@ -102,21 +102,30 @@ admin.site.register(Group, GroupAdmin)
class GroupFeaturesAdmin(admin.ModelAdmin):
list_display = [
'type',
'customize_workflow',
'has_chartering_process',
'has_default_jabber',
'has_dependencies',
'has_documents',
'has_nonsession_materials',
'has_milestones',
'has_chartering_process',
'has_documents',
'has_dependencies',
'has_session_materials',
'has_nonsession_materials',
'has_meetings',
'has_reviews',
'material_types',
'has_default_jabber',
'acts_like_wg',
'create_wiki',
'custom_group_roles',
'customize_workflow',
'is_schedulable',
'show_on_agenda',
'req_subm_approval',
'agenda_type',
'material_types',
'admin_roles',
'about_page',
'default_tab',
]
'matman_roles',
'role_order',
]
admin.site.register(GroupFeatures, GroupFeaturesAdmin)
class GroupHistoryAdmin(admin.ModelAdmin):

View file

@ -0,0 +1,107 @@
# Copyright The IETF Trust 2019, All Rights Reserved
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2019-01-10 07:51
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('group', '0003_groupfeatures_data'),
]
operations = [
migrations.AddField(
model_name='groupfeatures',
name='acts_like_wg',
field=models.BooleanField(default=False, verbose_name=b'WG-Like'),
),
migrations.AddField(
model_name='groupfeatures',
name='create_wiki',
field=models.BooleanField(default=False, verbose_name=b'Wiki'),
),
migrations.AddField(
model_name='groupfeatures',
name='custom_group_roles',
field=models.BooleanField(default=False, verbose_name=b'Group Roles'),
),
migrations.AddField(
model_name='groupfeatures',
name='has_session_materials',
field=models.BooleanField(default=False, verbose_name=b'Materials'),
),
migrations.AddField(
model_name='groupfeatures',
name='is_schedulable',
field=models.BooleanField(default=False, verbose_name=b'Schedulable'),
),
migrations.AddField(
model_name='groupfeatures',
name='role_order',
field=models.CharField(default=b'chair,secr,member', help_text=b'The order in which roles are shown, for instance on photo pages', max_length=128, validators=[django.core.validators.RegexValidator(code=b'invalid', message=b'Enter a comma-separated list of role slugs', regex=b'[a-z0-9_-]+(,[a-z0-9_-]+)*')]),
),
migrations.AddField(
model_name='groupfeatures',
name='show_on_agenda',
field=models.BooleanField(default=False, verbose_name=b'On Agenda'),
),
migrations.AddField(
model_name='groupfeatures',
name='req_subm_approval',
field=models.BooleanField(default=False, verbose_name=b'Subm. Approval'),
),
migrations.AddField(
model_name='groupfeatures',
name='matman_roles',
field=models.CharField(default=b'ad,chair,delegate,secr', max_length=64, validators=[django.core.validators.RegexValidator(code=b'invalid', message=b'Enter a comma-separated list of role slugs', regex=b'[a-z0-9_-]+(,[a-z0-9_-]+)*')]),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='acts_like_wg',
field=models.BooleanField(default=False, verbose_name=b'WG-Like'),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='create_wiki',
field=models.BooleanField(default=False, verbose_name=b'Wiki'),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='custom_group_roles',
field=models.BooleanField(default=False, verbose_name=b'Group Roles'),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='has_session_materials',
field=models.BooleanField(default=False, verbose_name=b'Materials'),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='is_schedulable',
field=models.BooleanField(default=False, verbose_name=b'Schedulable'),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='role_order',
field=models.CharField(default=b'chair,secr,member', help_text=b'The order in which roles are shown, for instance on photo pages', max_length=128, validators=[django.core.validators.RegexValidator(code=b'invalid', message=b'Enter a comma-separated list of role slugs', regex=b'[a-z0-9_-]+(,[a-z0-9_-]+)*')]),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='show_on_agenda',
field=models.BooleanField(default=False, verbose_name=b'On Agenda'),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='req_subm_approval',
field=models.BooleanField(default=False, verbose_name=b'Subm. Approval'),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='matman_roles',
field=models.CharField(default=b'ad,chair,delegate,secr', max_length=64, validators=[django.core.validators.RegexValidator(code=b'invalid', message=b'Enter a comma-separated list of role slugs', regex=b'[a-z0-9_-]+(,[a-z0-9_-]+)*')]),
),
]

View file

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.17 on 2018-12-18 23:49
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('group', '0003_groupfeatures_data'),
]
operations = [
migrations.AlterField(
model_name='historicalgroupfeatures',
name='agenda_type',
field=models.ForeignKey(blank=True, db_constraint=False, default=b'ietf', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='name.AgendaTypeName'),
),
]

View file

@ -0,0 +1,49 @@
# Copyright The IETF Trust 2019, All Rights Reserved
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2019-01-09 09:02
from __future__ import unicode_literals
import json
import re
from django.db import migrations
import debug # pyflakes:ignore
def forward(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
for f in GroupFeatures.objects.all():
for a in ['material_types', 'admin_roles', 'matman_roles', 'role_order']:
v = getattr(f, a, None)
if v != None:
v = re.sub(r'[][\\"\' ]+', '', v)
v = v.split(',')
v = json.dumps(v)
setattr(f, a, v)
f.save()
# This migration changes existing data fields in an incompatible manner, and
# would not be interleavable if we hadn't added compatibility code in
# Group.features() beforehand. With that patched in, we permit interleaving.
forward.interleavable = True
def reverse(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
for f in GroupFeatures.objects.all():
for a in ['material_types', 'admin_roles', 'matman_roles', 'role_order']:
v = getattr(f, a, None)
if v != None:
v = getattr(f, a)
v = json.loads(v)
v = ','.join(v)
setattr(f, a, v)
f.save()
class Migration(migrations.Migration):
dependencies = [
('group', '0004_add_group_feature_fields'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2019-01-16 05:53
from __future__ import unicode_literals
from django.db import migrations, models
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('group', '0005_group_features_list_data_to_json'),
]
operations = [
migrations.AlterField(
model_name='groupfeatures',
name='admin_roles',
field=jsonfield.fields.JSONField(default=b'chair', max_length=64),
),
migrations.AlterField(
model_name='groupfeatures',
name='custom_group_roles',
field=models.BooleanField(default=False, verbose_name=b'Cust. Roles'),
),
migrations.AlterField(
model_name='groupfeatures',
name='has_nonsession_materials',
field=models.BooleanField(default=False, verbose_name=b'Other Matrl.'),
),
migrations.AlterField(
model_name='groupfeatures',
name='has_session_materials',
field=models.BooleanField(default=False, verbose_name=b'Sess Matrl.'),
),
migrations.AlterField(
model_name='groupfeatures',
name='material_types',
field=jsonfield.fields.JSONField(default=b'slides', max_length=64),
),
migrations.AlterField(
model_name='groupfeatures',
name='matman_roles',
field=jsonfield.fields.JSONField(default=b'ad,chair,delegate,secr', max_length=128),
),
migrations.AlterField(
model_name='groupfeatures',
name='role_order',
field=jsonfield.fields.JSONField(default=b'chair,secr,member', help_text=b'The order in which roles are shown, for instance on photo pages. Enter valid JSON.', max_length=128),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='admin_roles',
field=jsonfield.fields.JSONField(default=b'chair', max_length=64),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='custom_group_roles',
field=models.BooleanField(default=False, verbose_name=b'Cust. Roles'),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='has_nonsession_materials',
field=models.BooleanField(default=False, verbose_name=b'Other Matrl.'),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='has_session_materials',
field=models.BooleanField(default=False, verbose_name=b'Sess Matrl.'),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='material_types',
field=jsonfield.fields.JSONField(default=b'slides', max_length=64),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='matman_roles',
field=jsonfield.fields.JSONField(default=b'ad,chair,delegate,secr', max_length=128),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='role_order',
field=jsonfield.fields.JSONField(default=b'chair,secr,member', help_text=b'The order in which roles are shown, for instance on photo pages. Enter valid JSON.', max_length=128),
),
]

View file

@ -0,0 +1,211 @@
# Copyright The IETF Trust 2019, All Rights Reserved
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2019-01-09 09:02
from __future__ import unicode_literals
from django.db import migrations
import debug # pyflakes:ignore
group_type_features = {
u'ag': {
'custom_group_roles': True,
'has_session_materials': True,
'acts_like_wg': True,
'create_wiki': True,
'is_schedulable': True,
'req_subm_approval': True,
'show_on_agenda': True,
'matman_roles': ['ad', 'chair', 'delegate', 'secr'],
'role_order': ['chair', 'secr'],
},
u'area': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': True,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['ad', 'chair', 'delegate', 'secr'],
'role_order': ['chair', 'secr'],
},
u'dir': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': True,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['ad', 'chair', 'delegate', 'secr'],
'role_order': ['chair', 'secr'],
},
u'review': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': True,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['ad', 'secr'],
'role_order': ['chair', 'secr'],
},
u'iab': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': True,
'matman_roles': [],
'role_order': ['chair', 'secr'],
},
u'ietf': {
'custom_group_roles': True,
'has_session_materials': True,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['chair', 'delegate'],
'role_order': ['chair', 'secr'],
},
u'individ': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': False,
'show_on_agenda': False,
'matman_roles': ['auth'],
'role_order': ['chair', 'secr'],
},
u'irtf': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['chair', 'delegate', 'secr'],
'role_order': ['chair', 'secr'],
},
u'isoc': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['chair', 'secr'],
'role_order': ['chair', 'secr'],
},
u'nomcom': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': True,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['chair'],
'role_order': ['chair', 'member', 'advisor'],
},
u'program': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': False,
'show_on_agenda': False,
'matman_roles': ['chair', 'secr'],
'role_order': ['chair', 'secr'],
},
u'rfcedtyp': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['chair', 'secr'],
'role_order': ['chair', 'secr'],
},
u'rg': {
'custom_group_roles': False,
'has_session_materials': True,
'acts_like_wg': True,
'create_wiki': True,
'is_schedulable': True,
'req_subm_approval': True,
'show_on_agenda': True,
'matman_roles': ['chair', 'secr'],
'role_order': ['chair', 'secr'],
},
u'sdo': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': False,
'is_schedulable': False,
'req_subm_approval': True,
'show_on_agenda': False,
'matman_roles': ['liaiman', 'matman'],
'role_order': ['liaiman'],
},
u'team': {
'custom_group_roles': True,
'has_session_materials': False,
'acts_like_wg': False,
'create_wiki': True,
'is_schedulable': False,
'req_subm_approval': False,
'show_on_agenda': False,
'matman_roles': ['chair', 'matman'],
'role_order': ['chair', 'member', 'matman'],
},
u'wg': {
'custom_group_roles': False,
'has_session_materials': True,
'acts_like_wg': True,
'create_wiki': True,
'is_schedulable': True,
'req_subm_approval': True,
'show_on_agenda': True,
'matman_roles': ['ad', 'chair', 'delegate', 'secr'],
'role_order': ['chair', 'secr', 'delegate'],
},
}
def forward(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
for type in group_type_features:
features = group_type_features[type]
gf = GroupFeatures.objects.get(type=type)
for k,v in features.items():
setattr(gf, k, v)
gf.save()
forward.interleavable = True
def reverse(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('group', '0006_group_features_lists_to_jsonfield')
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.16 on 2019-01-19 10:08
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import ietf.utils.models
class Migration(migrations.Migration):
dependencies = [
('group', '0007_new_group_features_data'),
]
operations = [
migrations.AlterField(
model_name='groupfeatures',
name='type',
field=ietf.utils.models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='features', serialize=False, to='name.GroupTypeName'),
),
migrations.AlterField(
model_name='historicalgroupfeatures',
name='type',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='name.GroupTypeName'),
),
]

View file

@ -2,6 +2,7 @@
import datetime
import email.utils
import jsonfield
import os
import re
from urlparse import urljoin
@ -40,31 +41,20 @@ class GroupInfo(models.Model):
def __unicode__(self):
return self.name
def name_with_acronym(self):
res = self.name
if self.type_id in ("wg", "rg", "ag", "area"):
res += " %s (%s)" % (self.type, self.acronym)
return res
def ad_role(self):
return self.role_set.filter(name='ad').first()
@property
def features(self):
if not hasattr(self, "features_cache"):
features = GroupFeatures.objects.get(type=self.type)
# convert textual lists to python lists:
for a in ['material_types', 'admin_roles', ]:
v = getattr(features, a)
setattr(features, a, v.split(','))
self.features_cache = features
self.features_cache = GroupFeatures.objects.get(type=self.type)
return self.features_cache
def about_url(self):
# bridge gap between group-type prefixed URLs and /group/ ones
from django.urls import reverse as urlreverse
kwargs = { 'acronym': self.acronym }
if self.type_id in ("wg", "rg", "ag"):
if self.features.acts_like_wg:
kwargs["group_type"] = self.type_id
return urlreverse(self.features.about_page, kwargs=kwargs)
@ -214,25 +204,35 @@ validate_comma_separated_roles = RegexValidator(
)
class GroupFeatures(models.Model):
type = ForeignKey(GroupTypeName, primary_key=True, null=False, related_name='features')
type = OneToOneField(GroupTypeName, primary_key=True, null=False, related_name='features')
history = HistoricalRecords()
#
has_milestones = models.BooleanField("Milestones", default=False)
has_chartering_process = models.BooleanField("Chartering", default=False)
has_documents = models.BooleanField("Documents", default=False) # i.e. drafts/RFCs
has_dependencies = models.BooleanField("Dependencies",default=False) # Do dependency graphs for group documents make sense?
has_nonsession_materials= models.BooleanField("Materials", default=False)
has_session_materials = models.BooleanField("Sess Matrl.", default=False)
has_nonsession_materials= models.BooleanField("Other Matrl.", default=False)
has_meetings = models.BooleanField("Meetings", default=False)
has_reviews = models.BooleanField("Reviews", default=False)
has_default_jabber = models.BooleanField("Jabber", default=False)
#
acts_like_wg = models.BooleanField("WG-Like", default=False)
create_wiki = models.BooleanField("Wiki", default=False)
custom_group_roles = models.BooleanField("Cust. Roles",default=False)
customize_workflow = models.BooleanField("Workflow", default=False)
is_schedulable = models.BooleanField("Schedulable",default=False)
show_on_agenda = models.BooleanField("On Agenda", default=False)
req_subm_approval = models.BooleanField("Subm. Approval", default=False)
#
agenda_type = models.ForeignKey(AgendaTypeName, null=True, default="ietf", on_delete=CASCADE)
about_page = models.CharField(max_length=64, blank=False, default="ietf.group.views.group_about" )
default_tab = models.CharField(max_length=64, blank=False, default="ietf.group.views.group_about" )
material_types = models.CharField(max_length=64, blank=False, default="slides",
validators=[validate_comma_separated_materials])
admin_roles = models.CharField(max_length=64, blank=False, default="chair",
validators=[validate_comma_separated_roles])
material_types = jsonfield.JSONField(max_length=64, blank=False, default="slides")
admin_roles = jsonfield.JSONField(max_length=64, blank=False, default="chair")
matman_roles = jsonfield.JSONField(max_length=128, blank=False, default="ad,chair,delegate,secr")
role_order = jsonfield.JSONField(max_length=128, blank=False, default="chair,secr,member",
help_text="The order in which roles are shown, for instance on photo pages. Enter valid JSON.")
class GroupHistory(GroupInfo):

View file

@ -121,12 +121,12 @@ def milestone_reviewer_for_group_type(group_type):
return "Area Director"
def can_manage_materials(user, group):
return has_role(user, 'Secretariat') or group.has_role(user, ("chair", "delegate", "secr", "matman", "ad"))
return has_role(user, 'Secretariat') or group.has_role(user, group.features.matman_roles)
def can_provide_status_update(user, group):
if not group.type_id in ['wg','rg','ag','team']:
if not group.features.acts_like_wg:
return False
return has_role(user, 'Secretariat') or group.has_role(user, ("chair", "delegate", "secr", "ad",))
return has_role(user, 'Secretariat') or group.has_role(user, group.features.matman_roles)
def get_group_or_404(acronym, group_type):
"""Helper to overcome the schism between group-type prefixed URLs and generic."""

View file

@ -1,4 +1,5 @@
# Copyright The IETF Trust 2007, All Rights Reserved
# Copyright The IETF Trust 2007-2019, All Rights Reserved
# Portion Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
@ -69,7 +70,8 @@ from ietf.group.forms import (GroupForm, StatusUpdateForm, ConcludeGroupForm, St
ManageReviewRequestForm, EmailOpenAssignmentsForm, ReviewerSettingsForm,
AddUnavailablePeriodForm, EndUnavailablePeriodForm, ReviewSecretarySettingsForm, )
from ietf.group.mails import email_admin_re_charter, email_personnel_change, email_comment
from ietf.group.models import ( Group, Role, GroupEvent, GroupStateTransitions, GroupURL, ChangeStateGroupEvent )
from ietf.group.models import ( Group, Role, GroupEvent, GroupStateTransitions, GroupURL,
ChangeStateGroupEvent, GroupFeatures )
from ietf.group.utils import (get_charter_text, can_manage_group_type,
milestone_reviewer_for_group_type, can_provide_status_update,
can_manage_materials,
@ -385,7 +387,8 @@ def bofs(request, group_type):
def chartering_groups(request):
charter_states = State.objects.filter(used=True, type="charter").exclude(slug__in=("approved", "notrev"))
group_types = GroupTypeName.objects.filter(slug__in=("wg", "rg"))
group_type_slugs = [ f.type.slug for f in GroupFeatures.objects.filter(has_chartering_process=True) ]
group_types = GroupTypeName.objects.filter(slug__in=group_type_slugs)
for t in group_types:
t.chartering_groups = Group.objects.filter(type=t, charter__states__in=charter_states).select_related("state", "charter").order_by("acronym")
@ -788,16 +791,7 @@ def group_photos(request, group_type=None, acronym=None):
group = get_object_or_404(Group, acronym=acronym)
roles = sorted(Role.objects.filter(group__acronym=acronym),key=lambda x: x.name.name+x.person.last_name())
if group.type_id in ['wg', 'rg', 'ag', ]:
roles = reorder_roles(roles, ['chair', 'secr'])
elif group.type_id in ['nomcom', ]:
roles = reorder_roles(roles, ['chair', 'member', 'advisor', ])
elif group.type_id in ['team', ]:
roles = reorder_roles(roles, ['chair', 'member', 'matman', ])
elif group.type_id in ['sdo', ]:
roles = reorder_roles(roles, ['liaiman', ])
else:
pass
roles = reorder_roles(roles, group.features.role_order)
for role in roles:
role.last_initial = role.person.last_name()[0]
return render(request, 'group/group_photos.html',
@ -1197,6 +1191,7 @@ def stream_documents(request, acronym):
docs, meta = prepare_document_table(request, qs, max_results=1000)
return render(request, 'group/stream_documents.html', {'stream':stream, 'docs':docs, 'meta':meta, 'editable':editable } )
def stream_edit(request, acronym):
group = get_object_or_404(Group, acronym=acronym)
@ -1252,7 +1247,7 @@ def group_json(request, acronym):
@cache_control(public=True, max_age=30*60)
@cache_page(30 * 60)
def group_menu_data(request):
groups = Group.objects.filter(state="active", type__in=("wg", "rg"), parent__state="active").order_by("acronym")
groups = Group.objects.filter(state="active", type__features__acts_like_wg=True, parent__state="active").order_by("acronym")
groups_by_parent = defaultdict(list)
for g in groups:

View file

@ -1,3 +1,5 @@
# Copyright The IETF Trust 2013-2019, All Rights Reserved
# various authentication and authorization utilities
from functools import wraps
@ -11,7 +13,7 @@ from django.utils.decorators import available_attrs
import debug # pyflakes:ignore
from ietf.group.models import Role
from ietf.group.models import Group, Role
from ietf.person.models import Person
def user_is_person(user, person):
@ -134,16 +136,20 @@ def is_authorized_in_doc_stream(user, doc):
if doc.stream.slug == "ietf" and doc.group.type_id == "individ":
return False
matman_roles = doc.group.features.matman_roles
if doc.stream.slug == "ietf":
group_req = Q(group=doc.group)
elif doc.stream.slug == "irtf":
group_req = Q(group__acronym=doc.stream.slug) | Q(group=doc.group)
elif doc.stream.slug in ("iab", "ise"):
if doc.group.type.slug == 'individ':
# A lot of special cases here, for stream slugs and group acronyms
matman_roles = Group.objects.get(acronym=doc.stream.slug).features.matman_roles
group_req = Q(group__acronym=doc.stream.slug)
else:
group_req = Q()
return Role.objects.filter(Q(name__in=("chair", "secr", "delegate", "auth"), person__user=user) & group_req).exists()
return Role.objects.filter(Q(name__in=matman_roles, person__user=user) & group_req).exists()
def is_authorized_in_group(user, group):
"""Return whether user is authorized to perform duties on
@ -163,7 +169,7 @@ def is_authorized_in_group(user, group):
if group.parent.acronym == 'iab' and has_role(user, ['IAB','IAB Executive Director',]):
return True
return Role.objects.filter(name__in=("chair", "secr", "delegate", "auth"), person__user=user,group=group ).exists()
return Role.objects.filter(name__in=group.features.matman_roles, person__user=user,group=group ).exists()
def is_individual_draft_author(user, doc):

View file

@ -11,7 +11,7 @@ from ietf.group.models import Group
from ietf.mailinglists.models import List
def groups(request):
groups = Group.objects.filter(type__in=("wg", "rg", "ag"), list_archive__startswith='http').exclude(state__in=('bof', 'conclude')).order_by("acronym")
groups = Group.objects.filter(type__features__acts_like_wg=True, list_archive__startswith='http').exclude(state__in=('bof', 'conclude')).order_by("acronym")
return render(request, "mailinglists/group_archives.html", { "groups": groups } )
@ -19,7 +19,7 @@ def groups(request):
# safely cache this for some time.
@cache_page(15*60)
def nonwg(request):
groups = Group.objects.filter(type__in=("wg", "rg")).exclude(state__in=['bof', 'conclude']).order_by("acronym")
groups = Group.objects.filter(type__features__acts_like_wg=True).exclude(state__in=['bof', 'conclude']).order_by("acronym")
#urls = [ g.list_archive for g in groups if '.ietf.org' in g.list_archive ]

View file

@ -68,7 +68,7 @@ class Recipient(models.Model):
addrs = []
if 'doc' in kwargs:
doc=kwargs['doc']
if doc.group and doc.group.type.slug in ['wg','rg','ag',]:
if doc.group and doc.group.features.acts_like_wg:
addrs.append('%s-chairs@ietf.org'%doc.group.acronym)
return addrs
@ -76,7 +76,7 @@ class Recipient(models.Model):
addrs = []
if 'doc' in kwargs:
doc=kwargs['doc']
if doc.group and doc.group.type.slug in ['wg','rg','ag',]:
if doc.group and doc.group.features.acts_like_wg:
addrs.extend(doc.group.role_set.filter(name='delegate').values_list('email__address',flat=True))
return addrs
@ -84,7 +84,7 @@ class Recipient(models.Model):
addrs = []
if 'doc' in kwargs:
doc=kwargs['doc']
if doc.group.type.slug in ['wg','rg','ag',]:
if doc.group.features.acts_like_wg:
if doc.group.list_email:
addrs.append(doc.group.list_email)
return addrs
@ -226,7 +226,7 @@ class Recipient(models.Model):
new_author_email_set = set(author["email"] for author in submission.authors if author.get("email"))
if doc.group and old_author_email_set != new_author_email_set:
if doc.group.type_id in ['wg','rg','ag']:
if doc.group.features.acts_like_wg:
addrs.extend(Recipient.objects.get(slug='group_chairs').gather(**{'group':doc.group}))
elif doc.group.type_id in ['area']:
addrs.extend(Recipient.objects.get(slug='group_responsible_directors').gather(**{'group':doc.group}))

View file

@ -67,9 +67,9 @@ def gather_relevant_expansions(**kwargs):
relevant.update(starts_with('group_'))
relevant.update(starts_with('milestones_'))
group = kwargs['group']
if group.type_id in ['rg','wg','ag',]:
if group.features.acts_like_wg:
relevant.update(starts_with('session_'))
if group.type_id in ['wg',]:
if group.features.has_chartering_process:
relevant.update(['charter_external_review',])
if 'submission' in kwargs:

View file

@ -1,3 +1,4 @@
# Copyright The IETF Trust 2009-2019, All Rights Reserved
# -*- coding: utf-8 -*-
import json
@ -294,14 +295,16 @@ class MeetingTests(TestCase):
self.assertTrue("1. WG status" in unicontent(r))
# session minutes
r = self.client.get(urlreverse("ietf.meeting.views.materials_document",
kwargs=dict(num=meeting.number, document=session.minutes())))
url = urlreverse("ietf.meeting.views.materials_document",
kwargs=dict(num=meeting.number, document=session.minutes()))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertTrue("1. More work items underway" in unicontent(r))
# test with explicit meeting number in url
if meeting.number.isdigit():
r = self.client.get(urlreverse("ietf.meeting.views.materials", kwargs=dict(num=meeting.number)))
url = urlreverse("ietf.meeting.views.materials", kwargs=dict(num=meeting.number))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
row = q('#content #%s' % str(session.group.acronym)).closest("tr")
@ -311,7 +314,8 @@ class MeetingTests(TestCase):
self.assertFalse(row.find("a:contains(\"Bad Slideshow\")"))
# test with no meeting number in url
r = self.client.get(urlreverse("ietf.meeting.views.materials", kwargs=dict()))
url = urlreverse("ietf.meeting.views.materials", kwargs=dict())
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
row = q('#content #%s' % str(session.group.acronym)).closest("tr")
@ -322,7 +326,8 @@ class MeetingTests(TestCase):
# test with a loggged-in wg chair
self.client.login(username="marschairman", password="marschairman+password")
r = self.client.get(urlreverse("ietf.meeting.views.materials", kwargs=dict(num=meeting.number)))
url = urlreverse("ietf.meeting.views.materials", kwargs=dict(num=meeting.number))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
row = q('#content #%s' % str(session.group.acronym)).closest("tr")

View file

@ -1264,8 +1264,8 @@
},
{
"fields": {
"desc": "This is the initial state when an AD proposes a new charter. The normal next state is Internal review if the idea is accepted, or Not currently under review if the idea is abandoned.",
"name": "Informal IESG review",
"desc": "The proposed charter is not being considered at this time. A proposed charter will remain in this state until an AD moves it to Start Chartering/Rechartering (Internal IESG/IAB Review). This state is useful for drafting the charter, discussing with chairs, etc.",
"name": "Draft Charter",
"next_states": [],
"order": 0,
"slug": "infrev",
@ -1277,8 +1277,8 @@
},
{
"fields": {
"desc": "The IESG and IAB are reviewing the early draft of the charter; this is the initial IESG and IAB review. The usual next state is External review if the idea is adopted, or Informal IESG review if the IESG decides the idea needs more work, or Not currently under review if the idea is abandoned",
"name": "Internal review",
"desc": "This is the state when you'd like to propose the charter / new charter. This state also allows you to ask whether external review can be skipped in ballot. After you select this state, the Secretariat takes over and drives the rest of the process.",
"name": "Start Chartering/Rechartering (Internal IESG/IAB Review)",
"next_states": [],
"order": 0,
"slug": "intrev",
@ -1290,8 +1290,8 @@
},
{
"fields": {
"desc": "The IETF community and possibly other standards development organizations (SDOs) are reviewing the proposed charter. The usual next state is IESG review, although it might move to Not currently under review if the idea is abandoned during the external review.",
"name": "External review",
"desc": "This state is selected by the Secretariat (AD's, keep yer grubby mits off this!) when it has been decided that the charter needs external review.",
"name": "External Review (Message to Community, Selected by Secretariat)",
"next_states": [],
"order": 0,
"slug": "extrev",
@ -1303,8 +1303,8 @@
},
{
"fields": {
"desc": "The IESG is reviewing the discussion from the external review of the proposed charter. The usual next state is Approved, or Not currently under review if the idea is abandoned.",
"name": "IESG review",
"desc": "This state is selected by the Secretariat (AD's, keep yer grubby mits off this!) when the IESG is reviewing the discussion from the external review of the proposed charter (this is similar to the IESG Evaluation state for a draft).",
"name": "IESG Review (Charter for Approval, Selected by Secretariat)",
"next_states": [],
"order": 0,
"slug": "iesgrev",
@ -2181,7 +2181,7 @@
"used": true
},
"model": "doc.state",
"pk": 152
"pk": 150
},
{
"fields": {
@ -2347,8 +2347,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": true,
"admin_roles": "[\"chair\"]",
"agenda_type": "ietf",
"create_wiki": true,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2359,7 +2362,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": true,
"is_schedulable": true,
"material_types": "[\"slides\"]",
"matman_roles": "[\"ad\",\"chair\",\"delegate\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": true
},
"model": "group.groupfeatures",
"pk": "ag"
@ -2367,8 +2376,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "ad",
"acts_like_wg": false,
"admin_roles": "[\"ad\"]",
"agenda_type": "ietf",
"create_wiki": true,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2379,7 +2391,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"ad\",\"chair\",\"delegate\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "area"
@ -2387,8 +2405,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair,secr",
"acts_like_wg": false,
"admin_roles": "[\"chair\",\"secr\"]",
"agenda_type": null,
"create_wiki": true,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2399,7 +2420,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"ad\",\"chair\",\"delegate\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "dir"
@ -2407,8 +2434,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": "ietf",
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2419,7 +2449,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": true
},
"model": "group.groupfeatures",
"pk": "iab"
@ -2427,8 +2463,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair,lead",
"acts_like_wg": false,
"admin_roles": "[\"chair\",\"lead\"]",
"agenda_type": "ietf",
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2439,7 +2478,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": true,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\",\"delegate\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "ietf"
@ -2447,8 +2492,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": null,
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2459,7 +2507,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"auth\"]",
"req_subm_approval": false,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "individ"
@ -2467,8 +2521,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": "ietf",
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2479,7 +2536,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\",\"delegate\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "irtf"
@ -2487,8 +2550,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": null,
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2499,7 +2565,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "isoc"
@ -2507,8 +2579,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": "side",
"create_wiki": true,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2519,7 +2594,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"member\",\"advisor\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "nomcom"
@ -2527,8 +2608,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "lead",
"acts_like_wg": false,
"admin_roles": "[\"lead\"]",
"agenda_type": null,
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2539,7 +2623,13 @@
"has_milestones": true,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\",\"secr\"]",
"req_subm_approval": false,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "program"
@ -2547,8 +2637,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair,secr",
"acts_like_wg": false,
"admin_roles": "[\"chair\",\"secr\"]",
"agenda_type": null,
"create_wiki": true,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.review_requests",
"has_chartering_process": false,
@ -2559,7 +2652,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": true,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"ad\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "review"
@ -2567,8 +2666,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": "side",
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2579,7 +2681,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "rfcedtyp"
@ -2587,8 +2695,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": true,
"admin_roles": "[\"chair\"]",
"agenda_type": "ietf",
"create_wiki": true,
"custom_group_roles": false,
"customize_workflow": true,
"default_tab": "ietf.group.views.group_documents",
"has_chartering_process": true,
@ -2599,7 +2710,13 @@
"has_milestones": true,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": true,
"is_schedulable": true,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\"]",
"show_on_agenda": true
},
"model": "group.groupfeatures",
"pk": "rg"
@ -2607,8 +2724,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": null,
"create_wiki": false,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2619,7 +2739,13 @@
"has_milestones": false,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"liaiman\",\"matman\"]",
"req_subm_approval": true,
"role_order": "[\"liaiman\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "sdo"
@ -2627,8 +2753,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": false,
"admin_roles": "[\"chair\"]",
"agenda_type": "ietf",
"create_wiki": true,
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"has_chartering_process": false,
@ -2639,7 +2768,13 @@
"has_milestones": false,
"has_nonsession_materials": true,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"chair\",\"matman\"]",
"req_subm_approval": false,
"role_order": "[\"chair\",\"member\",\"matman\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
"pk": "team"
@ -2647,8 +2782,11 @@
{
"fields": {
"about_page": "ietf.group.views.group_about",
"admin_roles": "chair",
"acts_like_wg": true,
"admin_roles": "[\"chair\"]",
"agenda_type": "ietf",
"create_wiki": true,
"custom_group_roles": false,
"customize_workflow": true,
"default_tab": "ietf.group.views.group_documents",
"has_chartering_process": true,
@ -2659,7 +2797,13 @@
"has_milestones": true,
"has_nonsession_materials": false,
"has_reviews": false,
"material_types": "slides"
"has_session_materials": true,
"is_schedulable": true,
"material_types": "[\"slides\"]",
"matman_roles": "[\"ad\",\"chair\",\"delegate\",\"secr\"]",
"req_subm_approval": true,
"role_order": "[\"chair\",\"secr\",\"delegate\"]",
"show_on_agenda": true
},
"model": "group.groupfeatures",
"pk": "wg"
@ -3360,7 +3504,9 @@
},
{
"fields": {
"cc": [],
"cc": [
"liaison_admin"
],
"desc": "Recipients for a message that a pending liaison statement needs approval",
"to": [
"liaison_approvers"
@ -4134,6 +4280,14 @@
"model": "mailtrigger.recipient",
"pk": "ipr_updatedipr_holders"
},
{
"fields": {
"desc": "Alias for secretariat liaison administration",
"template": "<statements@ietf.org>"
},
"model": "mailtrigger.recipient",
"pk": "liaison_admin"
},
{
"fields": {
"desc": "The set of people who can approve this liasion statemetns",
@ -10532,7 +10686,7 @@
"fields": {
"command": "xym",
"switch": "--version",
"time": "2018-12-04T00:08:13.259",
"time": "2019-01-21T00:08:23.930",
"used": true,
"version": "xym 0.4"
},
@ -10543,7 +10697,7 @@
"fields": {
"command": "pyang",
"switch": "--version",
"time": "2018-12-04T00:08:13.958",
"time": "2019-01-21T00:08:25.229",
"used": true,
"version": "pyang 1.7.5"
},
@ -10554,7 +10708,7 @@
"fields": {
"command": "yanglint",
"switch": "--version",
"time": "2018-12-04T00:08:14.121",
"time": "2019-01-21T00:08:25.465",
"used": true,
"version": "yanglint 0.14.80"
},
@ -10565,9 +10719,9 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
"time": "2018-12-04T00:08:15.775",
"time": "2019-01-21T00:08:26.897",
"used": true,
"version": "xml2rfc 2.15.2"
"version": "xml2rfc 2.16.3"
},
"model": "utils.versioninfo",
"pk": 4

View file

@ -712,7 +712,7 @@ def setup_reviewer_field(field, review_req):
def get_default_filter_re(person):
if type(person) != Person:
person = Person.objects.get(id=person)
groups_to_avoid = [r.group for r in person.role_set.filter(name='chair',group__type__in=['wg','rg'])]
groups_to_avoid = [ r.group for r in person.role_set.all() if r.name in r.group.features.admin_roles and r.group.features.acts_like_wg ]
if not groups_to_avoid:
return '^draft-%s-.*$' % ( person.last_name().lower(), )
else:

View file

@ -78,7 +78,7 @@ class GroupModelForm(forms.ModelForm):
parent = self.cleaned_data['parent']
type = self.cleaned_data['type']
if type.slug in ('ag','wg','rg') and not parent:
if type.features.acts_like_wg and not parent:
raise forms.ValidationError("This field is required.")
return parent
@ -130,8 +130,7 @@ class RoleForm(forms.Form):
self.group = kwargs.pop('group')
super(RoleForm, self).__init__(*args,**kwargs)
# this form is re-used in roles app, use different roles in select
# TODO: should 'ag' be excluded here as well?
if self.group.type.slug not in ('wg','rg'):
if self.group.features.custom_group_roles:
self.fields['name'].queryset = RoleName.objects.all()
# check for id within parenthesis to ensure name was selected from the list

View file

@ -552,7 +552,7 @@ MAX_WG_DELEGATES = 3
# These states aren't available in forms with drop-down choices for new
# document state:
GROUP_STATES_WITH_EXTRA_PROCESSING = ["sub-pub", "rfc-edit", ]
GROUP_TYPES_LISTED_ACTIVE = ['wg', 'rg', 'ag', 'team', 'dir', 'review', 'area', 'program', ]
DATE_FORMAT = "Y-m-d"
DATETIME_FORMAT = "Y-m-d H:i T"
@ -901,7 +901,8 @@ TRAC_ISSUE_URL_PATTERN = "https://trac.ietf.org/trac/%s/report/1"
TRAC_SVN_DIR_PATTERN = "/a/svn/group/%s"
#TRAC_SVN_URL_PATTERN = "https://svn.ietf.org/svn/group/%s/"
TRAC_CREATE_GROUP_TYPES = ['wg', 'rg', 'area', 'team', 'dir', 'review', 'ag', 'nomcom', ]
# The group types setting was replaced by a group feature entry 10 Jan 2019
#TRAC_CREATE_GROUP_TYPES = ['wg', 'rg', 'area', 'team', 'dir', 'review', 'ag', 'nomcom', ]
TRAC_CREATE_GROUP_STATES = ['bof', 'active', ]
TRAC_CREATE_GROUP_ACRONYMS = ['iesg', 'iaoc', 'ietf', ]
TRAC_CREATE_ADHOC_WIKIS = [

View file

@ -466,7 +466,7 @@ class PreapprovalForm(forms.Form):
if not components[-1]:
raise forms.ValidationError("Name ends with a dash.")
acronym = components[2]
if acronym not in self.groups.values_list('acronym', flat=True):
if acronym not in [ g.acronym for g in self.groups ]:
raise forms.ValidationError("Group acronym not recognized as one you can approve drafts for.")
if Preapproval.objects.filter(name=n):

View file

@ -556,7 +556,7 @@ def preapprovals_for_user(user):
if has_role(user, "Secretariat"):
return res
acronyms = [g.acronym for g in Group.objects.filter(role__person__user=user, type__in=("wg", "rg"))]
acronyms = [g.acronym for g in Group.objects.filter(role__person__user=user, type__features__acts_like_wg=True)]
res = res.filter(name__regex="draft-[^-]+-(%s)-.*" % "|".join(acronyms))

View file

@ -1,4 +1,4 @@
# Copyright The IETF Trust 2007, All Rights Reserved
# Copyright The IETF Trust 2007-2019, All Rights Reserved
import re
import base64
@ -17,7 +17,7 @@ import debug # pyflakes:ignore
from ietf.doc.models import Document, DocAlias, AddedMessageEvent
from ietf.doc.utils import prettify_std_name
from ietf.group.models import Group
from ietf.group.models import Group, Role
from ietf.ietfauth.utils import has_role, role_required
from ietf.mailtrigger.utils import gather_address_lists
from ietf.message.models import Message, MessageAttachment
@ -124,7 +124,9 @@ def api_submit(request):
submission.submitter = user.person.formatted_email()
docevent_from_submission(request, submission, desc="Uploaded new revision")
requires_group_approval = (submission.rev == '00' and submission.group and submission.group.type_id in ("wg", "rg", "ietf", "irtf", "iab", "iana", "rfcedtyp") and not Preapproval.objects.filter(name=submission.name).exists())
requires_group_approval = (submission.rev == '00'
and submission.group and submission.group.features.req_subm_approval
and not Preapproval.objects.filter(name=submission.name).exists())
requires_prev_authors_approval = Document.objects.filter(name=submission.name)
sent_to, desc, docDesc = send_confirmation_emails(request, submission, requires_group_approval, requires_prev_authors_approval)
@ -212,7 +214,9 @@ def submission_status(request, submission_id, access_token=None):
confirmation_list = addrs.to
confirmation_list.extend(addrs.cc)
requires_group_approval = (submission.rev == '00' and submission.group and submission.group.type_id in ("wg", "rg", "ietf", "irtf", "iab", "iana", "rfcedtyp") and not Preapproval.objects.filter(name=submission.name).exists())
requires_group_approval = (submission.rev == '00'
and submission.group and submission.group.features.req_subm_approval
and not Preapproval.objects.filter(name=submission.name).exists())
requires_prev_authors_approval = Document.objects.filter(name=submission.name)
@ -506,10 +510,11 @@ def approvals(request):
@role_required("Secretariat", "Area Director", "WG Chair", "RG Chair")
def add_preapproval(request):
groups = Group.objects.filter(type__in=("wg", "rg")).exclude(state__in=["conclude","bof-conc"]).order_by("acronym").distinct()
groups = Group.objects.filter(type__features__acts_like_wg=True).exclude(state__in=["conclude","bof-conc"]).order_by("acronym").distinct()
if not has_role(request.user, "Secretariat"):
groups = groups.filter(role__person__user=request.user,role__name__in=['ad','chair','delegate','secr'])
groups = [ g for g in groups.filter(role__person__user=request.user)
if Role.objects.filter(group=g, person__user=request.user, name__slug__in=g.type.features.matman_roles).exists() ]
if request.method == "POST":
form = PreapprovalForm(request.POST)

View file

@ -19,7 +19,7 @@ from django.template.loader import render_to_string
import debug # pyflakes:ignore
from ietf.group.models import Group, GroupURL
from ietf.group.models import Group, GroupURL, GroupFeatures
from ietf.utils.pipe import pipe
logtag = __name__.split('.')[-1]
@ -216,7 +216,7 @@ class Command(BaseCommand):
self.maybe_add_group_url(group, 'Issue tracker', settings.TRAC_ISSUE_URL_PATTERN % group.acronym)
# Use custom assets (if any) from the master setup
self.symlink_to_master_assets(group.trac_dir, env)
if group.type_id in ['wg', 'rg', 'ag', ]:
if group.features.acts_like_wg:
self.add_wg_draft_states(group, env)
self.add_custom_wiki_pages(group, env)
self.add_default_wiki_pages(env)
@ -338,7 +338,8 @@ class Command(BaseCommand):
if not os.path.exists(os.path.dirname(self.svn_dir_pattern)):
raise CommandError('The SVN base direcory specified for the SVN directories (%s) does not exist.' % os.path.dirname(self.svn_dir_pattern))
gfilter = Q(type__slug__in=settings.TRAC_CREATE_GROUP_TYPES, state__slug__in=settings.TRAC_CREATE_GROUP_STATES)
gtypes = [ f.type for f in GroupFeatures.objects.filter(create_wiki=True) ]
gfilter = Q(type__in=gtypes, state__slug__in=settings.TRAC_CREATE_GROUP_STATES)
gfilter |= Q(acronym__in=settings.TRAC_CREATE_GROUP_ACRONYMS)
groups = Group.objects.filter(gfilter).order_by('acronym')

View file

@ -1,4 +1,6 @@
# Copyright The IETF Trust 2007, All Rights Reserved
# Copyright The IETF Trust 2007-2019, All Rights Reserved
# -*- coding: utf-8 -*-
# Portion Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
# All rights reserved. Contact: Pasi Eronen <pasi.eronen@nokia.com>
@ -422,6 +424,8 @@ class CoverageTest(unittest.TestCase):
if issubclass(cl, ModelOperation) or issubclass(cl, FieldOperation):
ops.append(('schema', cl.__name__))
elif issubclass(cl, Operation):
if getattr(op, 'code', None) and getattr(op.code, 'interleavable', None) == True:
continue
ops.append(('data', cl.__name__))
else:
raise RuntimeError("Found unexpected operation type in migration: %s" % (op))
@ -441,10 +445,15 @@ class CoverageTest(unittest.TestCase):
unreleased.append((node, op, nm))
# gather the transitions in operation types. We'll allow 1
# transition, but not 2 or more.
mixed = [ unreleased[i] for i in range(1,len(unreleased)) if unreleased[i][1] != unreleased[i-1][1] ]
for s in range(len(unreleased)):
# ignore leading data migrations, they run with the production
# schema so can take any time they like
if unreleased[s][1] != 'data':
break
mixed = [ unreleased[i] for i in range(s+1,len(unreleased)) if unreleased[i][1] != unreleased[i-1][1] ]
if len(mixed) > 1:
raise self.failureException('Found interleaved schema and data operations in unreleased migrations;'
' please see if they can be re-ordered with all schema migrations before the data migrations:\n'
' please see if they can be re-ordered with all data migrations before the schema migrations:\n'
+('\n'.join([' %-6s: %-12s, %s (%s)'% (op, node.key[0], node.key[1], nm) for (node, op, nm) in unreleased ])))
class IetfTestRunner(DiscoverRunner):

View file

@ -10,7 +10,7 @@ coverage>=4.0.1,!=4.0.2
#cssselect>=0.6.1 # for PyQuery
decorator>=4.0.4
defusedxml>=0.4.1 # for TastyPie when ussing xml; not a declared dependency
Django>=1.11,<1.12
Django>=1.11,!=1.11.18,<1.12 # 1.11.18 has problems exporting BinaryField from django.db.models
django-bcrypt>=0.9.2 # for the BCrypt password hasher option. Remove when all bcrypt upgraded to argon2
django-bootstrap3>=8.2.1,<9.0.0
django-cors-headers>=2.4.0