Merged in [17764] from rjsparks@nostrum.com:

Allow IAB programs to use normal meeting mechanics. Fixes #2970.
 - Legacy-Id: 17773
Note: SVN reference [17764] has been migrated to Git commit fb8e5c6842
This commit is contained in:
Henrik Levkowetz 2020-05-11 11:41:22 +00:00
commit a26ace5bfa
19 changed files with 475 additions and 83 deletions

View file

@ -18,6 +18,15 @@ class GroupFactory(factory.DjangoModelFactory):
list_email = factory.LazyAttribute(lambda a: '%s@ietf.org'% a.acronym)
uses_milestone_dates = True
@factory.lazy_attribute
def parent(self):
if self.type_id in ['wg','ag']:
return GroupFactory(type_id='area')
elif self.type_id in ['rg']:
return GroupFactory(acronym='irtf', type_id='irtf')
else:
return None
class ReviewTeamFactory(GroupFactory):
type_id = 'review'

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-04 13:10
from __future__ import unicode_literals
from django.db import migrations
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('group', '0023_use_milestone_dates_default_to_true'),
]
operations = [
migrations.AddField(
model_name='groupfeatures',
name='groupman_authroles',
field=jsonfield.fields.JSONField(default=['Secretariat'], max_length=128),
),
migrations.AddField(
model_name='historicalgroupfeatures',
name='groupman_authroles',
field=jsonfield.fields.JSONField(default=['Secretariat'], max_length=128),
),
]

View file

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-01 12:54
from __future__ import unicode_literals
from django.db import migrations
authroles_map = {
'adhoc': ['Secretariat'],
'admin': ['Secretariat'],
'ag': ['Secretariat', 'Area Director'],
'area': ['Secretariat'],
'dir': ['Secretariat'],
'iab': ['Secretariat'],
'iana': ['Secretariat'],
'iesg': ['Secretariat'],
'ietf': ['Secretariat'],
'individ': [],
'irtf': ['Secretariat'],
'ise': ['Secretariat'],
'isoc': ['Secretariat'],
'nomcom': ['Secretariat'],
'program': ['Secretariat', 'IAB'],
'review': ['Secretariat'],
'rfcedtyp': ['Secretariat'],
'rg': ['Secretariat', 'IRTF Chair'],
'sdo': ['Secretariat'],
'team': ['Secretariat'],
'wg': ['Secretariat', 'Area Director'],
}
def forward(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
for type_id, authroles in authroles_map.items():
GroupFeatures.objects.filter(type_id=type_id).update(groupman_authroles=authroles)
def reverse(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('group', '0024_add_groupman_authroles'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-01 12:54
from __future__ import unicode_literals
from django.db import migrations
def forward(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
program = GroupFeatures.objects.get(type_id='program')
program.has_meetings = True
program.matman_roles = ['lead', 'chair', 'secr']
program.docman_roles = ['lead', 'chair', 'secr']
program.groupman_roles = ['lead', 'chair', 'secr']
program.role_order = ['lead', 'chair', 'secr']
program.save()
def reverse(apps, schema_editor):
GroupFeatures = apps.get_model('group', 'GroupFeatures')
program = GroupFeatures.objects.get(type_id='program')
program.has_meetings = False
program.matman_roles = ['lead', 'secr']
program.docman_roles = ['lead', 'secr']
program.groupman_roles = ['lead', 'secr']
program.role_order = ['lead', 'secr']
program.save()
class Migration(migrations.Migration):
dependencies = [
('group', '0025_populate_groupman_authroles'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2020-05-08 09:02
from __future__ import unicode_literals
from django.db import migrations
def forward(apps, schema_editor):
Group = apps.get_model('group','Group')
iab = Group.objects.get(acronym='iab')
Group.objects.filter(type_id='program').update(parent=iab)
def reverse(apps, schema_editor):
pass # No point in removing the parents
class Migration(migrations.Migration):
dependencies = [
('group', '0026_programs_meet'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View file

@ -65,9 +65,6 @@ class GroupInfo(models.Model):
kwargs["group_type"] = self.type_id
return urlreverse(self.features.about_page, kwargs=kwargs)
def interim_approval_roles(self):
return list(set([ role for role in self.parent.role_set.filter(name__in=['ad', 'chair']) ]))
def is_bof(self):
return self.state_id in ["bof", "bof-conc"]
@ -238,6 +235,7 @@ class GroupFeatures(models.Model):
admin_roles = jsonfield.JSONField(max_length=64, blank=False, default=["chair"]) # Trac Admin
docman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair","delegate","secr"])
groupman_roles = jsonfield.JSONField(max_length=128, blank=False, default=["ad","chair",])
groupman_authroles = jsonfield.JSONField(max_length=128, blank=False, default=["Secretariat",])
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.")

View file

@ -264,7 +264,7 @@ class GroupPagesTests(TestCase):
can_edit = {
'wg' : ['secretary','ad'],
'rg' : ['secretary','irtf-chair'],
'ag' : ['secretary', ],
'ag' : ['secretary', 'ad' ],
'team' : ['secretary',], # The code currently doesn't let ads edit teams or directorates. Maybe it should.
'dir' : ['secretary',],
'review' : ['secretary',],

View file

@ -15,7 +15,7 @@ import debug # pyflakes:ignore
from ietf.community.models import CommunityList, SearchRule
from ietf.community.utils import reset_name_contains_index_for_rule, can_manage_community_list
from ietf.doc.models import Document, State
from ietf.group.models import Group, RoleHistory, Role
from ietf.group.models import Group, RoleHistory, Role, GroupFeatures
from ietf.ietfauth.utils import has_role
from ietf.name.models import GroupTypeName
from ietf.person.models import Email
@ -105,6 +105,7 @@ def save_milestone_in_history(milestone):
return h
# TODO: rework this using features.groupman_authroles
def can_manage_group_type(user, group, type_id=None):
if not user.is_authenticated:
return False
@ -125,8 +126,11 @@ def can_manage_group_type(user, group, type_id=None):
return has_role(user, ('Secretariat'))
def can_manage_group(user, group):
if can_manage_group_type(user, group):
return True
if not user.is_authenticated:
return False
for authrole in group.features.groupman_authroles:
if has_role(user, authrole):
return True
return group.has_role(user, group.features.groupman_roles)
def milestone_reviewer_for_group_type(group_type):
@ -141,6 +145,18 @@ def can_manage_materials(user, group):
def can_manage_session_materials(user, group, session):
return has_role(user, 'Secretariat') or (group.has_role(user, group.features.matman_roles) and not session.is_material_submission_cutoff())
# Maybe this should be cached...
def can_manage_some_groups(user):
if not user.is_authenticated:
return False
for gf in GroupFeatures.objects.all():
for authrole in gf.groupman_authroles:
if has_role(user, authrole):
return True
if Role.objects.filter(name__in=gf.groupman_roles, group__type_id=gf.type_id, person__user=user).exists():
return True
return False
def can_provide_status_update(user, group):
if not group.features.acts_like_wg:
return False

View file

@ -70,6 +70,9 @@ def has_role(user, role_names, *args, **kwargs):
"RG Secretary": Q(person=person,name="secr", group__type="rg", group__state__in=["active","proposed"]),
"AG Secretary": Q(person=person,name="secr", group__type="ag", group__state__in=["active"]),
"Team Chair": Q(person=person,name="chair", group__type="team", group__state="active"),
"Program Lead": Q(person=person,name="lead", group__type="program", group__state="active"),
"Program Secretary": Q(person=person,name="secr", group__type="program", group__state="active"),
"Program Chair": Q(person=person,name="chair", group__type="program", group__state="active"),
"Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),
"Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')),

View file

@ -166,6 +166,8 @@ class Recipient(models.Model):
addrs.extend(group.role_set.filter(name='ad').values_list('email__address',flat=True))
if group.type_id=='rg':
addrs.extend(Recipient.objects.get(slug='stream_managers').gather(**{'streams':['irtf']}))
elif group.type_id=='program':
addrs.extend(Recipient.objects.get(slug='iab').gather(**{}))
return addrs
def gather_group_secretaries(self, **kwargs):

View file

@ -15,7 +15,7 @@ from django.forms import BaseInlineFormSet
import debug # pyflakes:ignore
from ietf.doc.models import Document, DocAlias, State, NewRevisionDocEvent
from ietf.group.models import Group
from ietf.group.models import Group, GroupFeatures
from ietf.ietfauth.utils import has_role
from ietf.meeting.models import Session, Meeting, Schedule, countries, timezones
from ietf.meeting.helpers import get_next_interim_number, make_materials_directories
@ -100,8 +100,7 @@ class InterimSessionInlineFormSet(BaseInlineFormSet):
return # formset doesn't have cleaned_data
class InterimMeetingModelForm(forms.ModelForm):
# TODO: Should area groups get to schedule Interims?
group = GroupModelChoiceField(queryset=Group.objects.filter(type__in=('wg', 'rg', 'ag'), state__in=('active', 'proposed', 'bof')).order_by('acronym'), required=False)
group = GroupModelChoiceField(queryset=Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof')).order_by('acronym'), required=False)
in_person = forms.BooleanField(required=False)
meeting_type = forms.ChoiceField(choices=(
("single", "Single"),
@ -156,13 +155,15 @@ class InterimMeetingModelForm(forms.ModelForm):
return # don't reduce group options
q_objects = Q()
if has_role(self.user, "Area Director"):
q_objects.add(Q(type="wg", state__in=("active", "proposed", "bof")), Q.OR)
q_objects.add(Q(type__in=["wg", "ag"], state__in=("active", "proposed", "bof")), Q.OR)
if has_role(self.user, "IRTF Chair"):
q_objects.add(Q(type="rg", state__in=("active", "proposed")), Q.OR)
if has_role(self.user, "WG Chair"):
q_objects.add(Q(type="wg", state__in=("active", "proposed", "bof"), role__person=self.person, role__name="chair"), Q.OR)
if has_role(self.user, "RG Chair"):
q_objects.add(Q(type="rg", state__in=("active", "proposed"), role__person=self.person, role__name="chair"), Q.OR)
if has_role(self.user, "Program Lead") or has_role(self.user, "Program Chair"):
q_objects.add(Q(type="program", state__in=("active", "proposed"), role__person=self.person, role__name__in=["chair", "lead"]), Q.OR)
queryset = Group.objects.filter(q_objects).distinct().order_by('acronym')
self.fields['group'].queryset = queryset

View file

@ -21,6 +21,7 @@ import debug # pyflakes:ignore
from ietf.doc.models import Document
from ietf.group.models import Group
from ietf.group.utils import can_manage_some_groups, can_manage_group
from ietf.ietfauth.utils import has_role, user_is_person
from ietf.liaisons.utils import get_person_for_user
from ietf.mailtrigger.utils import gather_address_lists
@ -324,11 +325,14 @@ def can_approve_interim_request(meeting, user):
if not session:
return False
group = session.group
if group.type.slug == 'wg':
if group.type.slug in ['wg','ag']:
if group.parent.role_set.filter(name='ad', person=person) or group.role_set.filter(name='ad', person=person):
return True
if group.type.slug == 'rg' and group.parent.role_set.filter(name='chair', person=person):
return True
if group.type.slug == 'program':
if person.role_set.filter(group__acronym='iab', name='member'):
return True
return False
@ -336,14 +340,13 @@ def can_edit_interim_request(meeting, user):
'''Returns True if the user can edit the interim meeting request'''
if meeting.type.slug != 'interim':
return False
if has_role(user, 'Secretariat'):
if has_role(user, 'Secretariat'): # Consider removing - can_manage_group should handle this
return True
person = get_person_for_user(user)
session = meeting.session_set.first()
if not session:
return False
group = session.group
if group.role_set.filter(name='chair', person=person):
if can_manage_group(user, group):
return True
elif can_approve_interim_request(meeting, user):
return True
@ -352,29 +355,17 @@ def can_edit_interim_request(meeting, user):
def can_request_interim_meeting(user):
if has_role(user, ('Secretariat', 'Area Director', 'WG Chair', 'IRTF Chair', 'RG Chair')):
return True
return False
return can_manage_some_groups(user)
def can_view_interim_request(meeting, user):
'''Returns True if the user can see the pending interim request in the pending interim view'''
if meeting.type.slug != 'interim':
return False
if has_role(user, 'Secretariat'):
return True
person = get_person_for_user(user)
session = meeting.session_set.first()
if not session:
return False
group = session.group
if has_role(user, 'Area Director') and group.type.slug == 'wg':
return True
if has_role(user, 'IRTF Chair') and group.type.slug == 'rg':
return True
if group.role_set.filter(name='chair', person=person):
return True
return False
return can_manage_group(user, group)
def create_interim_meeting(group, date, city='', country='', timezone='UTC',
@ -512,11 +503,17 @@ def send_interim_approval_request(meetings):
else:
is_series = False
approver_set = set()
for role in group.interim_approval_roles():
approver = "%s of the %s" % ( role.name.name, role.group.name)
approver_set.add(approver)
for authrole in group.features.groupman_authroles: # NOTE: This makes an assumption that the authroles are exactly the set of approvers
approver_set.add(authrole)
approvers = list(approver_set)
context = locals() # TODO Unnecessarily complex, context needs to only contain what the template needs
context = {
'group': group,
'is_series': is_series,
'meetings': meetings,
'approvers': approvers,
'requester': requester,
'approval_urls': approval_urls,
}
send_mail(None,
to_email,
from_email,

View file

@ -26,7 +26,8 @@ from django.db.models import F
import debug # pyflakes:ignore
from ietf.doc.models import Document
from ietf.group.models import Group, Role
from ietf.group.models import Group, Role, GroupFeatures
from ietf.group.utils import can_manage_group
from ietf.person.models import Person
from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request
from ietf.meeting.helpers import send_interim_approval_request
@ -37,7 +38,7 @@ from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
from ietf.meeting.utils import finalize, condition_slide_order
from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.meeting.views import session_draft_list
from ietf.name.models import SessionStatusName, ImportantDateName
from ietf.name.models import SessionStatusName, ImportantDateName, RoleName
from ietf.utils.decorators import skip_coverage
from ietf.utils.mail import outbox, empty_outbox, get_payload
from ietf.utils.test_utils import TestCase, login_testing_unauthorized, unicontent
@ -1555,7 +1556,8 @@ class InterimTests(TestCase):
r = self.client.get("/meeting/interim/request/")
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(Group.objects.filter(type__in=('wg', 'rg', 'ag'), state__in=('active', 'proposed')).count(),
Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof'))
self.assertEqual(Group.objects.filter(type_id__in=GroupFeatures.objects.filter(has_meetings=True).values_list('type_id',flat=True), state__in=('active', 'proposed', 'bof')).count(),
len(q("#id_group option")) - 1) # -1 for options placeholder
self.client.logout()
@ -1939,6 +1941,28 @@ class InterimTests(TestCase):
user = User.objects.get(username='ameschairman')
self.assertFalse(can_view_interim_request(meeting=meeting,user=user))
def test_can_manage_group(self):
make_meeting_test_data()
# unprivileged user
user = User.objects.get(username='plain')
group = Group.objects.get(acronym='mars')
self.assertFalse(can_manage_group(user=user,group=group))
# Secretariat
user = User.objects.get(username='secretary')
self.assertTrue(can_manage_group(user=user,group=group))
# related AD
user = User.objects.get(username='ad')
self.assertTrue(can_manage_group(user=user,group=group))
# other AD
user = User.objects.get(username='ops-ad')
self.assertTrue(can_manage_group(user=user,group=group))
# WG Chair
user = User.objects.get(username='marschairman')
self.assertTrue(can_manage_group(user=user,group=group))
# Other WG Chair
user = User.objects.get(username='ameschairman')
self.assertFalse(can_manage_group(user=user,group=group))
def test_interim_request_details(self):
make_meeting_test_data()
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
@ -1983,13 +2007,6 @@ class InterimTests(TestCase):
make_meeting_test_data()
meeting = add_event_info_to_session_qs(Session.objects.filter(meeting__type='interim', group__acronym='mars')).filter(current_status='apprw').first().meeting
url = urlreverse('ietf.meeting.views.interim_request_details', kwargs={'number': meeting.number})
# ensure no cancel button for unauthorized user
self.client.login(username="ameschairman", password="ameschairman+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q("a.btn:contains('Cancel')")), 0)
# ensure cancel button for authorized user
self.client.login(username="marschairman", password="marschairman+password")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
@ -2793,3 +2810,164 @@ class SessionTests(TestCase):
})
self.assertEqual(r.status_code,302)
self.assertEqual(len(outbox),1)
class HasMeetingsTests(TestCase):
def do_request_interim(self, url, group, user, meeting_count):
login_testing_unauthorized(self,user.username, url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('#id_group option[value="%d"]'%group.pk))
date = datetime.date.today() + datetime.timedelta(days=30+meeting_count)
time = datetime.datetime.now().time().replace(microsecond=0,second=0)
remote_instructions = 'Use webex'
agenda = 'Intro. Slides. Discuss.'
agenda_note = 'On second level'
meeting_count = Meeting.objects.filter(number__contains='-%s-'%group.acronym, date__year=date.year).count()
next_num = "%02d" % (meeting_count+1)
data = {'group':group.pk,
'meeting_type':'single',
'city':'',
'country':'',
'time_zone':'UTC',
'session_set-0-date':date.strftime("%Y-%m-%d"),
'session_set-0-time':time.strftime('%H:%M'),
'session_set-0-requested_duration':'03:00:00',
'session_set-0-remote_instructions':remote_instructions,
'session_set-0-agenda':agenda,
'session_set-0-agenda_note':agenda_note,
'session_set-TOTAL_FORMS':1,
'session_set-INITIAL_FORMS':0,
'session_set-MIN_NUM_FORMS':0,
'session_set-MAX_NUM_FORMS':1000}
r = self.client.post(urlreverse("ietf.meeting.views.interim_request"),data)
self.assertRedirects(r,urlreverse('ietf.meeting.views.upcoming'))
meeting = Meeting.objects.order_by('id').last()
self.assertEqual(meeting.type_id,'interim')
self.assertEqual(meeting.date,date)
self.assertEqual(meeting.number,'interim-%s-%s-%s' % (date.year, group.acronym, next_num))
self.client.logout()
def create_role_for_authrole(self, authrole):
role = None
if authrole == 'Secretariat':
role = RoleFactory.create(group__acronym='secretariat',name_id='secr')
elif authrole == 'Area Director':
role = RoleFactory.create(name_id='ad', group__type_id='area')
elif authrole == 'IAB':
role = RoleFactory.create(name_id='member', group__acronym='iab')
elif authrole == 'IRTF Chair':
role = RoleFactory.create(name_id='chair', group__acronym='irtf')
if role is None:
self.assertIsNone("Can't test authrole:"+authrole)
self.assertNotEqual(role, None)
return role
def test_can_request_interim(self):
url = urlreverse('ietf.meeting.views.interim_request')
for gf in GroupFeatures.objects.filter(has_meetings=True):
meeting_count = 0
for role in gf.groupman_roles:
role = RoleFactory(group__type_id=gf.type_id, name_id=role)
self.do_request_interim(url, role.group, role.person.user, meeting_count)
for authrole in gf.groupman_authroles:
group = GroupFactory(type_id=gf.type_id)
role = self.create_role_for_authrole(authrole)
self.do_request_interim(url, group, role.person.user, 0)
def test_cannot_request_interim(self):
url = urlreverse('ietf.meeting.views.interim_request')
self.client.login(username='secretary', password='secretary+password')
nomeetings = []
for gf in GroupFeatures.objects.exclude(has_meetings=True):
nomeetings.append(GroupFactory(type_id=gf.type_id))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
for group in nomeetings:
self.assertFalse(q('#id_group option[value="%d"]'%group.pk))
self.client.logout()
all_role_names = set(RoleName.objects.values_list('slug',flat=True))
for gf in GroupFeatures.objects.filter(has_meetings=True):
for role_name in all_role_names - set(gf.groupman_roles):
role = RoleFactory(group__type_id=gf.type_id,name_id=role_name)
self.client.login(username=role.person.user.username, password=role.person.user.username+'+password')
r = self.client.get(url)
self.assertEqual(r.status_code, 403)
self.client.logout()
def test_appears_on_upcoming(self):
url = urlreverse('ietf.meeting.views.upcoming')
for gf in GroupFeatures.objects.filter(has_meetings=True):
session = SessionFactory(
group__type_id = gf.type_id,
meeting__type_id='interim',
meeting__date = datetime.datetime.today()+datetime.timedelta(days=30),
status_id='sched',
)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())
def test_appears_on_pending(self):
url = urlreverse('ietf.meeting.views.interim_pending')
for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id)
meeting_date = datetime.datetime.today() + datetime.timedelta(days=30)
session = SessionFactory(
group=group,
meeting__type_id='interim',
meeting__date = meeting_date,
meeting__number = 'interim-%d-%s-00'%(meeting_date.year,group.acronym),
status_id='apprw',
)
for role_name in gf.groupman_roles:
role = RoleFactory(group=group, name_id=role_name)
self.client.login(username=role.person.user.username, password=role.person.user.username+'+password')
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())
self.client.logout()
for authrole in gf.groupman_authroles:
role = self.create_role_for_authrole(authrole)
self.client.login(username=role.person.user.username, password=role.person.user.username+'+password')
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())
self.client.logout()
def test_appears_on_announce(self):
url = urlreverse('ietf.meeting.views.interim_announce')
login_testing_unauthorized(self,"secretary",url)
sessions=[]
for gf in GroupFeatures.objects.filter(has_meetings=True):
group = GroupFactory(type_id=gf.type_id)
meeting_date = datetime.datetime.today() + datetime.timedelta(days=30)
session = SessionFactory(
group=group,
meeting__type_id='interim',
meeting__date = meeting_date,
meeting__number = 'interim-%d-%s-00'%(meeting_date.year,group.acronym),
status_id='scheda',
)
sessions.append(session)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
for session in sessions:
self.assertIn(session.meeting.number, q('.interim-meeting-link').text())

View file

@ -49,7 +49,7 @@ from django.views.generic import RedirectView
from ietf.doc.fields import SearchableDocumentsField
from ietf.doc.models import Document, State, DocEvent, NewRevisionDocEvent, DocAlias
from ietf.group.models import Group
from ietf.group.utils import can_manage_session_materials
from ietf.group.utils import can_manage_session_materials, can_manage_some_groups, can_manage_group
from ietf.person.models import Person
from ietf.person.name import plain_name
from ietf.ietfauth.utils import role_required, has_role
@ -96,7 +96,7 @@ from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSession
def get_interim_menu_entries(request):
'''Setup menu entries for interim meeting view tabs'''
entries = []
if has_role(request.user, ('Area Director','Secretariat','IRTF Chair','WG Chair', 'RG Chair')):
if can_manage_some_groups(request.user):
entries.append(("Upcoming", reverse("ietf.meeting.views.upcoming")))
entries.append(("Pending", reverse("ietf.meeting.views.interim_pending")))
if has_role(request.user, "Secretariat"):
@ -1508,7 +1508,7 @@ def meeting_requests(request, num=None):
s.current_status_name = status_names.get(s.current_status, s.current_status)
s.requested_by_person = session_requesters.get(s.requested_by)
groups_not_meeting = Group.objects.filter(state='Active',type__in=['wg','rg','ag','bof']).exclude(acronym__in = [session.group.acronym for session in sessions]).order_by("parent__acronym","acronym").prefetch_related("parent")
groups_not_meeting = Group.objects.filter(state='Active',type__in=['wg','rg','ag','bof','program']).exclude(acronym__in = [session.group.acronym for session in sessions]).order_by("parent__acronym","acronym").prefetch_related("parent")
return render(request, "meeting/requests.html",
{"meeting": meeting, "sessions":sessions,
@ -2392,8 +2392,12 @@ def interim_skip_announcement(request, number):
'meeting': meeting})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_pending(request):
if not can_manage_some_groups(request.user):
return HttpResponseForbidden()
'''View which shows interim meeting requests pending approval'''
meetings = data_for_meetings_overview(Meeting.objects.filter(type='interim').order_by('date'), interim_status='apprw')
@ -2411,8 +2415,12 @@ def interim_pending(request):
'meetings': meetings})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request(request):
if not can_manage_some_groups(request.user):
return HttpResponseForbidden("You don't have permission to request any interims")
'''View for requesting an interim meeting'''
SessionFormset = inlineformset_factory(
Meeting,
@ -2497,15 +2505,15 @@ def interim_request(request):
"formset": formset})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request_cancel(request, number):
'''View for cancelling an interim meeting request'''
meeting = get_object_or_404(Meeting, number=number)
first_session = meeting.session_set.first()
session_status = current_session_status(first_session)
group = first_session.group
if not can_view_interim_request(meeting, request.user):
if not can_manage_group(request.user, group):
return HttpResponseForbidden("You do not have permissions to cancel this meeting request")
session_status = current_session_status(first_session)
if request.method == 'POST':
form = InterimCancelForm(request.POST)
@ -2538,10 +2546,13 @@ def interim_request_cancel(request, number):
})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request_details(request, number):
'''View details of an interim meeting reqeust'''
'''View details of an interim meeting request'''
meeting = get_object_or_404(Meeting, number=number)
group = meeting.session_set.first().group
if not can_manage_group(request.user, group):
return HttpResponseForbidden("You do not have permissions to manage this meeting request")
sessions = meeting.session_set.all()
can_edit = can_edit_interim_request(meeting, request.user)
can_approve = can_approve_interim_request(meeting, request.user)
@ -2582,7 +2593,7 @@ def interim_request_details(request, number):
"can_approve": can_approve})
@role_required('Area Director', 'Secretariat', 'IRTF Chair', 'WG Chair', 'RG Chair')
@login_required
def interim_request_edit(request, number):
'''Edit details of an interim meeting reqeust'''
meeting = get_object_or_404(Meeting, number=number)

View file

@ -2484,6 +2484,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"lead\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": true,
@ -2514,6 +2515,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2544,6 +2546,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"Area Director\"]",
"groupman_roles": "[\"ad\",\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2574,6 +2577,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"ad\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"ad\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2604,6 +2608,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"ad\",\"secr\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2634,6 +2639,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2664,6 +2670,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2694,6 +2701,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2724,6 +2732,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2754,6 +2763,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"auth\"]",
"groupman_authroles": "[]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2784,6 +2794,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2814,6 +2825,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2844,6 +2856,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2874,6 +2887,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\",\"advisor\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2903,21 +2917,22 @@
"custom_group_roles": true,
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"lead\",\"secr\"]",
"groupman_roles": "[\"lead\",\"secr\"]",
"docman_roles": "[\"lead\",\"chair\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"IAB\"]",
"groupman_roles": "[\"lead\",\"chair\",\"secr\"]",
"has_chartering_process": false,
"has_default_jabber": false,
"has_documents": true,
"has_meetings": false,
"has_meetings": true,
"has_milestones": true,
"has_nonsession_materials": false,
"has_reviews": false,
"has_session_materials": false,
"is_schedulable": false,
"material_types": "[\"slides\"]",
"matman_roles": "[\"lead\",\"secr\"]",
"matman_roles": "[\"lead\",\"chair\",\"secr\"]",
"req_subm_approval": false,
"role_order": "[\"lead\",\"secr\"]",
"role_order": "[\"lead\",\"chair\",\"secr\"]",
"show_on_agenda": false
},
"model": "group.groupfeatures",
@ -2934,6 +2949,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.review_requests",
"docman_roles": "[\"secr\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"ad\",\"secr\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2964,6 +2980,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -2994,6 +3011,7 @@
"customize_workflow": true,
"default_tab": "ietf.group.views.group_documents",
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"IRTF Chair\"]",
"groupman_roles": "[\"chair\",\"delegate\"]",
"has_chartering_process": true,
"has_default_jabber": true,
@ -3024,6 +3042,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"liaiman\",\"matman\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -3054,6 +3073,7 @@
"customize_workflow": false,
"default_tab": "ietf.group.views.group_about",
"docman_roles": "[\"chair\"]",
"groupman_authroles": "[\"Secretariat\"]",
"groupman_roles": "[\"chair\"]",
"has_chartering_process": false,
"has_default_jabber": false,
@ -3084,6 +3104,7 @@
"customize_workflow": true,
"default_tab": "ietf.group.views.group_documents",
"docman_roles": "[\"chair\",\"delegate\",\"secr\"]",
"groupman_authroles": "[\"Secretariat\",\"Area Director\"]",
"groupman_roles": "[\"ad\",\"chair\",\"delegate\",\"secr\"]",
"has_chartering_process": true,
"has_default_jabber": true,
@ -5603,8 +5624,8 @@
},
{
"fields": {
"editor_label": "(1)",
"desc": "",
"editor_label": "(1)",
"name": "Conflicts with",
"order": 0,
"penalty": 100000,
@ -5616,9 +5637,10 @@
{
"fields": {
"desc": "",
"editor_label": "time_relation",
"name": "Preference for time between sessions",
"order": 0,
"penalty": 100000,
"penalty": 1000,
"used": true
},
"model": "name.constraintname",
@ -5627,6 +5649,7 @@
{
"fields": {
"desc": "",
"editor_label": "timerange",
"name": "Can't meet within timerange",
"order": 0,
"penalty": 100000,
@ -5638,9 +5661,10 @@
{
"fields": {
"desc": "",
"editor_label": "wg_adjacent",
"name": "Request for adjacent scheduling with another WG",
"order": 0,
"penalty": 100000,
"penalty": 10000,
"used": true
},
"model": "name.constraintname",
@ -10181,6 +10205,17 @@
"model": "name.importantdatename",
"pk": "idcutoff"
},
{
"fields": {
"default_offset_days": 70,
"desc": "Announcement of whether conditions have improved enough to hold an in-person meeting in Madrid, or if IETF 108 will be held as a virtual meeting",
"name": "IETF 108 Go-ahead Announcement",
"order": 0,
"used": false
},
"model": "name.importantdatename",
"pk": "ietf-108-go-ahead"
},
{
"fields": {
"default_offset_days": -82,
@ -11268,6 +11303,16 @@
"model": "name.rolename",
"pk": "chair"
},
{
"fields": {
"desc": "",
"name": "Communications Director",
"order": 0,
"used": true
},
"model": "name.rolename",
"pk": "comdir"
},
{
"fields": {
"desc": "",
@ -11420,7 +11465,7 @@
},
{
"fields": {
"desc": "Provides log-in permission to restricted Trac instances",
"desc": "Provides log-in permission to restricted Trac instances. Used by the generate_apache_perms management command, called from ../../scripts/Cron-runner",
"name": "Trac Editor",
"order": 0,
"used": true
@ -11500,8 +11545,8 @@
},
{
"fields": {
"desc": "WebEx support",
"name": "WebEx session",
"desc": "Web streaming support",
"name": "WebEx",
"order": 0,
"used": true
},
@ -14436,9 +14481,9 @@
"fields": {
"command": "xym",
"switch": "--version",
"time": "2020-02-19T00:13:43.554",
"time": "2020-04-29T00:13:24.969",
"used": true,
"version": "xym 0.4"
"version": "xym 0.4.8"
},
"model": "utils.versioninfo",
"pk": 1
@ -14447,9 +14492,9 @@
"fields": {
"command": "pyang",
"switch": "--version",
"time": "2020-02-19T00:13:44.450",
"time": "2020-04-29T00:13:26.832",
"used": true,
"version": "pyang 2.1.1"
"version": "pyang 2.2.1"
},
"model": "utils.versioninfo",
"pk": 2
@ -14458,9 +14503,9 @@
"fields": {
"command": "yanglint",
"switch": "--version",
"time": "2020-02-19T00:13:44.597",
"time": "2020-04-29T00:13:27.209",
"used": true,
"version": "yanglint 0.14.80"
"version": "yanglint SO 1.6.7"
},
"model": "utils.versioninfo",
"pk": 3
@ -14469,9 +14514,9 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
"time": "2020-02-19T00:13:45.481",
"time": "2020-04-29T00:13:28.798",
"used": true,
"version": "xml2rfc 2.40.0"
"version": "xml2rfc 2.44.0"
},
"model": "utils.versioninfo",
"pk": 4

View file

@ -29,7 +29,8 @@ from ietf.mailtrigger.utils import gather_address_lists
# -------------------------------------------------
# Globals
# -------------------------------------------------
AUTHORIZED_ROLES=('WG Chair','WG Secretary','RG Chair','IAB Group Chair','Area Director','Secretariat','Team Chair','IRTF Chair')
# TODO: This needs to be replaced with something that pays attention to groupfeatures
AUTHORIZED_ROLES=('WG Chair','WG Secretary','RG Chair','IAB Group Chair','Area Director','Secretariat','Team Chair','IRTF Chair','Program Chair','Program Lead','Program Secretary')
# -------------------------------------------------
# Helper Functions
@ -319,7 +320,10 @@ def confirm(request, acronym):
)
if 'resources' in form.data:
new_session.resources.set(session_data['resources'])
if int(form.data.get('joint_for_session', '-1')) == count:
jfs = form.data.get('joint_for_session', '-1')
if not jfs: # jfs might be ''
jfs = '-1'
if int(jfs) == count:
groups_split = form.cleaned_data.get('joint_with_groups').replace(',',' ').split()
joint = Group.objects.filter(acronym__in=groups_split)
new_session.joint_with_groups.set(joint)

View file

@ -39,7 +39,7 @@
<td>{{ meeting.date }}</td>
<td>{{ meeting.responsible_group.acronym }}</td>
<td>
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}</a>
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}</a>
</td>
</tr>
{% endfor %}

View file

@ -40,11 +40,7 @@
<td>{{ meeting.date }}</td>
<td>{{ meeting.responsible_group.acronym }}</td>
<td>
{% if meeting.type_id == "interim" %}
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %}&nbsp;&nbsp;<span class="label label-warning">CANCELLED</span>{% endif %}</a>
{% else %}
<a href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">IETF - {{ meeting.number }}</a>
{% endif %}
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.interim_request_details' number=meeting.number %}">{{ meeting.number }}{% if meeting.interim_meeting_cancelled %}&nbsp;&nbsp;<span class="label label-warning">CANCELLED</span>{% endif %}</a>
</td>
<td>{% if meeting.can_approve %}<span class="label label-success">can be approved</span>{% endif %}</td>
</tr>

View file

@ -60,7 +60,7 @@
{% with meeting=entry %}
<td>{{ meeting.date }} - {{ meeting.end }}</td>
<td>ietf</td>
<td><a href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF {{ meeting.number }}</a></td>
<td><a class="ietf-meeting-link" href="{% url 'ietf.meeting.views.agenda' num=meeting.number %}">IETF {{ meeting.number }}</a></td>
<td></td>
{% endwith %}
{% elif entry|classname == 'Session' %}
@ -68,7 +68,7 @@
<td>{{ session.official_timeslotassignment.timeslot.utc_start_time | date:"Y-m-d H:i"}} - {{ session.official_timeslotassignment.timeslot.utc_end_time | date:"H:i e" }}</td>
<td><a href="{% url 'ietf.group.views.group_home' acronym=session.group.acronym %}">{{ session.group.acronym }}</a></td>
<td>
<a href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}">{{ session.meeting.number }}{% if session.current_status == 'canceled' %}&nbsp;&nbsp;<span class="label label-warning">CANCELLED</span>{% endif %}</a>
<a class="interim-meeting-link" href="{% url 'ietf.meeting.views.session_details' num=session.meeting.number acronym=session.group.acronym %}">{{ session.meeting.number }}{% if session.current_status == 'canceled' %}&nbsp;&nbsp;<span class="label label-warning">CANCELLED</span>{% endif %}</a>
</td>
<td class='text-right'>
{% include "meeting/interim_session_buttons.html" %}