Repository: ietf-tools/datatracker

Issue:
/meeting/materials should split out named sessions the same way /meeting/proceedings does
Right now, in /meeting/116/proceedings/, the emodir sessions are separated by session name. They are not separated, and appear all in one row at /meeting/116/materials. The materials view should behave the same as the proceedings view in this case.


Discussion:
(See #5458)

Resolving patch:
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -155,7 +155,7 @@ def materials(request, num=None):
     ).distinct().select_related('meeting__schedule', 'group__state', 'group__parent')).order_by('group__acronym')
 
     plenaries = sessions.filter(name__icontains='plenary')
-    ietf      = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu')
+    ietf      = sessions.filter(group__parent__type__slug = 'area').exclude(group__acronym='edu').order_by('group__parent__acronym', 'group__acronym')
     irtf      = sessions.filter(group__parent__acronym = 'irtf')
     training  = sessions.filter(group__acronym__in=['edu','iaoc'], type_id__in=['regular', 'other', ])
     iab       = sessions.filter(group__parent__acronym = 'iab')
@@ -179,12 +179,26 @@ def materials(request, num=None):
         for type_name in ProceedingsMaterialTypeName.objects.all()
     ]
 
+    plenaries, _ = organize_proceedings_sessions(plenaries)
+    irtf, _ = organize_proceedings_sessions(irtf)
+    training, _ = organize_proceedings_sessions(training)
+    iab, _ = organize_proceedings_sessions(iab)
+    other, _ = organize_proceedings_sessions(other)
+
+    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))
+
     with timezone.override(meeting.tz()):
         return render(request, "meeting/materials.html", {
             'meeting': meeting,
             'proceedings_materials': proceedings_materials,
             'plenaries': plenaries,
-            'ietf': ietf,
+            'ietf_areas': ietf_areas,
             'training': training,
             'irtf': irtf,
             'iab': iab,
@@ -3671,6 +3685,7 @@ def organize_proceedings_sessions(sessions):
             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():
+            session = ss[0] if ss else None
             def _format_materials(items):
                 """Format session/material for template
 
@@ -3697,6 +3712,7 @@ def _format_materials(items):
             entry = {
                 'group': group,
                 'name': sess_name,
+                'session': session,
                 'canceled': all_canceled,
                 'has_materials': s.sessionpresentation_set.exists(),
                 'agendas': _format_materials((s, s.agenda()) for s in ss),
@@ -3705,6 +3721,7 @@ def _format_materials(items):
                 '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),
+                'last_update': session.last_update if hasattr(session, 'last_update') else None
             }
             if is_meeting:
                 meeting_groups.append(entry)


Test patch:
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -508,6 +508,66 @@ def test_interim_materials(self):
 
         self.do_test_materials(meeting, session)
 
+    def test_named_session(self):
+        """Session with a name should appear separately in the materials"""
+        meeting = MeetingFactory(type_id='ietf', number='100')
+        meeting.importantdate_set.create(name_id='revsub',date=date_today() + datetime.timedelta(days=20))
+        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', '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.materials', 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}"]'))
+
     @override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True)
     def test_meeting_materials_non_utf8(self):
         meeting = make_meeting_test_data()