diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
index 9d7fc8f28..38a051b91 100644
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -7310,7 +7310,12 @@ class ProceedingsTests(BaseMeetingTestCase):
)
def test_proceedings(self):
- """Proceedings should be displayed correctly"""
+ """Proceedings should be displayed correctly
+
+ Currently only tests that the view responds with a 200 response code and checks the ProceedingsMaterials
+ at the top of the proceedings. Ought to actually test the display of the individual group/session
+ materials as well.
+ """
meeting = make_meeting_test_data(meeting=MeetingFactory(type_id='ietf', number='100'))
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
GroupEventFactory(group=session.group,type='status_update')
@@ -7364,6 +7369,65 @@ class ProceedingsTests(BaseMeetingTestCase):
self._assertMeetingHostsDisplayed(r, meeting)
self._assertProceedingsMaterialsDisplayed(r, meeting)
+ def test_named_session(self):
+ """Session with a name should appear separately in the proceedings"""
+ meeting = MeetingFactory(type_id='ietf', number='100')
+ group = GroupFactory()
+ plain_session = SessionFactory(meeting=meeting, group=group)
+ named_session = SessionFactory(meeting=meeting, group=group, name='I Got a Name')
+ for doc_type_id in ('agenda', 'minutes', 'bluesheets', 'recording', 'slides', 'draft'):
+ # Set up sessions materials that will have distinct URLs for each session.
+ # This depends on settings.MEETING_DOC_HREFS and may need updating if that changes.
+ SessionPresentationFactory(
+ session=plain_session,
+ document__type_id=doc_type_id,
+ document__uploaded_filename=f'upload-{doc_type_id}-plain',
+ document__external_url=f'external_url-{doc_type_id}-plain',
+ )
+ SessionPresentationFactory(
+ session=named_session,
+ document__type_id=doc_type_id,
+ document__uploaded_filename=f'upload-{doc_type_id}-named',
+ document__external_url=f'external_url-{doc_type_id}-named',
+ )
+
+ url = urlreverse('ietf.meeting.views.proceedings', kwargs={'num': meeting.number})
+ r = self.client.get(url)
+ self.assertEqual(r.status_code, 200)
+ q = PyQuery(r.content)
+
+ plain_label = q(f'div#{group.acronym}')
+ self.assertEqual(plain_label.text(), group.acronym)
+ plain_row = plain_label.closest('tr')
+ self.assertTrue(plain_row)
+
+ named_label = q(f'div#{slugify(named_session.name)}')
+ self.assertEqual(named_label.text(), named_session.name)
+ named_row = named_label.closest('tr')
+ self.assertTrue(named_row)
+
+ for material in (sp.document for sp in plain_session.sessionpresentation_set.all()):
+ if material.type_id == 'draft':
+ expected_url = urlreverse(
+ 'ietf.doc.views_doc.document_main',
+ kwargs={'name': material.canonical_name()},
+ )
+ else:
+ expected_url = material.get_href(meeting)
+ self.assertTrue(plain_row.find(f'a[href="{expected_url}"]'))
+ self.assertFalse(named_row.find(f'a[href="{expected_url}"]'))
+
+ for material in (sp.document for sp in named_session.sessionpresentation_set.all()):
+ if material.type_id == 'draft':
+ expected_url = urlreverse(
+ 'ietf.doc.views_doc.document_main',
+ kwargs={'name': material.canonical_name()},
+ )
+ else:
+ expected_url = material.get_href(meeting)
+ self.assertFalse(plain_row.find(f'a[href="{expected_url}"]'))
+ self.assertTrue(named_row.find(f'a[href="{expected_url}"]'))
+
def test_proceedings_no_agenda(self):
# Meeting number must be larger than the last special-cased proceedings (currently 96)
meeting = MeetingFactory(type_id='ietf',populate_schedule=False,date=date_today(), number='100')
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
index b90f2506a..9415be9bc 100644
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -3586,6 +3586,74 @@ def upcoming_json(request):
response = HttpResponse(json.dumps(data, indent=2, sort_keys=False), content_type='application/json;charset=%s'%settings.DEFAULT_CHARSET)
return response
+def organize_proceedings_sessions(sessions):
+ # Collect sessions by Group, then bin by session name (including sessions with blank names).
+ # If all of a group's sessions are 'notmeet', the processed data goes in not_meeting_sessions.
+ # Otherwise, the data goes in meeting_sessions.
+ meeting_groups = []
+ not_meeting_groups = []
+ for group_acronym, group_sessions in itertools.groupby(sessions, key=lambda s: s.group.acronym):
+ by_name = {}
+ is_meeting = False
+ all_canceled = True
+ group = None
+ for s in sorted(
+ group_sessions,
+ key=lambda gs: (
+ gs.official_timeslotassignment().timeslot.time
+ if gs.official_timeslotassignment() else datetime.datetime(datetime.MAXYEAR, 1, 1)
+ ),
+ ):
+ group = s.group
+ if s.current_status != 'notmeet':
+ is_meeting = True
+ if s.current_status != 'canceled':
+ all_canceled = False
+ by_name.setdefault(s.name, [])
+ if s.current_status != 'notmeet' or s.sessionpresentation_set.exists():
+ by_name[s.name].append(s) # for notmeet, only include sessions with materials
+ for sess_name, ss in by_name.items():
+ def _format_materials(items):
+ """Format session/material for template
+
+ Input is a list of (session, materials) pairs. The materials value can be a single value or a list.
+ """
+ material_times = {} # key is material, value is first timestamp it appeared
+ for s, mats in items:
+ timestamp = s.official_timeslotassignment().timeslot.time
+ if not isinstance(mats, list):
+ mats = [mats]
+ for mat in mats:
+ if mat and mat not in material_times:
+ material_times[mat] = timestamp
+ n_mats = len(material_times)
+ result = []
+ if n_mats == 1:
+ result.append({'material': list(material_times)[0]}) # no 'time' when only a single material
+ elif n_mats > 1:
+ for mat, timestamp in material_times.items():
+ result.append({'material': mat, 'time': timestamp})
+ return result
+
+ entry = {
+ 'group': group,
+ 'name': sess_name,
+ 'canceled': all_canceled,
+ # pass sessions instead of the materials here so session data (like time) is easily available
+ 'agendas': _format_materials((s, s.agenda()) for s in ss),
+ 'minutes': _format_materials((s, s.minutes()) for s in ss),
+ 'bluesheets': _format_materials((s, s.bluesheets()) for s in ss),
+ 'recordings': _format_materials((s, s.recordings()) for s in ss),
+ 'slides': _format_materials((s, s.slides()) for s in ss),
+ 'drafts': _format_materials((s, s.drafts()) for s in ss),
+ }
+ if is_meeting:
+ meeting_groups.append(entry)
+ else:
+ not_meeting_groups.append(entry)
+ return meeting_groups, not_meeting_groups
+
+
def proceedings(request, num=None):
meeting = get_meeting(num)
@@ -3606,36 +3674,48 @@ def proceedings(request, num=None):
today_utc = date_today(datetime.timezone.utc)
schedule = get_schedule(meeting, None)
- sessions = add_event_info_to_session_qs(
- Session.objects.filter(meeting__number=meeting.number)
- ).filter(
- Q(timeslotassignments__schedule__in=[schedule, schedule.base if schedule else None]) | Q(current_status='notmeet')
- ).select_related().order_by('-current_status')
- plenaries = sessions.filter(name__icontains='plenary').exclude(current_status='notmeet')
- ietf = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu')
- irtf = sessions.filter(group__parent__acronym = 'irtf')
- training = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['regular', 'other',]).exclude(current_status='notmeet')
- iab = sessions.filter(group__parent__acronym = 'iab').exclude(current_status='notmeet')
+ sessions = (
+ meeting.session_set.with_current_status()
+ .filter(Q(timeslotassignments__schedule__in=[schedule, schedule.base if schedule else None])
+ | Q(current_status='notmeet'))
+ .select_related()
+ .order_by('-current_status')
+ )
+
+ plenaries, _ = organize_proceedings_sessions(
+ sessions.filter(name__icontains='plenary')
+ .exclude(current_status='notmeet')
+ )
+ irtf, _ = organize_proceedings_sessions(
+ sessions.filter(group__parent__acronym = 'irtf').order_by('group__acronym')
+ )
+ training, _ = organize_proceedings_sessions(
+ sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['regular', 'other',])
+ .exclude(current_status='notmeet')
+ )
+ iab, _ = organize_proceedings_sessions(
+ sessions.filter(group__parent__acronym = 'iab')
+ .exclude(current_status='notmeet')
+ )
+
+ ietf = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu').order_by('group__parent__acronym', 'group__acronym')
+ ietf_areas = []
+ for area, area_sessions in itertools.groupby(
+ ietf,
+ key=lambda s: s.group.parent
+ ):
+ meeting_groups, not_meeting_groups = organize_proceedings_sessions(area_sessions)
+ ietf_areas.append((area, meeting_groups, not_meeting_groups))
cache_version = Document.objects.filter(session__meeting__number=meeting.number).aggregate(Max('time'))["time__max"]
- ietf_areas = []
- for area, sessions in itertools.groupby(sorted(ietf, key=lambda s: (s.group.parent.acronym, s.group.acronym)), key=lambda s: s.group.parent):
- sessions = list(sessions)
- meeting_groups = set(s.group_id for s in sessions if s.current_status != 'notmeet')
- meeting_sessions = []
- not_meeting_sessions = []
- for s in sessions:
- if s.current_status == 'notmeet' and s.group_id not in meeting_groups:
- not_meeting_sessions.append(s)
- else:
- meeting_sessions.append(s)
- ietf_areas.append((area, meeting_sessions, not_meeting_sessions))
-
with timezone.override(meeting.tz()):
return render(request, "meeting/proceedings.html", {
'meeting': meeting,
- 'plenaries': plenaries, 'ietf': ietf, 'training': training, 'irtf': irtf, 'iab': iab,
+ 'plenaries': plenaries,
+ 'training': training,
+ 'irtf': irtf,
+ 'iab': iab,
'ietf_areas': ietf_areas,
'cut_off_date': cut_off_date,
'cor_cut_off_date': cor_cut_off_date,
diff --git a/ietf/templates/meeting/group_proceedings.html b/ietf/templates/meeting/group_proceedings.html
index 99722fca4..1e0bcfe06 100644
--- a/ietf/templates/meeting/group_proceedings.html
+++ b/ietf/templates/meeting/group_proceedings.html
@@ -5,111 +5,85 @@
{% load proceedings_filters %}
- {% if session.name %}
- {{ session.name }}
- {% else %}
-
- {{ session.group.acronym }}
+ {% if entry.name %}
+ {{ entry.name }}
+ {% elif entry.group.acronym %}
+
- {% if session.group.state_id == "bof" %} BOF{% endif %}
+ {% if entry.group.state_id == "bof" %} BOF{% endif %}
+ {% else %}
+ {{ entry.group }}
{% endif %}
|
- {% if session.all_meeting_sessions_cancelled %}
+ {% if entry.canceled %}
Session cancelled
|
{% else %}
+ {# artifacts #}
- {% if session.all_meeting_agendas %}
- {% if session.all_meeting_agendas|length == 1 %}
- Agenda
-
- {% else %}
- {% for agenda in session.all_meeting_agendas %}
-
- Agenda {{ agenda.sessionpresentation_set.first.session.official_timeslotassignment.timeslot.time|date:"D G:i" }}
-
-
- {% endfor %}
- {% endif %}
- {% else %}
- {% if show_agenda == "True" and not meeting.proceedings_final %}
+ {% for agenda in entry.agendas %}
+
+ Agenda
+ {% if agenda.time %}{{agenda.time|date:"D G:i"}}{% endif %}
+
+
+ {% empty %}
+ {% if show_agenda and not meeting.proceedings_final %}
No agenda
{% endif %}
- {% endif %}
- {% if session.all_meeting_minutes %}
- {% if session.all_meeting_minutes|length == 1 %}
- Minutes
-
- {% else %}
- {% for minutes in session.all_meeting_minutes %}
-
- Minutes {{ minutes.sessionpresentation_set.first.session.official_timeslotassignment.timeslot.time|date:"D G:i" }}
-
-
- {% endfor %}
- {% endif %}
- {% else %}
- {% if show_agenda == "True" and not meeting.proceedings_final %}
+ {% endfor %}
+ {% for minutes in entry.minutes %}
+
+ Minutes
+ {% if minutes.time %}{{minutes.time|date:"D G:i"}}{% endif %}
+
+
+ {% empty %}
+ {% if show_agenda and not meeting.proceedings_final %}
No minutes
{% endif %}
- {% endif %}
- {% if session.all_meeting_bluesheets %}
- {% if session.all_meeting_bluesheets|length == 1 %}
- Bluesheets
-
- {% else %}
- {% for bs in session.all_meeting_bluesheets %}
-
- Bluesheets {{ bs.sessionpresentation_set.first.session.official_timeslotassignment.timeslot.time|date:"D G:i" }}
-
-
- {% endfor %}
- {% endif %}
- {% endif %}
- {% with session.group|status_for_meeting:meeting as status %}
- {% if status %}
-
- Status
-
-
- {% endif %}
- {% endwith %}
+ {% endfor %}
+ {% for bs in entry.bluesheets %}
+
+ Bluesheets
+ {% if bs.time %}{{ bs.time|date:"D G:i" }}{% endif %}
+
+
+ {% endfor %}
|
+ {# recordings #}
- {% if session.all_meeting_sessions_for_group|length == 1 %}
- {% for rec in session.all_meeting_recordings %}
- {{ rec|hack_recording_title:False }}
-
- {% endfor %}
- {% else %}
- {% for rec in session.all_meeting_recordings %}
- {{ rec|hack_recording_title:True }}
-
- {% endfor %}
- {% endif %}
+ {% for rec in entry.recordings %}
+
+ {{ rec.material|hack_recording_title }}
+ {% if rec.time %}{{ rec.time|date:"D G:i"}}{% endif %}
+
+
+ {% endfor %}
|
+ {# slides #}
- {% with session.all_meeting_slides as slides %}
- {% for slide in slides %}
- {{ slide.title|clean_whitespace }}
-
- {% empty %}
- {% if not meeting.proceedings_final %}No slides{% endif %}
- {% endfor %}
- {% endwith %}
+ {% for slide in entry.slides %}
+ {{ slide.material.title|clean_whitespace }}
+
+ {% empty %}
+ {% if not meeting.proceedings_final %}No slides{% endif %}
+ {% endfor %}
|
+ {# drafts #}
- {% with session.all_meeting_drafts as drafts %}
- {% for draft in drafts %}
- {{ draft.canonical_name }}
-
- {% empty %}
- {% if not meeting.proceedings_final %}No drafts{% endif %}
- {% endfor %}
- {% endwith %}
+ {% for draft in entry.drafts %}
+
+ {{ draft.material.canonical_name }}
+
+
+ {% empty %}
+ {% if not meeting.proceedings_final %}No drafts{% endif %}
+ {% endfor %}
|
{% endif %}
-
\ No newline at end of file
+
diff --git a/ietf/templates/meeting/proceedings.html b/ietf/templates/meeting/proceedings.html
index 687c5dac1..6f3f2cda4 100644
--- a/ietf/templates/meeting/proceedings.html
+++ b/ietf/templates/meeting/proceedings.html
@@ -23,10 +23,32 @@
{% load cache %}
{% cache 900 ietf_meeting_proceedings meeting.number cache_version %}
{% include 'meeting/proceedings/introduction.html' with meeting=meeting only %}
- {% with "True" as show_agenda %}
-
- {% if plenaries %}
- Plenaries
+
+ {% if plenaries %}
+ Plenaries
+
+
+
+ Group |
+ Artifacts |
+ Recordings |
+ Slides |
+ Drafts |
+
+
+
+ {% for entry in plenaries %}
+ {% include "meeting/group_proceedings.html" with entry=entry meeting=meeting show_agenda=True only %}
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% for area, meeting_groups, not_meeting_groups in ietf_areas %}
+
+ {{ area.acronym|upper }} {{ area.name }}
+
+ {% if meeting_groups %}
@@ -38,161 +60,125 @@
- {% for session in plenaries %}
- {% include "meeting/group_proceedings.html" %}
+ {% for entry in meeting_groups %}
+ {% include "meeting/group_proceedings.html" with entry=entry meeting=meeting show_agenda=True only %}
{% endfor %}
{% endif %}
-
- {% for area, meeting_sessions, not_meeting_sessions in ietf_areas %}
-
- {{ area.acronym|upper }} {{ area.name }}
-
- {% if meeting_sessions %}
-
-
-
- Group |
- Artifacts |
- Recordings |
- Slides |
- Drafts |
-
-
-
- {% for session in meeting_sessions %}
- {% ifchanged session.group.acronym %}
- {% include "meeting/group_proceedings.html" %}
- {% endifchanged %}
- {% endfor %}
-
-
- {% endif %}
- {% if not_meeting_sessions %}
-
- {{ area.name }} groups not meeting:
- {% for session in not_meeting_sessions %}
- {% ifchanged session.group.acronym %}
- {{ session.group.acronym }}{% if not forloop.last %},{% endif %}
- {% endifchanged %}
- {% endfor %}
-
-
-
-
- |
- |
- |
- |
- |
-
-
-
- {% for session in not_meeting_sessions %}
- {% ifchanged session.group.acronym %}
- {% if session.sessionpresentation_set.exists %}
- {% include "meeting/group_proceedings.html" %}
- {% endif %}
- {% endifchanged %}
- {% endfor %}
-
-
- {% endif %}
- {% endfor %}
-
- {% if training %}
- {% with "False" as show_agenda %}
- Training
-
-
-
- Group |
- Artifacts |
- Recordings |
- Slides |
- Drafts |
-
-
-
- {% for session in training %}
- {% ifchanged %}
- {% include "meeting/group_proceedings.html" %}
- {% endifchanged %}
- {% endfor %}
-
-
- {% endwith %}
- {% endif %}
-
- {% if iab %}
-
- IAB Internet Architecture Board
-
-
+ {% if not_meeting_groups %}
+
+ {{ area.name }} groups not meeting:
+ {% for entry in not_meeting_groups %}
+ {% if entry.name == "" %}{# do not show named sessions in this list #}
+
+ {{ entry.group.acronym }}
+ {% if not forloop.last %},{% endif %}
+ {% endif %}
+ {% endfor %}
+
+
-
- Group
- |
-
- Artifacts
- |
-
- Recordings
- |
-
- Slides
- |
-
- Drafts
- |
+ |
+ |
+ |
+ |
+ |
- {% for session in iab %}
- {% ifchanged %}
- {% include "meeting/group_proceedings.html" %}
- {% endifchanged %}
- {% endfor %}
+ {% for entry in not_meeting_groups %}{% if entry.sessions_with_materials %}
+ {% include "meeting/group_proceedings.html" with entry=entry meeting=meeting show_agenda=True only %}
+ {% endif %}{% endfor %}
{% endif %}
-
- {% if irtf %}
-
- IRTF Internet Research Task Force
-
-
-
-
-
- Group
- |
-
- Artifacts
- |
-
- Recordings
- |
-
- Slides
- |
-
- Drafts
- |
-
-
-
- {% for session in irtf|dictsort:"group.acronym" %}
- {% ifchanged %}
- {% include "meeting/group_proceedings.html" %}
- {% endifchanged %}
- {% endfor %}
-
-
- {% endif %}
- {% endwith %}
+ {% endfor %}
+
+ {% if training %}
+ Training
+
+
+
+ Group |
+ Artifacts |
+ Recordings |
+ Slides |
+ Drafts |
+
+
+
+ {% for entry in training %}
+ {% include "meeting/group_proceedings.html" with entry=entry meeting=meeting show_agenda=False only %}
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% if iab %}
+
+ IAB Internet Architecture Board
+
+
+
+
+
+ Group
+ |
+
+ Artifacts
+ |
+
+ Recordings
+ |
+
+ Slides
+ |
+
+ Drafts
+ |
+
+
+
+ {% for entry in iab %}
+ {% include "meeting/group_proceedings.html" with entry=entry meeting=meeting show_agenda=True only %}
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% if irtf %}
+
+ IRTF Internet Research Task Force
+
+
+
+
+
+ Group
+ |
+
+ Artifacts
+ |
+
+ Recordings
+ |
+
+ Slides
+ |
+
+ Drafts
+ |
+
+
+
+ {% for entry in irtf %}
+ {% include "meeting/group_proceedings.html" with entry=entry meeting=meeting show_agenda=True only %}
+ {% endfor %}
+
+
+ {% endif %}
{% endcache %}
{% endblock %}
{% block js %}