Add support for structured entry and storage of joint sessions in meetings.

- Also adds additional tests for the SessionForm
- Fixes a javascript error in session requests for non-WG groups,
  that could cause incorrect form behaviour.
- Expands the tests added in [17289] a bit.
 - Legacy-Id: 17321
Note: SVN reference [17289] has been migrated to Git commit a227813dc5
This commit is contained in:
Sasha Romijn 2020-02-21 13:27:11 +00:00
parent 1bfcf2e699
commit 825a054d19
9 changed files with 293 additions and 14 deletions

View file

@ -900,6 +900,7 @@ class Session(models.Model):
short = models.CharField(blank=True, max_length=32, help_text="Short version of 'name' above, for use in filenames.")
type = ForeignKey(TimeSlotTypeName)
group = ForeignKey(Group) # The group type historically determined the session type. BOFs also need to be added as a group. Note that not all meeting requests have a natural group to associate with.
joint_with_groups = models.ManyToManyField(Group, related_name='sessions_joint_in')
attendees = models.IntegerField(null=True, blank=True)
agenda_note = models.CharField(blank=True, max_length=255)
requested_duration = models.DurationField(default=datetime.timedelta(0))
@ -1023,6 +1024,9 @@ class Session(models.Model):
def is_material_submission_cutoff(self):
return datetime.date.today() > self.meeting.get_submission_correction_date()
def joint_with_groups_acronyms(self):
return [group.acronym for group in self.joint_with_groups.all()]
def __str__(self):
if self.meeting.type_id == "interim":

View file

@ -22,6 +22,7 @@ NUM_SESSION_CHOICES = (('','--Please select'),('1','1'),('2','2'))
# LENGTH_SESSION_CHOICES = (('','--Please select'),('1800','30 minutes'),('3600','1 hour'),('5400','1.5 hours'), ('7200','2 hours'),('9000','2.5 hours'))
LENGTH_SESSION_CHOICES = (('','--Please select'),('1800','30 minutes'),('3600','1 hour'),('5400','1.5 hours'), ('7200','2 hours'))
SESSION_TIME_RELATION_CHOICES = (('', 'No preference'),) + Constraint.TIME_RELATION_CHOICES
JOINT_FOR_SESSION_CHOICES = (('1', 'First session'), ('2', 'Second session'), ('3', 'Third session'), )
# -------------------------------------------------
# Helper Functions
@ -86,10 +87,13 @@ class SessionForm(forms.Form):
conflict1 = forms.CharField(max_length=255,required=False)
conflict2 = forms.CharField(max_length=255,required=False)
conflict3 = forms.CharField(max_length=255,required=False)
joint_with_groups = forms.CharField(max_length=255,required=False)
joint_for_session = forms.ChoiceField(choices=JOINT_FOR_SESSION_CHOICES, required=False)
comments = forms.CharField(max_length=200,required=False)
wg_selector1 = forms.ChoiceField(choices=[],required=False)
wg_selector2 = forms.ChoiceField(choices=[],required=False)
wg_selector3 = forms.ChoiceField(choices=[],required=False)
wg_selector4 = forms.ChoiceField(choices=[],required=False)
third_session = forms.BooleanField(required=False)
resources = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple,required=False)
bethere = SearchablePersonsField(label="Must be present", required=False)
@ -115,7 +119,7 @@ class SessionForm(forms.Form):
other_groups = list(allowed_conflicting_groups().exclude(pk=group.pk).values_list('acronym', 'acronym').order_by('acronym'))
self.fields['adjacent_with_wg'].choices = [('', '--No preference')] + other_groups
group_acronym_choices = [('','--Select WG(s)')] + other_groups
for i in range(1, 4):
for i in range(1, 5):
self.fields['wg_selector{}'.format(i)].choices = group_acronym_choices
# disabling handleconflictfield (which only enables or disables form elements) while we're hacking the meaning of the three constraints currently in use:
@ -125,6 +129,7 @@ class SessionForm(forms.Form):
self.fields['wg_selector1'].widget.attrs['onChange'] = "document.form_post.conflict1.value=document.form_post.conflict1.value + ' ' + this.options[this.selectedIndex].value; return 1;"
self.fields['wg_selector2'].widget.attrs['onChange'] = "document.form_post.conflict2.value=document.form_post.conflict2.value + ' ' + this.options[this.selectedIndex].value; return 1;"
self.fields['wg_selector3'].widget.attrs['onChange'] = "document.form_post.conflict3.value=document.form_post.conflict3.value + ' ' + this.options[this.selectedIndex].value; return 1;"
self.fields['wg_selector4'].widget.attrs['onChange'] = "document.form_post.joint_with_groups.value=document.form_post.joint_with_groups.value + ' ' + this.options[this.selectedIndex].value; return 1;"
# disabling check_prior_conflict javascript while we're hacking the meaning of the three constraints currently in use
#self.fields['wg_selector2'].widget.attrs['onClick'] = "return check_prior_conflict(2);"
@ -159,7 +164,12 @@ class SessionForm(forms.Form):
conflict = self.cleaned_data['conflict3']
check_conflict(conflict, self.group)
return conflict
def clean_joint_with_groups(self):
groups = self.cleaned_data['joint_with_groups']
check_conflict(groups, self.group)
return groups
def clean(self):
super(SessionForm, self).clean()
data = self.cleaned_data
@ -180,12 +190,20 @@ class SessionForm(forms.Form):
if data.get('num_session','') == '2':
if not data['length_session2']:
raise forms.ValidationError('You must enter a length for all sessions')
elif data.get('session_time_relation'):
raise forms.ValidationError('Time between sessions can only be used when two sessions are requested.')
else:
if data.get('session_time_relation'):
raise forms.ValidationError('Time between sessions can only be used when two '
'sessions are requested.')
if data['joint_for_session'] == '2':
raise forms.ValidationError('The second session can not be the joint session, '
'because you have not requested a second session.')
if data.get('third_session',False):
if not data['length_session2'] or not data.get('length_session3',None):
raise forms.ValidationError('You must enter a length for all sessions')
elif data['joint_for_session'] == '3':
raise forms.ValidationError('The third session can not be the joint session, '
'because you have not requested a third session.')
return data

View file

@ -17,6 +17,7 @@ from ietf.meeting.models import Session, ResourceAssociation, SchedulingEvent, C
from ietf.meeting.factories import MeetingFactory, SessionFactory
from ietf.name.models import TimerangeName
from ietf.person.models import Person
from ietf.secr.sreq.forms import SessionForm
from ietf.utils.mail import outbox, empty_outbox
from pyquery import PyQuery
@ -82,6 +83,8 @@ class SessionRequestTestCase(TestCase):
meeting = MeetingFactory(type_id='ietf', date=datetime.date.today())
mars = RoleFactory(name_id='chair', person__user__username='marschairman', group__acronym='mars').group
group2 = GroupFactory()
group3 = GroupFactory()
group4 = GroupFactory()
SessionFactory(meeting=meeting,group=mars,status_id='sched')
url = reverse('ietf.secr.sreq.views.edit', kwargs={'acronym':'mars'})
@ -96,6 +99,8 @@ class SessionRequestTestCase(TestCase):
'comments':'need lights',
'session_time_relation': 'subsequent-days',
'adjacent_with_wg': group2.acronym,
'joint_with_groups': group3.acronym + ' ' + group4.acronym,
'joint_for_session': '2',
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
'submit': 'Continue'}
r = self.client.post(url, post_data, HTTP_HOST='example.com')
@ -112,12 +117,43 @@ class SessionRequestTestCase(TestCase):
list(session.constraints().get(name='timerange').timeranges.all().values('name')),
list(TimerangeName.objects.filter(name__in=['thursday-afternoon-early', 'thursday-afternoon-late']).values('name'))
)
self.assertFalse(sessions[0].joint_with_groups.count())
self.assertEqual(list(sessions[1].joint_with_groups.all()), [group3, group4])
# Check whether the updated data is visible on the view page
r = self.client.get(redirect_url)
self.assertContains(r, 'Schedule the sessions on subsequent days')
self.assertContains(r, 'Thursday early afternoon, Thursday late afternoon')
self.assertContains(r, group2.acronym)
self.assertContains(r, 'Second session with: {} {}'.format(group3.acronym, group4.acronym))
# Edit again, changing the joint sessions and clearing some fields. The behaviour of
# edit is different depending on whether previous joint sessions were recorded.
post_data = {'num_session':'2',
'length_session1':'3600',
'length_session2':'3600',
'attendees':'10',
'conflict1':'',
'comments':'need lights',
'joint_with_groups': group2.acronym,
'joint_for_session': '1',
'submit': 'Continue'}
r = self.client.post(url, post_data, HTTP_HOST='example.com')
self.assertRedirects(r, redirect_url)
# Check whether updates were stored in the database
sessions = Session.objects.filter(meeting=meeting, group=mars)
self.assertEqual(len(sessions), 2)
session = sessions[0]
self.assertFalse(session.constraints().filter(name='time_relation'))
self.assertFalse(session.constraints().filter(name='wg_adjacent'))
self.assertFalse(session.constraints().filter(name='timerange'))
self.assertEqual(list(sessions[0].joint_with_groups.all()), [group2])
self.assertFalse(sessions[1].joint_with_groups.count())
# Check whether the updated data is visible on the view page
r = self.client.get(redirect_url)
self.assertContains(r, 'First session with: {}'.format(group2.acronym))
def test_tool_status(self):
MeetingFactory(type_id='ietf', date=datetime.date.today())
@ -135,6 +171,8 @@ class SubmitRequestCase(TestCase):
area = RoleFactory(name_id='ad', person=ad, group__type_id='area').group
group = GroupFactory(parent=area)
group2 = GroupFactory(parent=area)
group3 = GroupFactory(parent=area)
group4 = GroupFactory(parent=area)
session_count_before = Session.objects.filter(meeting=meeting, group=group).count()
url = reverse('ietf.secr.sreq.views.new',kwargs={'acronym':group.acronym})
confirm_url = reverse('ietf.secr.sreq.views.confirm',kwargs={'acronym':group.acronym})
@ -146,6 +184,8 @@ class SubmitRequestCase(TestCase):
'comments':'need projector',
'adjacent_with_wg': group2.acronym,
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
'joint_with_groups': group3.acronym + ' ' + group4.acronym,
'joint_for_session': '1',
'submit': 'Continue'}
self.client.login(username="secretary", password="secretary+password")
r = self.client.post(url,post_data)
@ -154,6 +194,7 @@ class SubmitRequestCase(TestCase):
# Verify the contents of the confirm view
self.assertContains(r, 'Thursday early afternoon, Thursday late afternoon')
self.assertContains(r, group2.acronym)
self.assertContains(r, 'First session with: {} {}'.format(group3.acronym, group4.acronym))
post_data['submit'] = 'Submit'
r = self.client.post(confirm_url,post_data)
@ -166,6 +207,15 @@ class SubmitRequestCase(TestCase):
self.assertRedirects(r, main_url)
session_count_after = Session.objects.filter(meeting=meeting, group=group, type='regular').count()
self.assertEqual(session_count_after, session_count_before + 1)
# Verify database content
session = Session.objects.get(meeting=meeting, group=group)
self.assertEqual(session.constraints().get(name='wg_adjacent').target.acronym, group2.acronym)
self.assertEqual(
list(session.constraints().get(name='timerange').timeranges.all().values('name')),
list(TimerangeName.objects.filter(name__in=['thursday-afternoon-early', 'thursday-afternoon-late']).values('name'))
)
self.assertEqual(list(session.joint_with_groups.all()), [group3, group4])
def test_submit_request_invalid(self):
MeetingFactory(type_id='ietf', date=datetime.date.today())
@ -237,6 +287,7 @@ class SubmitRequestCase(TestCase):
RoleFactory(name_id='ad', person=ad, group=area)
group = GroupFactory(acronym='ames', parent=area)
group2 = GroupFactory(acronym='ames2', parent=area)
group3 = GroupFactory(acronym='ames2', parent=area)
RoleFactory(name_id='chair', group=group, person__user__username='ameschairman')
resource = ResourceAssociation.objects.create(name_id='project')
# Bit of a test data hack - the fixture now has no used resources to pick from
@ -256,6 +307,8 @@ class SubmitRequestCase(TestCase):
'resources': resource.pk,
'session_time_relation': 'subsequent-days',
'adjacent_with_wg': group2.acronym,
'joint_with_groups': group3.acronym,
'joint_for_session': '2',
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
'submit': 'Continue'}
self.client.login(username="ameschairman", password="ameschairman+password")
@ -284,10 +337,11 @@ class SubmitRequestCase(TestCase):
list(TimerangeName.objects.filter(name__in=['thursday-afternoon-early', 'thursday-afternoon-late']).values('name'))
)
resource = session.resources.first()
self.assertTrue(resource.desc in notification_payload)
self.assertTrue('Schedule the sessions on subsequent days' in notification_payload)
self.assertTrue(group2.acronym in notification_payload)
self.assertTrue("Can't meet: Thursday early afternoon, Thursday late" in notification_payload)
self.assertTrue(resource.desc in notification_payload)
self.assertTrue('Second session joint with: {}'.format(group3.acronym) in notification_payload)
self.assertTrue(ad.ascii_name() in notification_payload)
class LockAppTestCase(TestCase):
@ -359,9 +413,133 @@ class NotMeetingCase(TestCase):
class RetrievePreviousCase(TestCase):
pass
# test error if already scheduled
# test get previous exists/doesn't exist
# test that groups scheduled and unscheduled add up to total groups
# test access by unauthorized
class SessionFormTest(TestCase):
def setUp(self):
self.group1 = GroupFactory()
self.group2 = GroupFactory()
self.group3 = GroupFactory()
self.group4 = GroupFactory()
self.group5 = GroupFactory()
self.group6 = GroupFactory()
self.valid_form_data = {
'num_session': '2',
'third_session': 'true',
'length_session1': '3600',
'length_session2': '3600',
'length_session3': '3600',
'attendees': '10',
'conflict1': self.group2.acronym,
'conflict2': self.group3.acronym,
'conflict3': self.group4.acronym,
'comments': 'need lights',
'session_time_relation': 'subsequent-days',
'adjacent_with_wg': self.group5.acronym,
'joint_with_groups': self.group6.acronym,
'joint_for_session': '3',
'timeranges': ['thursday-afternoon-early', 'thursday-afternoon-late'],
'submit': 'Continue'
}
def test_valid(self):
# Test with three sessions
form = SessionForm(data=self.valid_form_data, group=self.group1)
self.assertTrue(form.is_valid())
# Test with two sessions
self.valid_form_data.update({
'length_session3': '',
'third_session': '',
'joint_for_session': '2'
})
form = SessionForm(data=self.valid_form_data, group=self.group1)
self.assertTrue(form.is_valid())
# Test with one session
self.valid_form_data.update({
'length_session2': '',
'num_session': 1,
'joint_for_session': '1',
'session_time_relation': '',
})
form = SessionForm(data=self.valid_form_data, group=self.group1)
self.assertTrue(form.is_valid())
def test_invalid_groups(self):
new_form_data = {
'conflict1': 'doesnotexist',
'conflict2': 'doesnotexist',
'conflict3': 'doesnotexist',
'adjacent_with_wg': 'doesnotexist',
'joint_with_groups': 'doesnotexist',
}
form = self._invalid_test_helper(new_form_data)
self.assertEqual(set(form.errors.keys()), set(new_form_data.keys()))
def test_invalid_group_appears_in_multiple_conflicts(self):
new_form_data = {
'conflict1': self.group2.acronym,
'conflict2': self.group2.acronym,
}
form = self._invalid_test_helper(new_form_data)
self.assertEqual(form.non_field_errors(), ['%s appears in conflicts more than once' % self.group2.acronym])
def test_invalid_conflict_with_self(self):
new_form_data = {
'conflict1': self.group1.acronym,
}
self._invalid_test_helper(new_form_data)
def test_invalid_session_time_relation(self):
form = self._invalid_test_helper({
'third_session': '',
'length_session2': '',
'num_session': 1,
'joint_for_session': '1',
})
self.assertEqual(form.non_field_errors(), ['Time between sessions can only be used when two '
'sessions are requested.'])
def test_invalid_joint_for_session(self):
form = self._invalid_test_helper({
'third_session': '',
'num_session': 2,
'joint_for_session': '3',
})
self.assertEqual(form.non_field_errors(), ['The third session can not be the joint session, '
'because you have not requested a third session.'])
form = self._invalid_test_helper({
'third_session': '',
'length_session2': '',
'num_session': 1,
'joint_for_session': '2',
'session_time_relation': '',
})
self.assertEqual(form.non_field_errors(), ['The second session can not be the joint session, '
'because you have not requested a second session.'])
def test_invalid_missing_session_length(self):
form = self._invalid_test_helper({
'length_session2': '',
'third_session': 'true',
})
self.assertEqual(form.non_field_errors(), ['You must enter a length for all sessions'])
form = self._invalid_test_helper({'length_session2': ''})
self.assertEqual(form.non_field_errors(), ['You must enter a length for all sessions'])
form = self._invalid_test_helper({'length_session3': ''})
self.assertEqual(form.non_field_errors(), ['You must enter a length for all sessions'])
def _invalid_test_helper(self, new_form_data):
form_data = dict(self.valid_form_data, **new_form_data)
form = SessionForm(data=form_data, group=self.group1)
self.assertFalse(form.is_valid())
return form

View file

@ -21,7 +21,7 @@ from ietf.meeting.models import Meeting, Session, Constraint, ResourceAssociatio
from ietf.meeting.helpers import get_meeting
from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.name.models import SessionStatusName, ConstraintName
from ietf.secr.sreq.forms import SessionForm, ToolStatusForm, allowed_conflicting_groups
from ietf.secr.sreq.forms import SessionForm, ToolStatusForm, allowed_conflicting_groups, JOINT_FOR_SESSION_CHOICES
from ietf.secr.utils.decorators import check_permissions
from ietf.secr.utils.group import get_my_groups
from ietf.utils.mail import send_mail
@ -88,6 +88,11 @@ def get_initial_session(sessions, prune_conflicts=False):
timeranges = conflicts.filter(name__slug='timerange')
initial['timeranges'] = timeranges[0].timeranges.all() if timeranges else []
initial['timeranges_display'] = [t.desc for t in initial['timeranges']]
for idx, session in enumerate(sessions):
if session.joint_with_groups.count():
initial['joint_with_groups'] = ' '.join(session.joint_with_groups_acronyms())
initial['joint_for_session'] = str(idx + 1)
initial['joint_for_session_display'] = dict(JOINT_FOR_SESSION_CHOICES)[initial['joint_for_session']]
return initial
def get_lock_message(meeting=None):
@ -273,6 +278,8 @@ def confirm(request, acronym):
session_data['bethere'] = Person.objects.filter(pk__in=person_id_list)
if session_data.get('session_time_relation'):
session_data['session_time_relation_display'] = dict(Constraint.TIME_RELATION_CHOICES)[session_data['session_time_relation']]
if session_data.get('joint_for_session'):
session_data['joint_for_session_display'] = dict(JOINT_FOR_SESSION_CHOICES)[session_data['joint_for_session']]
if form.cleaned_data.get('timeranges'):
session_data['timeranges_display'] = [t.desc for t in form.cleaned_data['timeranges']]
session_data['resources'] = [ ResourceAssociation.objects.get(pk=pk) for pk in request.POST.getlist('resources') ]
@ -314,6 +321,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:
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)
session_changed(new_session)
# write constraint records
@ -469,7 +480,30 @@ def edit(request, acronym, num=None):
session.save()
session_changed(session)
# New sessions may have been created, refresh the sessions list
sessions = add_event_info_to_session_qs(
Session.objects.filter(group=group, meeting=meeting)).filter(
Q(current_status__isnull=True) | ~Q(
current_status__in=['canceled', 'notmeet'])).order_by('id')
if 'joint_with_groups' in form.changed_data or 'joint_for_session' in form.changed_data:
joint_with_groups_list = form.cleaned_data.get('joint_with_groups').replace(',', ' ').split()
new_joint_with_groups = Group.objects.filter(acronym__in=joint_with_groups_list)
new_joint_for_session_idx = int(form.data.get('joint_for_session', '-1')) - 1
current_joint_for_session_idx = None
current_joint_with_groups = None
for idx, session in enumerate(sessions):
if session.joint_with_groups.count():
current_joint_for_session_idx = idx
current_joint_with_groups = session.joint_with_groups.all()
if current_joint_with_groups != new_joint_with_groups or current_joint_for_session_idx != new_joint_for_session_idx:
if current_joint_for_session_idx is not None:
sessions[current_joint_for_session_idx].joint_with_groups.clear()
session_changed(sessions[current_joint_for_session_idx])
sessions[new_joint_for_session_idx].joint_with_groups.set(new_joint_with_groups)
session_changed(sessions[new_joint_for_session_idx])
if 'attendees' in form.changed_data:
sessions.update(attendees=form.cleaned_data['attendees'])
if 'comments' in form.changed_data:

View file

@ -15,28 +15,34 @@ function stat_ls (val){
if (val == 0) {
document.form_post.length_session1.disabled = true;
document.form_post.length_session2.disabled = true;
document.form_post.length_session3.disabled = true;
if (document.form_post.length_session3) { document.form_post.length_session3.disabled = true; }
document.form_post.session_time_relation.disabled = true;
document.form_post.joint_for_session.disabled = true;
document.form_post.length_session1.value = 0;
document.form_post.length_session2.value = 0;
document.form_post.length_session3.value = 0;
document.form_post.session_time_relation.value = '';
document.form_post.joint_for_session.value = '';
document.form_post.third_session.checked=false;
}
if (val == 1) {
document.form_post.length_session1.disabled = false;
document.form_post.length_session2.disabled = true;
document.form_post.length_session3.disabled = true;
if (document.form_post.length_session3) { document.form_post.length_session3.disabled = true; }
document.form_post.session_time_relation.disabled = true;
document.form_post.joint_for_session.disabled = true;
document.form_post.length_session2.value = 0;
document.form_post.length_session3.value = 0;
document.form_post.session_time_relation.value = '';
document.form_post.joint_for_session.value = '1';
document.form_post.third_session.checked=false;
}
if (val == 2) {
document.form_post.length_session1.disabled = false;
document.form_post.length_session2.disabled = false;
if (document.form_post.length_session3) { document.form_post.length_session3.disabled = false; }
document.form_post.session_time_relation.disabled = false;
document.form_post.joint_for_session.disabled = false;
}
}
@ -116,6 +122,15 @@ function delete_last3 () {
document.form_post.conflict3.value = b;
document.form_post.wg_selector3.selectedIndex=0;
}
function delete_last_joint_with_groups () {
var b = document.form_post.joint_with_groups.value;
var temp = new Array();
temp = b.split(' ');
temp.pop();
b = temp.join(' ');
document.form_post.joint_with_groups.value = b;
document.form_post.wg_selector4.selectedIndex=0;
}
// Not calling check_prior_confict (see ietf/secr/sreq/forms.py definition of SessionForm)
// while we are hacking the use of the current three constraint types around. We could bring

View file

@ -3,6 +3,7 @@
Working Group Name: {{ group.name }}
Area Name: {{ group.parent }}
Session Requester: {{ login }}
{% if session.joint_with_groups %}{{ session.joint_for_session_display }} joint with: {{ session.joint_with_groups }}{% endif %}
Number of Sessions: {{ session.num_session }}
Length of Session(s): {{ session.length_session1|display_duration }}{% if session.length_session2 %}, {{ session.length_session2|display_duration }}{% endif %}{% if session.length_session3 %}, {{ session.length_session3|display_duration }}{% endif %}

View file

@ -72,6 +72,22 @@
<td>{{ form.adjacent_with_wg.errors }}{{ form.adjacent_with_wg }}</td>
</tr>
<tr class="bg1">
<td>
Joint session with:<br />
(To request one session for multiple WGs together.)
</td>
<td>{{ form.wg_selector4 }}
<input type="button" value="Delete the last entry" onClick="delete_last_joint_with_groups(); return 1;"><br>
{{ form.joint_with_groups.errors }}{{ form.joint_with_groups }}
</td>
</tr>
<tr class="bg1">
<td>
Of the sessions requested by this WG, the joint session, if applicable, is:
</td>
<td>{{ form.joint_for_session.errors }}{{ form.joint_for_session }}</td>
</tr>
<tr class="bg2">
<td valign="top">Special Requests:<br />&nbsp;<br />i.e. restrictions on meeting times / days, etc.</td>
<td>{{ form.comments.errors }}{{ form.comments }}</td>
</tr>

View file

@ -7,7 +7,7 @@
<tr class="row2"><td>Length of Session 1:</td><td>{{ session.length_session1|display_duration }}</td></tr>
{% if session.length_session2 %}
<tr class="row2"><td>Length of Session 2:</td><td>{{ session.length_session2|display_duration }}</td></tr>
<tr class="row2"><td>Time between sessions:</td><td>{{ session.session_time_relation_display|default:"No preference" }}</td></tr>
<tr class="row2"><td>Time between sessions:</td><td>{% if session.session_time_relation_display %}{{ session.session_time_relation_display }}{% else %}No preference{% endif %}</td></tr>
{% endif %}
{% if session.length_session3 %}
<tr class="row2"><td>Length of Session 3:</td><td>{{ session.length_session3|display_duration }}</td></tr>
@ -42,7 +42,17 @@
<td>Adjacent with WG:</td>
<td>{{ session.adjacent_with_wg|default:'No preference' }}</td>
</tr>
<tr class="row2">
<td>Joint session:</td>
<td>
{% if session.joint_with_groups %}
{{ session.joint_for_session_display }} with: {{ session.joint_with_groups }}
{% else %}
Not a joint session
{% endif %}
</td>
</tr>
{% autoescape off %}
<tr class="row2"><td>Special Requests:</td><td>{{ session.comments }}</td></tr>
<tr class="row1"><td>Special Requests:</td><td>{{ session.comments }}</td></tr>
{% endautoescape %}
</table>

View file

@ -62,6 +62,9 @@
<a href="{% url "ietf.secr.sreq.views.edit" num=meeting.number acronym=session.group.acronym %}">
{{session.group.acronym}}
</a>
{% if session.joint_with_groups.count %}
joint with {{ session.joint_with_groups_acronyms|join:' ' }}
{% endif %}
</th>
<td class="text-right">