From 2daef52bea0732b69886d20b760b5c18169350af Mon Sep 17 00:00:00 2001 From: Henrik Levkowetz Date: Thu, 12 Jul 2018 10:51:48 +0000 Subject: [PATCH] This commit replaces the code defined group features with features held in a database table: - Added a GroupFeatures model to the group models, and removed the old features.py - Added a agenda type for future use in showing different group types on different agendas. - Renamed the group feature has_materials to has_nonsession_materials. - Added API resources and admin support for the new tables. - Added a Directorate (with reviews) group type as complement to Directorate, to distinguish between directorates with and without reviews. - Adjusted tests as needed. - Updated the fixtures, and fixed the generate_fixtures script to include the new AgendaTypeName objects. There still exists about 70 instances of code comparing the group type with a list of types; most of these should probably be replaced with new features, instead, to make it possible to add new group types through the database table, rather than having to edit the code. That was the purpose of this refactoring from the start, but the presence of this large number of comparisons of group type against lists of types defeats the goal until we add appropriate features and replace the group type list comparisons. - Legacy-Id: 15316 --- ietf/doc/templatetags/active_groups_menu.py | 2 +- ietf/doc/tests_review.py | 24 +- ietf/doc/views_doc.py | 2 +- ietf/doc/views_material.py | 7 +- ietf/group/admin.py | 21 +- ietf/group/factories.py | 2 +- ietf/group/features.py | 46 --- ietf/group/forms.py | 2 +- ...2_groupfeatures_historicalgroupfeatures.py | 75 ++++ .../migrations/0003_groupfeatures_data.py | 299 ++++++++++++++ ietf/group/models.py | 48 ++- ietf/group/resources.py | 64 ++- ietf/group/tests_info.py | 14 +- ietf/group/utils.py | 7 +- ietf/group/views.py | 16 +- ietf/meeting/views.py | 4 + ietf/name/admin.py | 3 +- ietf/name/fixtures/names.json | 387 +++++++++++++++++- ietf/name/generate_fixtures.py | 3 + ietf/name/migrations/0002_agendatypename.py | 28 ++ .../migrations/0003_agendatypename_data.py | 55 +++ ietf/name/models.py | 2 + ietf/name/resources.py | 35 +- ietf/review/factories.py | 4 +- ietf/settings.py | 2 +- ietf/templates/meeting/materials.html | 39 ++ ietf/urls.py | 2 +- ietf/utils/test_data.py | 6 +- 28 files changed, 1093 insertions(+), 106 deletions(-) delete mode 100644 ietf/group/features.py create mode 100644 ietf/group/migrations/0002_groupfeatures_historicalgroupfeatures.py create mode 100644 ietf/group/migrations/0003_groupfeatures_data.py create mode 100644 ietf/name/migrations/0002_agendatypename.py create mode 100644 ietf/name/migrations/0003_agendatypename_data.py diff --git a/ietf/doc/templatetags/active_groups_menu.py b/ietf/doc/templatetags/active_groups_menu.py index 3a566091a..6bf50f850 100644 --- a/ietf/doc/templatetags/active_groups_menu.py +++ b/ietf/doc/templatetags/active_groups_menu.py @@ -7,7 +7,7 @@ register = template.Library() @register.simple_tag def active_groups_menu(): - parents = GroupTypeName.objects.filter(slug__in=['ag','area','team','dir','program']) + parents = GroupTypeName.objects.filter(slug__in=['ag','area','team','dir','review','program']) for p in parents: p.menu_url = '/%s/'%p.slug return render_to_string('base/menu_active_groups.html', { 'parents': parents }) diff --git a/ietf/doc/tests_review.py b/ietf/doc/tests_review.py index d53840191..05e43d8c1 100644 --- a/ietf/doc/tests_review.py +++ b/ietf/doc/tests_review.py @@ -52,8 +52,8 @@ class ReviewTests(TestCase): doc = WgDraftFactory(group__acronym='mars',rev='01') NewRevisionDocEventFactory(doc=doc,rev='01') RoleFactory(name_id='chair',person__user__username='marschairman',group=doc.group) - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) - review_team3 = ReviewTeamFactory(acronym="reviewteam3", name="Review Team3", type_id="dir", list_email="reviewteam3@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team3 = ReviewTeamFactory(acronym="reviewteam3", name="Review Team3", type_id="review", list_email="reviewteam3@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team3,person=rev_role.person,name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') @@ -111,7 +111,7 @@ class ReviewTests(TestCase): def test_doc_page(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -132,7 +132,7 @@ class ReviewTests(TestCase): def test_review_request(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -145,7 +145,7 @@ class ReviewTests(TestCase): def test_close_request(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -268,7 +268,7 @@ class ReviewTests(TestCase): def test_assign_reviewer(self): doc = WgDraftFactory(pages=2) - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',person__name=u'Some Reviewer',name_id='reviewer') RoleFactory(group=review_team,person__user__username='marschairman',person__name=u'WG Cháir Man',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') @@ -382,7 +382,7 @@ class ReviewTests(TestCase): def test_accept_reviewer_assignment(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='requested',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -403,7 +403,7 @@ class ReviewTests(TestCase): def test_reject_reviewer_assignment(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -476,7 +476,7 @@ class ReviewTests(TestCase): def test_search_mail_archive(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -523,7 +523,7 @@ class ReviewTests(TestCase): def setup_complete_review_test(self): doc = WgDraftFactory(group__acronym='mars',rev='01') NewRevisionDocEventFactory(doc=doc,rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -804,7 +804,7 @@ class ReviewTests(TestCase): def test_edit_comment(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) @@ -825,7 +825,7 @@ class ReviewTests(TestCase): def test_edit_deadline(self): doc = WgDraftFactory(group__acronym='mars',rev='01') - review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + review_team = ReviewTeamFactory(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) rev_role = RoleFactory(group=review_team,person__user__username='reviewer',person__user__email='reviewer@example.com',name_id='reviewer') RoleFactory(group=review_team,person__user__username='reviewsecretary',person__user__email='reviewsecretary@example.com',name_id='secr') review_req = ReviewRequestFactory(doc=doc,team=review_team,type_id='early',state_id='accepted',requested_by=rev_role.person,reviewer=rev_role.person.email_set.first(),deadline=datetime.datetime.now()+datetime.timedelta(days=20)) diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 91b1d2d5d..643e029cb 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -572,7 +572,7 @@ def document_main(request, name, rev=None): latest_rev=latest_rev, snapshot=snapshot, can_manage_material=can_manage_material, - in_group_materials_types = doc.group and doc.group.features.has_materials and doc.type_id in doc.group.features.material_types, + in_group_materials_types = doc.group and doc.group.features.has_nonsession_materials and doc.type_id in doc.group.features.material_types, other_types=other_types, presentations=presentations, )) diff --git a/ietf/doc/views_material.py b/ietf/doc/views_material.py index 0dae3d4ec..dbbc98369 100644 --- a/ietf/doc/views_material.py +++ b/ietf/doc/views_material.py @@ -20,7 +20,7 @@ from ietf.group.utils import can_manage_materials @login_required def choose_material_type(request, acronym): group = get_object_or_404(Group, acronym=acronym) - if not group.features.has_materials: + if not group.features.has_nonsession_materials: raise Http404 return render(request, 'doc/material/choose_material_type.html', { @@ -85,7 +85,7 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None): if action == "new": group = get_object_or_404(Group, acronym=acronym) - if not group.features.has_materials: + if not group.features.has_nonsession_materials: raise Http404 doc = None @@ -95,7 +95,8 @@ def edit_material(request, name=None, acronym=None, action=None, doc_type=None): group = doc.group document_type = doc.type - if document_type not in DocTypeName.objects.filter(slug__in=group.features.material_types) and document_type.slug not in ['minutes','agenda','bluesheets',]: + if (document_type not in DocTypeName.objects.filter(slug__in=group.features.material_types) + and document_type.slug not in ['minutes','agenda','bluesheets',]): raise Http404 diff --git a/ietf/group/admin.py b/ietf/group/admin.py index a0b645442..96e57895b 100644 --- a/ietf/group/admin.py +++ b/ietf/group/admin.py @@ -10,7 +10,7 @@ from django.utils.encoding import force_unicode from django.utils.html import escape from django.utils.translation import ugettext as _ -from ietf.group.models import (Group, GroupHistory, GroupEvent, GroupURL, GroupMilestone, +from ietf.group.models import (Group, GroupFeatures, GroupHistory, GroupEvent, GroupURL, GroupMilestone, GroupMilestoneHistory, GroupStateTransitions, Role, RoleHistory, ChangeStateGroupEvent, MilestoneGroupEvent, ) @@ -100,6 +100,25 @@ class GroupAdmin(admin.ModelAdmin): 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_reviews', + 'material_types', + 'agenda_type', + 'admin_roles', + 'about_page', + 'default_tab', + ] +admin.site.register(GroupFeatures, GroupFeaturesAdmin) + class GroupHistoryAdmin(admin.ModelAdmin): list_display = ["time", "acronym", "name", "type"] list_display_links = ["acronym", "name"] diff --git a/ietf/group/factories.py b/ietf/group/factories.py index df7f752c4..aba181949 100644 --- a/ietf/group/factories.py +++ b/ietf/group/factories.py @@ -19,7 +19,7 @@ class ReviewTeamFactory(factory.DjangoModelFactory): class Meta: model = Group - type_id = 'dir' + type_id = 'review' name = factory.Faker('sentence',nb_words=6) acronym = factory.Sequence(lambda n: 'acronym%d' %n) state_id = 'active' diff --git a/ietf/group/features.py b/ietf/group/features.py deleted file mode 100644 index 1c0700419..000000000 --- a/ietf/group/features.py +++ /dev/null @@ -1,46 +0,0 @@ -class GroupFeatures(object): - """Configuration of group pages and processes to have this collected - in one place rather than scattered over the group page views.""" - - has_milestones = False - has_chartering_process = False - has_documents = False # i.e. drafts/RFCs - has_dependencies = False # Do dependency graphs for group documents make sense? - has_materials = False - has_reviews = False - has_default_jabber = False - customize_workflow = False - about_page = "ietf.group.views.group_about" - default_tab = about_page - material_types = ["slides"] - admin_roles = ["chair"] - - def __init__(self, group): - # TODO: should 'ag' be in this list - if group.type_id in ("wg", "rg"): - self.has_milestones = True - self.has_chartering_process = True - self.has_documents = True - self.customize_workflow = True - self.has_default_jabber = True - self.has_dependencies = True - self.default_tab = "ietf.group.views.group_documents" - elif group.type_id in ("team",): - self.has_materials = True - self.default_tab = "ietf.group.views.group_about" - elif group.type_id in ("program",): - self.has_documents = True - self.has_milestones = True - self.admin_roles = ["lead",] - elif group.type_id == "dir": - self.admin_roles = ["chair", "secr"] - - if self.has_chartering_process: - self.about_page = "ietf.group.views.group_about" - - from ietf.review.utils import active_review_teams - if group in active_review_teams(): - self.has_reviews = True - import ietf.group.views - self.default_tab = ietf.group.views.review_requests - diff --git a/ietf/group/forms.py b/ietf/group/forms.py index 023d2e0ab..1f04cc827 100644 --- a/ietf/group/forms.py +++ b/ietf/group/forms.py @@ -27,7 +27,7 @@ MAX_GROUP_DELEGATES = 3 def roles_for_group_type(group_type): roles = ["chair", "secr", "techadv", "delegate", ] - if group_type == "dir": + if group_type == "review": roles.append("reviewer") return roles diff --git a/ietf/group/migrations/0002_groupfeatures_historicalgroupfeatures.py b/ietf/group/migrations/0002_groupfeatures_historicalgroupfeatures.py new file mode 100644 index 000000000..ac5bcbba0 --- /dev/null +++ b/ietf/group/migrations/0002_groupfeatures_historicalgroupfeatures.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.13 on 2018-07-10 15:58 +from __future__ import unicode_literals + +import django.core.validators +import django.db.models.deletion + +from django.conf import settings +from django.db import migrations, models + + +import debug # pyflakes:ignore + +import ietf.utils.models + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('name', '0002_agendatypename'), + ('group', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='GroupFeatures', + fields=[ + ('type', ietf.utils.models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='name.GroupTypeName')), + ('has_milestones', models.BooleanField(default=False, verbose_name=b'Milestones')), + ('has_chartering_process', models.BooleanField(default=False, verbose_name=b'Chartering')), + ('has_documents', models.BooleanField(default=False, verbose_name=b'Documents')), + ('has_dependencies', models.BooleanField(default=False, verbose_name=b'Dependencies')), + ('has_nonsession_materials', models.BooleanField(default=False, verbose_name=b'Materials')), + ('has_meetings', models.BooleanField(default=False, verbose_name=b'Meetings')), + ('has_reviews', models.BooleanField(default=False, verbose_name=b'Reviews')), + ('has_default_jabber', models.BooleanField(default=False, verbose_name=b'Jabber')), + ('customize_workflow', models.BooleanField(default=False, verbose_name=b'Workflow')), + ('about_page', models.CharField(default=b'ietf.group.views.group_about', max_length=64)), + ('default_tab', models.CharField(default=b'ietf.group.views.group_about', max_length=64)), + ('material_types', models.CharField(default=b'slides', max_length=64, validators=[django.core.validators.RegexValidator(code=b'invalid', message=b'Enter a comma-separated list of material types', regex=b'[a-z0-9_-]+(,[a-z0-9_-]+)*')])), + ('admin_roles', models.CharField(default=b'chair', 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_-]+)*')])), + ('agenda_type', models.ForeignKey(default=b'ietf', null=True, on_delete=django.db.models.deletion.CASCADE, to='name.AgendaTypeName')), + ], + ), + migrations.CreateModel( + name='HistoricalGroupFeatures', + fields=[ + ('has_milestones', models.BooleanField(default=False, verbose_name=b'Milestones')), + ('has_chartering_process', models.BooleanField(default=False, verbose_name=b'Chartering')), + ('has_documents', models.BooleanField(default=False, verbose_name=b'Documents')), + ('has_dependencies', models.BooleanField(default=False, verbose_name=b'Dependencies')), + ('has_nonsession_materials', models.BooleanField(default=False, verbose_name=b'Materials')), + ('has_meetings', models.BooleanField(default=False, verbose_name=b'Meetings')), + ('has_reviews', models.BooleanField(default=False, verbose_name=b'Reviews')), + ('has_default_jabber', models.BooleanField(default=False, verbose_name=b'Jabber')), + ('customize_workflow', models.BooleanField(default=False, verbose_name=b'Workflow')), + ('about_page', models.CharField(default=b'ietf.group.views.group_about', max_length=64)), + ('default_tab', models.CharField(default=b'ietf.group.views.group_about', max_length=64)), + ('material_types', models.CharField(default=b'slides', max_length=64, validators=[django.core.validators.RegexValidator(code=b'invalid', message=b'Enter a comma-separated list of material types', regex=b'[a-z0-9_-]+(,[a-z0-9_-]+)*')])), + ('admin_roles', models.CharField(default=b'chair', 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_-]+)*')])), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_date', models.DateTimeField()), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('agenda_type', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='name.AgendaTypeName')), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ('type', ietf.utils.models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='name.GroupTypeName')), + ], + options={ + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': 'history_date', + 'verbose_name': 'historical group features', + }, + ), + ] diff --git a/ietf/group/migrations/0003_groupfeatures_data.py b/ietf/group/migrations/0003_groupfeatures_data.py new file mode 100644 index 000000000..58760b96a --- /dev/null +++ b/ietf/group/migrations/0003_groupfeatures_data.py @@ -0,0 +1,299 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.13 on 2018-07-10 15:58 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations + +import debug # pyflakes:ignore + +from ietf.review.utils import active_review_teams + +group_type_features = { + u'ag': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': True, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'area': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'dir': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair,secr', + 'agenda_type': None, + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'review': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair,secr', + 'agenda_type': None, + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.review_requests', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': True, + 'material_types': 'slides'}, + u'iab': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': True, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'ietf': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': True, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'individ': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': None, + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'irtf': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'isoc': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': None, + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'nomcom': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'side', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'program': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'lead', + 'agenda_type': None, + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': True, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': True, + 'has_reviews': False, + 'material_types': 'slides'}, + u'rfcedtyp': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'side', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'rg': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': True, + 'default_tab': 'ietf.group.views.group_documents', + 'has_chartering_process': True, + 'has_default_jabber': True, + 'has_dependencies': True, + 'has_documents': True, + 'has_meetings': True, + 'has_nonsession_materials': False, + 'has_milestones': True, + 'has_reviews': False, + 'material_types': 'slides'}, + u'sdo': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': None, + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': False, + 'has_nonsession_materials': False, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'team': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': False, + 'default_tab': 'ietf.group.views.group_about', + 'has_chartering_process': False, + 'has_default_jabber': False, + 'has_dependencies': False, + 'has_documents': False, + 'has_meetings': True, + 'has_nonsession_materials': True, + 'has_milestones': False, + 'has_reviews': False, + 'material_types': 'slides'}, + u'wg': { + 'about_page': 'ietf.group.views.group_about', + 'admin_roles': 'chair', + 'agenda_type': 'ietf', + 'customize_workflow': True, + 'default_tab': 'ietf.group.views.group_documents', + 'has_chartering_process': True, + 'has_default_jabber': True, + 'has_dependencies': True, + 'has_documents': True, + 'has_meetings': True, + 'has_nonsession_materials': False, + 'has_milestones': True, + 'has_reviews': False, + 'material_types': 'slides'}, +} + + +def forward(apps, schema_editor): + Group = apps.get_model('group', 'Group') + GroupTypeName = apps.get_model('name', 'GroupTypeName') + GroupFeatures = apps.get_model('group', 'GroupFeatures') + AgendaTypeName = apps.get_model('name', 'AgendaTypeName') + for type in group_type_features: + features = group_type_features[type] + features['type_id'] = type + if features['agenda_type']: + features['agenda_type'] = AgendaTypeName.objects.get(slug=features['agenda_type']) + GroupFeatures.objects.create(**features) + dir = GroupTypeName.objects.get(slug='dir') + review = GroupTypeName.objects.create(slug='review', name='Directorate (with reviews)', desc='', used=True, order=0) + review_teams = [ g.acronym for g in active_review_teams() ] + for group in Group.objects.filter(type=dir): + if group.acronym in review_teams: + group.type = review + group.save() + +def reverse(apps, schema_editor): + Group = apps.get_model('group', 'Group') + GroupFeatures = apps.get_model('group', 'GroupFeatures') + GroupTypeName = apps.get_model('name', 'GroupTypeName') + dir = GroupTypeName.objects.get(slug='dir') + review = GroupTypeName.objects.get(slug='review') + for group in Group.objects.filter(type=review): + debug.show('group.acronym') + group.type = dir + group.save() + for entry in GroupFeatures.objects.all(): + entry.delete() + review.delete() + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('name', '0002_agendatypename'), + ('group', '0002_groupfeatures_historicalgroupfeatures'), + ] + + operations = [ + migrations.RunPython(forward, reverse), + ] diff --git a/ietf/group/models.py b/ietf/group/models.py index d9f8d9b71..0197f34cf 100644 --- a/ietf/group/models.py +++ b/ietf/group/models.py @@ -6,12 +6,16 @@ import os import re from urlparse import urljoin +from django.core.validators import RegexValidator from django.db import models +from django.db.models.deletion import CASCADE + +from simple_history.models import HistoricalRecords import debug # pyflakes:ignore from ietf.group.colors import fg_group_colors, bg_group_colors -from ietf.name.models import GroupStateName, GroupTypeName, DocTagName, GroupMilestoneStateName, RoleName +from ietf.name.models import GroupStateName, GroupTypeName, DocTagName, GroupMilestoneStateName, RoleName, AgendaTypeName from ietf.person.models import Email, Person from ietf.utils.mail import formataddr from ietf.utils import log @@ -48,8 +52,12 @@ class GroupInfo(models.Model): @property def features(self): if not hasattr(self, "features_cache"): - from ietf.group.features import GroupFeatures - self.features_cache = GroupFeatures(self) + 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 return self.features_cache def about_url(self): @@ -193,6 +201,40 @@ class Group(GroupInfo): desc = [ p for p in re.split('\r?\n\s*\r?\n\s*', text) if p.strip() ][0] return desc + +validate_comma_separated_materials = RegexValidator( + regex=r"[a-z0-9_-]+(,[a-z0-9_-]+)*", + message="Enter a comma-separated list of material types", + code='invalid', +) +validate_comma_separated_roles = RegexValidator( + regex=r"[a-z0-9_-]+(,[a-z0-9_-]+)*", + message="Enter a comma-separated list of role slugs", + code='invalid', +) + +class GroupFeatures(models.Model): + type = ForeignKey(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_meetings = models.BooleanField("Meetings", default=False) + has_reviews = models.BooleanField("Reviews", default=False) + has_default_jabber = models.BooleanField("Jabber", default=False) + customize_workflow = models.BooleanField("Workflow", 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]) + + class GroupHistory(GroupInfo): group = ForeignKey(Group, related_name='history_set') acronym = models.CharField(max_length=40) diff --git a/ietf/group/resources.py b/ietf/group/resources.py index c2210cba3..ef3c18584 100644 --- a/ietf/group/resources.py +++ b/ietf/group/resources.py @@ -9,7 +9,7 @@ from ietf import api from ietf.group.models import (Group, GroupStateTransitions, GroupMilestone, GroupHistory, GroupURL, Role, GroupEvent, RoleHistory, GroupMilestoneHistory, MilestoneGroupEvent, - ChangeStateGroupEvent) + ChangeStateGroupEvent, GroupFeatures, HistoricalGroupFeatures) from ietf.person.resources import PersonResource @@ -269,3 +269,65 @@ class ChangeStateGroupEventResource(ModelResource): } api.group.register(ChangeStateGroupEventResource()) +from ietf.name.resources import GroupTypeNameResource, AgendaTypeNameResource +class GroupFeaturesResource(ModelResource): + type = ToOneField(GroupTypeNameResource, 'type') + agenda_type = ToOneField(AgendaTypeNameResource, 'agenda_type', null=True) + class Meta: + queryset = GroupFeatures.objects.all() + serializer = api.Serializer() + cache = SimpleCache() + #resource_name = 'groupfeatures' + filtering = { + "has_milestones": ALL, + "has_chartering_process": ALL, + "has_documents": ALL, + "has_dependencies": ALL, + "has_nonsession_materials": ALL, + "has_meetings": ALL, + "has_reviews": ALL, + "has_default_jabber": ALL, + "customize_workflow": ALL, + "about_page": ALL, + "default_tab": ALL, + "material_types": ALL, + "admin_roles": ALL, + "type": ALL_WITH_RELATIONS, + "agenda_type": ALL_WITH_RELATIONS, + } +api.group.register(GroupFeaturesResource()) + +from ietf.name.resources import GroupTypeNameResource, AgendaTypeNameResource +from ietf.utils.resources import UserResource +class HistoricalGroupFeaturesResource(ModelResource): + type = ToOneField(GroupTypeNameResource, 'type', null=True) + agenda_type = ToOneField(AgendaTypeNameResource, 'agenda_type', null=True) + history_user = ToOneField(UserResource, 'history_user', null=True) + class Meta: + queryset = HistoricalGroupFeatures.objects.all() + serializer = api.Serializer() + cache = SimpleCache() + #resource_name = 'historicalgroupfeatures' + filtering = { + "has_milestones": ALL, + "has_chartering_process": ALL, + "has_documents": ALL, + "has_dependencies": ALL, + "has_nonsession_materials": ALL, + "has_meetings": ALL, + "has_reviews": ALL, + "has_default_jabber": ALL, + "customize_workflow": ALL, + "about_page": ALL, + "default_tab": ALL, + "material_types": ALL, + "admin_roles": ALL, + "history_id": ALL, + "history_change_reason": ALL, + "history_date": ALL, + "history_type": ALL, + "type": ALL_WITH_RELATIONS, + "agenda_type": ALL_WITH_RELATIONS, + "history_user": ALL_WITH_RELATIONS, + } +api.group.register(HistoricalGroupFeaturesResource()) diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py index ca9a10a47..26ee1cd10 100644 --- a/ietf/group/tests_info.py +++ b/ietf/group/tests_info.py @@ -65,9 +65,9 @@ class GroupPagesTests(TestCase): self.assertTrue(group.name in unicontent(r)) self.assertTrue(group.ad_role().person.plain_name() in unicontent(r)) - for t in ('rg','area','ag','dir','team','program'): + for t in ('rg','area','ag','dir','review','team','program'): g = GroupFactory.create(type_id=t,state_id='active') - if t=='dir': + if t in ['dir','review']: g.parent = GroupFactory.create(type_id='area',state_id='active') g.save() url = urlreverse('ietf.group.views.active_groups', kwargs=dict(group_type=t)) @@ -81,7 +81,7 @@ class GroupPagesTests(TestCase): self.assertTrue("Directorate" in unicontent(r)) self.assertTrue("AG" in unicontent(r)) - for slug in GroupTypeName.objects.exclude(slug__in=['wg','rg','ag','area','dir','team', 'program']).values_list('slug',flat=True): + for slug in GroupTypeName.objects.exclude(slug__in=['wg','rg','ag','area','dir','review','team', 'program']).values_list('slug',flat=True): with self.assertRaises(NoReverseMatch): url=urlreverse('ietf.group.views.active_groups', kwargs=dict(group_type=slug)) @@ -286,6 +286,7 @@ class GroupPagesTests(TestCase): 'ag' : ['secretary', ], 'team' : ['secretary',], # The code currently doesn't let ads edit teams or directorates. Maybe it should. 'dir' : ['secretary',], + 'review' : ['secretary',], 'program' : ['secretary', 'iab-member'], } @@ -302,9 +303,10 @@ class GroupPagesTests(TestCase): setup_role(g,'chair') test_groups.append(g) - g = GroupFactory(type_id='dir') - setup_role(g,'secr') - test_groups.append(g) + for t in ['dir','review',]: + g = GroupFactory(type_id=t) + setup_role(g,'secr') + test_groups.append(g) g = GroupFactory(type_id='program') setup_role(g, 'lead') diff --git a/ietf/group/utils.py b/ietf/group/utils.py index 3fcdff20f..5615a2280 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -175,13 +175,14 @@ def construct_group_menu_context(request, group, selected, group_type, others): entries.append(("About", urlreverse("ietf.group.views.group_about", kwargs=kwargs))) if group.features.has_documents: entries.append(("Documents", urlreverse("ietf.group.views.group_documents", kwargs=kwargs))) - if group.features.has_materials and get_group_materials(group).exists(): + if group.features.has_nonsession_materials and get_group_materials(group).exists(): entries.append(("Materials", urlreverse("ietf.group.views.materials", kwargs=kwargs))) if group.features.has_reviews: import ietf.group.views entries.append(("Review requests", urlreverse(ietf.group.views.review_requests, kwargs=kwargs))) entries.append(("Reviewers", urlreverse(ietf.group.views.reviewer_overview, kwargs=kwargs))) - if group.type_id in ('rg','wg','ag','team'): + + if group.features.has_meetings: # type_id in ('rg','wg','ag','team'): entries.append(("Meetings", urlreverse("ietf.group.views.meetings", kwargs=kwargs))) entries.append(("History", urlreverse("ietf.group.views.history", kwargs=kwargs))) entries.append(("Photos", urlreverse("ietf.group.views.group_photos", kwargs=kwargs))) @@ -211,7 +212,7 @@ def construct_group_menu_context(request, group, selected, group_type, others): import ietf.community.views actions.append((u'Manage document list', urlreverse(ietf.community.views.manage_list, kwargs=kwargs))) - if group.features.has_materials and can_manage_materials(request.user, group): + if group.features.has_nonsession_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.features.has_reviews and can_manage_review_requests_for_team(request.user, group): diff --git a/ietf/group/views.py b/ietf/group/views.py index 75a563299..3e7343099 100644 --- a/ietf/group/views.py +++ b/ietf/group/views.py @@ -124,7 +124,7 @@ def roles(group, role_name): def roles_for_group_type(group_type): roles = ["chair", "secr", "techadv", "delegate", ] - if group_type == "dir": + if group_type == "review": roles.append("reviewer") return roles @@ -293,13 +293,15 @@ def active_groups(request, group_type=None): return active_teams(request) elif group_type == "dir": return active_dirs(request) + elif group_type == "review": + return active_review_dirs(request) elif group_type == "program": return active_programs(request) else: raise Http404 def active_group_types(request): - grouptypes = GroupTypeName.objects.filter(slug__in=['wg','rg','ag','team','dir','area','program']) + grouptypes = GroupTypeName.objects.filter(slug__in=['wg','rg','ag','team','dir','review','area','program']) return render(request, 'group/active_groups.html', {'grouptypes':grouptypes}) def active_dirs(request): @@ -310,6 +312,14 @@ def active_dirs(request): group.secretaries = sorted(roles(group, "secr"), key=extract_last_name) return render(request, 'group/active_dirs.html', {'dirs' : dirs }) +def active_review_dirs(request): + dirs = Group.objects.filter(type="review", state="active").order_by("name") + for group in dirs: + group.chairs = sorted(roles(group, "chair"), key=extract_last_name) + group.ads = sorted(roles(group, "ad"), key=extract_last_name) + group.secretaries = sorted(roles(group, "secr"), key=extract_last_name) + return render(request, 'group/active_review_dirs.html', {'dirs' : dirs }) + def active_teams(request): teams = Group.objects.filter(type="team", state="active").order_by("name") for group in teams: @@ -646,7 +656,7 @@ def history(request, acronym, group_type=None): def materials(request, acronym, group_type=None): group = get_group_or_404(acronym, group_type) - if not group.features.has_materials: + if not group.features.has_nonsession_materials: raise Http404 docs = get_group_materials(group).order_by("type__order", "-time").select_related("type") diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 66e49a03b..337f32811 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -130,6 +130,9 @@ def materials(request, num=None): irtf = sessions.filter(group__parent__acronym = 'irtf') training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['session', 'other', ]) iab = sessions.filter(group__parent__acronym = 'iab') + other = sessions.filter(type_id__in=['session'], group__type__features__has_meetings = True) + for ss in [plenaries, ietf, irtf, training, iab]: + other = other.exclude(pk__in=[s.pk for s in ss]) for topic in [plenaries, ietf, training, irtf, iab]: for event in topic: @@ -145,6 +148,7 @@ def materials(request, num=None): 'training': training, 'irtf': irtf, 'iab': iab, + 'other': other, 'cut_off_date': cut_off_date, 'cor_cut_off_date': cor_cut_off_date, 'submission_started': now > begin_date, diff --git a/ietf/name/admin.py b/ietf/name/admin.py index 1fc9f85d3..9eb0f5cc5 100644 --- a/ietf/name/admin.py +++ b/ietf/name/admin.py @@ -1,7 +1,7 @@ from django.contrib import admin from ietf.name.models import ( - BallotPositionName, ConstraintName, ContinentName, CountryName, DBTemplateTypeName, + AgendaTypeName, BallotPositionName, ConstraintName, ContinentName, CountryName, DBTemplateTypeName, DocRelationshipName, DocReminderTypeName, DocTagName, DocTypeName, DraftSubmissionStateName, FeedbackTypeName, FormalLanguageName, GroupMilestoneStateName, GroupStateName, GroupTypeName, ImportantDateName, IntendedStdLevelName, IprDisclosureStateName, IprEventTypeName, @@ -45,6 +45,7 @@ class ImportantDateNameAdmin(NameAdmin): ordering = ('-used','default_offset_days',) admin.site.register(ImportantDateName,ImportantDateNameAdmin) +admin.site.register(AgendaTypeName, NameAdmin) admin.site.register(BallotPositionName, NameAdmin) admin.site.register(ConstraintName, NameAdmin) admin.site.register(ContinentName, NameAdmin) diff --git a/ietf/name/fixtures/names.json b/ietf/name/fixtures/names.json index dbf5e4ca9..657f79825 100644 --- a/ietf/name/fixtures/names.json +++ b/ietf/name/fixtures/names.json @@ -2328,6 +2328,326 @@ "model": "doc.statetype", "pk": "statchg" }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": true, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "ag" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "area" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair,secr", + "agenda_type": null, + "customize_workflow": false, + "default_tab": "ietf.group.views.review_requests", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "dir" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": true, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "iab" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": true, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "ietf" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": null, + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "individ" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "irtf" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": null, + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "isoc" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "side", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "nomcom" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "lead", + "agenda_type": null, + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": true, + "has_meetings": false, + "has_milestones": true, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "program" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair,secr", + "agenda_type": null, + "customize_workflow": false, + "default_tab": "ietf.group.views.review_requests", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": true, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "review" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "side", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "rfcedtyp" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": true, + "default_tab": "ietf.group.views.group_documents", + "has_chartering_process": true, + "has_default_jabber": true, + "has_dependencies": true, + "has_documents": true, + "has_meetings": true, + "has_milestones": true, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "rg" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": null, + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": false, + "has_milestones": false, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "sdo" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": false, + "default_tab": "ietf.group.views.group_about", + "has_chartering_process": false, + "has_default_jabber": false, + "has_dependencies": false, + "has_documents": false, + "has_meetings": true, + "has_milestones": false, + "has_nonsession_materials": true, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "team" + }, + { + "fields": { + "about_page": "ietf.group.views.group_about", + "admin_roles": "chair", + "agenda_type": "ietf", + "customize_workflow": true, + "default_tab": "ietf.group.views.group_documents", + "has_chartering_process": true, + "has_default_jabber": true, + "has_dependencies": true, + "has_documents": true, + "has_meetings": true, + "has_milestones": true, + "has_nonsession_materials": false, + "has_reviews": false, + "material_types": "slides" + }, + "model": "group.groupfeatures", + "pk": "wg" + }, { "fields": { "cc": [ @@ -2544,6 +2864,8 @@ { "fields": { "cc": [ + "conflict_review_steering_group", + "conflict_review_stream_manager", "doc_affecteddoc_authors", "doc_affecteddoc_group_chairs", "doc_affecteddoc_notify", @@ -2775,6 +3097,8 @@ "cc": [], "desc": "Recipients when a document's telechat date or other telechat specific details are changed", "to": [ + "conflict_review_steering_group", + "conflict_review_stream_manager", "doc_affecteddoc_authors", "doc_affecteddoc_group_chairs", "doc_affecteddoc_notify", @@ -3969,6 +4293,46 @@ "model": "mailtrigger.recipient", "pk": "submission_submitter" }, + { + "fields": { + "desc": "", + "name": "AD Office Hours", + "order": 0, + "used": true + }, + "model": "name.agendatypename", + "pk": "ad" + }, + { + "fields": { + "desc": "", + "name": "IETF Agenda", + "order": 0, + "used": true + }, + "model": "name.agendatypename", + "pk": "ietf" + }, + { + "fields": { + "desc": "", + "name": "Side Meetings", + "order": 0, + "used": true + }, + "model": "name.agendatypename", + "pk": "side" + }, + { + "fields": { + "desc": "", + "name": "Workshops", + "order": 0, + "used": true + }, + "model": "name.agendatypename", + "pk": "workshop" + }, { "fields": { "blocking": false, @@ -8352,6 +8716,17 @@ "model": "name.grouptypename", "pk": "program" }, + { + "fields": { + "desc": "", + "name": "Directorate (with reviews)", + "order": 0, + "used": true, + "verbose_name": "" + }, + "model": "name.grouptypename", + "pk": "review" + }, { "fields": { "desc": "", @@ -8468,7 +8843,7 @@ "desc": "Final Pre-Registration and Pre-Payment cut-off at 17:00 local meeting time", "name": "Pre-Registration Cutoff", "order": 0, - "used": true + "used": false }, "model": "name.importantdatename", "pk": "cutoffpre" @@ -10090,7 +10465,7 @@ "fields": { "command": "xym", "switch": "--version", - "time": "2018-05-21T00:08:52.105", + "time": "2018-07-11T00:07:49.096", "used": true, "version": "xym 0.4" }, @@ -10101,7 +10476,7 @@ "fields": { "command": "pyang", "switch": "--version", - "time": "2018-05-21T00:08:53.585", + "time": "2018-07-11T00:07:49.823", "used": true, "version": "pyang 1.7.5" }, @@ -10112,7 +10487,7 @@ "fields": { "command": "yanglint", "switch": "--version", - "time": "2018-05-21T00:08:53.827", + "time": "2018-07-11T00:07:50.005", "used": true, "version": "yanglint 0.14.80" }, @@ -10123,9 +10498,9 @@ "fields": { "command": "xml2rfc", "switch": "--version", - "time": "2018-05-21T00:08:54.971", + "time": "2018-07-11T00:07:51.102", "used": true, - "version": "xml2rfc 2.9.6" + "version": "xml2rfc 2.9.8" }, "model": "utils.versioninfo", "pk": 4 diff --git a/ietf/name/generate_fixtures.py b/ietf/name/generate_fixtures.py index f90abcbda..707c54aff 100644 --- a/ietf/name/generate_fixtures.py +++ b/ietf/name/generate_fixtures.py @@ -43,6 +43,9 @@ objects += ietf.doc.models.StateType.objects.all() objects += ietf.doc.models.State.objects.all() objects += ietf.doc.models.BallotType.objects.all() +import ietf.group.models +objects += ietf.group.models.GroupFeatures.objects.all() + import ietf.mailtrigger.models objects += ietf.mailtrigger.models.Recipient.objects.all() objects += ietf.mailtrigger.models.MailTrigger.objects.all() diff --git a/ietf/name/migrations/0002_agendatypename.py b/ietf/name/migrations/0002_agendatypename.py new file mode 100644 index 000000000..11fd824df --- /dev/null +++ b/ietf/name/migrations/0002_agendatypename.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.13 on 2018-07-10 13:47 +from __future__ import unicode_literals + +from django.db import migrations, models + +class Migration(migrations.Migration): + + dependencies = [ + ('name', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AgendaTypeName', + fields=[ + ('slug', models.CharField(max_length=32, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=255)), + ('desc', models.TextField(blank=True)), + ('used', models.BooleanField(default=True)), + ('order', models.IntegerField(default=0)), + ], + options={ + 'ordering': ['order', 'name'], + 'abstract': False, + }, + ), + ] diff --git a/ietf/name/migrations/0003_agendatypename_data.py b/ietf/name/migrations/0003_agendatypename_data.py new file mode 100644 index 000000000..0c975e91b --- /dev/null +++ b/ietf/name/migrations/0003_agendatypename_data.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.13 on 2018-07-10 13:47 +from __future__ import unicode_literals + +from django.db import migrations + +agenda_type_names = ( + { + 'slug': 'ietf', + 'name': 'IETF Agenda', + 'desc': '', + 'used': True, + 'order': 0, + }, + { + 'slug': 'ad', + 'name': 'AD Office Hours', + 'desc': '', + 'used': True, + 'order': 0, + }, + { + 'slug': 'side', + 'name': 'Side Meetings', + 'desc': '', + 'used': True, + 'order': 0, + }, + { + 'slug': 'workshop', + 'name': 'Workshops', + 'desc': '', + 'used': True, + 'order': 0, + }, +) + +def forward(apps, schema_editor): + AgendaTypeName = apps.get_model('name', 'AgendaTypeName') + for entry in agenda_type_names: + AgendaTypeName.objects.create(**entry) + +def reverse(apps, schema_editor): + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('name', '0001_initial'), + ('group', '0002_groupfeatures_historicalgroupfeatures'), + ] + + operations = [ + migrations.RunPython(forward, reverse), + ] diff --git a/ietf/name/models.py b/ietf/name/models.py index 42d4bb61c..ac96ab9bc 100644 --- a/ietf/name/models.py +++ b/ietf/name/models.py @@ -57,6 +57,8 @@ class BallotPositionName(NameModel): blocking = models.BooleanField(default=False) class MeetingTypeName(NameModel): """IETF, Interim""" +class AgendaTypeName(NameModel): + """ietf, ad, side, workshop, ...""" class SessionStatusName(NameModel): """Waiting for Approval, Approved, Waiting for Scheduling, Scheduled, Cancelled, Disapproved""" class TimeSlotTypeName(NameModel): diff --git a/ietf/name/resources.py b/ietf/name/resources.py index 13249f2b3..76dca2a15 100644 --- a/ietf/name/resources.py +++ b/ietf/name/resources.py @@ -7,16 +7,15 @@ from tastypie.cache import SimpleCache from ietf import api -from ietf.name.models import (TimeSlotTypeName, GroupStateName, DocTagName, IntendedStdLevelName, - LiaisonStatementPurposeName, DraftSubmissionStateName, DocTypeName, RoleName, - IprDisclosureStateName, StdLevelName, LiaisonStatementEventTypeName, GroupTypeName, - IprEventTypeName, GroupMilestoneStateName, SessionStatusName, DocReminderTypeName, - ConstraintName, MeetingTypeName, DocRelationshipName, RoomResourceName, IprLicenseTypeName, - LiaisonStatementTagName, FeedbackTypeName, LiaisonStatementState, StreamName, - BallotPositionName, DBTemplateTypeName, NomineePositionStateName, ReviewRequestStateName, - ReviewTypeName, ReviewResultName, TopicAudienceName, FormalLanguageName, ContinentName, - CountryName, ImportantDateName, DocUrlTagName) - +from ietf.name.models import ( AgendaTypeName, BallotPositionName, ConstraintName, + ContinentName, CountryName, DBTemplateTypeName, DocRelationshipName, DocReminderTypeName, + DocTagName, DocTypeName, DocUrlTagName, DraftSubmissionStateName, FeedbackTypeName, + FormalLanguageName, GroupMilestoneStateName, GroupStateName, GroupTypeName, + ImportantDateName, IntendedStdLevelName, IprDisclosureStateName, IprEventTypeName, + IprLicenseTypeName, LiaisonStatementEventTypeName, LiaisonStatementPurposeName, + LiaisonStatementState, LiaisonStatementTagName, MeetingTypeName, NomineePositionStateName, + ReviewRequestStateName, ReviewResultName, ReviewTypeName, RoleName, RoomResourceName, + SessionStatusName, StdLevelName, StreamName, TimeSlotTypeName, TopicAudienceName, ) class TimeSlotTypeNameResource(ModelResource): class Meta: @@ -552,3 +551,19 @@ class DocUrlTagNameResource(ModelResource): "order": ALL, } api.name.register(DocUrlTagNameResource()) + + +class AgendaTypeNameResource(ModelResource): + class Meta: + queryset = AgendaTypeName.objects.all() + serializer = api.Serializer() + cache = SimpleCache() + #resource_name = 'agendatypename' + filtering = { + "slug": ALL, + "name": ALL, + "desc": ALL, + "used": ALL, + "order": ALL, + } +api.name.register(AgendaTypeNameResource()) diff --git a/ietf/review/factories.py b/ietf/review/factories.py index 5a6b46d1e..5d13e375c 100644 --- a/ietf/review/factories.py +++ b/ietf/review/factories.py @@ -8,7 +8,7 @@ class ReviewTeamSettingsFactory(factory.DjangoModelFactory): class Meta: model = ReviewTeamSettings - group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='dir') + group = factory.SubFactory('ietf.group.factories.GroupFactory',type_id='review') @factory.post_generation def review_types(obj, create, extracted, **kwargs): @@ -35,7 +35,7 @@ class ReviewRequestFactory(factory.DjangoModelFactory): state_id = 'requested' type_id = 'lc' doc = factory.SubFactory('ietf.doc.factories.DocumentFactory',type_id='draft') - team = factory.SubFactory('ietf.group.factories.ReviewTeamFactory',type_id='dir') + team = factory.SubFactory('ietf.group.factories.ReviewTeamFactory',type_id='review') deadline = datetime.datetime.today()+datetime.timedelta(days=14) requested_by = factory.SubFactory('ietf.person.factories.PersonFactory') diff --git a/ietf/settings.py b/ietf/settings.py index caabf0550..a2f552fe1 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -883,7 +883,7 @@ 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', 'ag', ] +TRAC_CREATE_GROUP_TYPES = ['wg', 'rg', 'area', 'team', 'dir', 'review', 'ag', ] TRAC_CREATE_GROUP_STATES = ['bof', 'active', ] TRAC_CREATE_GROUP_ACRONYMS = ['iesg', 'iaoc', 'ietf', ] TRAC_CREATE_ADHOC_WIKIS = [ diff --git a/ietf/templates/meeting/materials.html b/ietf/templates/meeting/materials.html index 37eff253d..9f397d124 100644 --- a/ietf/templates/meeting/materials.html +++ b/ietf/templates/meeting/materials.html @@ -212,6 +212,41 @@ {% endif %} + + {% if other %} +

Other Miscellaneous other sessions

+ + + + {% if user|has_role:"Secretariat" or user|managed_groups %} + + + + + + + + {% else %} + + + + + + + {% endif %} + + + + + {% for session in other|dictsort:"group.acronym" %} + {% ifchanged %} + {% include "meeting/group_materials.html" %} + {% endifchanged %} + {% endfor %} + +
GroupAgendaMinutesSlidesDraftsUpdated GroupAgendaMinutesSlidesDraftsUpdated
+ {% endif %} + {% endwith %} @@ -235,6 +270,10 @@ {% if irtf %}
  • IRTF
  • {% endif %} + {% if other %} +
  • Other
  • + {% endif %} + diff --git a/ietf/urls.py b/ietf/urls.py index 67b474437..57d682c37 100644 --- a/ietf/urls.py +++ b/ietf/urls.py @@ -66,7 +66,7 @@ urlpatterns = [ url(r'^submit/', include('ietf.submit.urls')), url(r'^sync/', include('ietf.sync.urls')), url(r'^templates/', include('ietf.dbtemplate.urls')), - url(r'^(?P(wg|rg|ag|team|dir|area|program))/', include(grouptype_urls)), + url(r'^(?P(wg|rg|ag|team|dir|review|area|program))/', include(grouptype_urls)), # Redirects url(r'^(?Ppublic)/', include('ietf.redirects.urls')), diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py index 51b816b41..562ee239d 100644 --- a/ietf/utils/test_data.py +++ b/ietf/utils/test_data.py @@ -416,9 +416,9 @@ def make_test_data(): return draft def make_review_data(doc): - team1 = create_group(acronym="reviewteam", name="Review Team", type_id="dir", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) - team2 = create_group(acronym="reviewteam2", name="Review Team 2", type_id="dir", list_email="reviewteam2@ietf.org", parent=Group.objects.get(acronym="farfut")) - team3 = create_group(acronym="reviewteam3", name="Review Team 3", type_id="dir", list_email="reviewteam2@ietf.org", parent=Group.objects.get(acronym="farfut")) + team1 = create_group(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut")) + team2 = create_group(acronym="reviewteam2", name="Review Team 2", type_id="review", list_email="reviewteam2@ietf.org", parent=Group.objects.get(acronym="farfut")) + team3 = create_group(acronym="reviewteam3", name="Review Team 3", type_id="review", list_email="reviewteam2@ietf.org", parent=Group.objects.get(acronym="farfut")) for team in (team1, team2, team3): ReviewTeamSettings.objects.create(group=team) for r in ReviewResultName.objects.filter(slug__in=["issues", "ready-issues", "ready", "not-ready"]):