Allow IAB programs to use normal meeting mechanics. Fixes #2970. Commit ready for merge.

- Legacy-Id: 17764
This commit is contained in:
Robert Sparks 2020-05-08 21:11:25 +00:00
parent caa109cb42
commit fb8e5c6842
19 changed files with 14933 additions and 14541 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
@ -1546,7 +1547,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()
@ -1930,6 +1932,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
@ -1974,13 +1998,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)
@ -2784,3 +2801,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)

File diff suppressed because it is too large Load diff

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" %}