Repository: ietf-tools/datatracker

Issue:
The session requests page needs to show non wg/rg requests
We are now using Sessions to model meetings of many group types other than rg/wg. These sessions should show on the /meeting/requests page.


Discussion:
Example: https://datatracker.ietf.org/group/tdd/meetings/

(In case this isn't already obvious) I don't think *all* other types of sessions should show on /meeting/requests -- particularly now that there are so many "Secretariat" group sessions for breaks, etc. I'm happy to help look at the options and determine which other kinds of groups/sessions should show on the page if that would be helpful. 
I think groupfeatures.has_meetings is probably a good first cut at what group types to include.
Currently, the page shows Sessions that exist for any group with a parent, then lists groups of various types as not having requested a session. These are:
```
type__in=['wg','rg','ag','rag','bof','program']
```
vs the types with `has_meetings`: 
```
GroupFeatures.objects.filter(has_meetings=True).values_list('type__pk', flat=True)
<QuerySet ['adhoc', 'ag', 'dir', 'iab', 'iabasg', 'ietf', 'program', 'rag', 'review', 'rg', 'team', 'wg']>
```
The 'bof' type does not appear, but there *is no* bof `GroupTypeName` so I think that's ok to drop.

Resolving patch:
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -2137,15 +2137,18 @@ def agenda_json(request, num=None):
 
 def meeting_requests(request, num=None):
     meeting = get_meeting(num)
-    sessions = Session.objects.requests().filter(
-        meeting__number=meeting.number,
-        group__parent__isnull=False
-    ).with_current_status().with_requested_by().exclude(
-        requested_by=0
-    ).order_by(
-        "group__parent__acronym", "current_status", "group__acronym"
-    ).prefetch_related(
-        "group","group__ad_role__person"
+    groups_to_show = Group.objects.filter(state_id='active', type__features__has_meetings=True)
+    sessions = list(
+        Session.objects.requests().filter(
+            meeting__number=meeting.number,
+            group__in=groups_to_show,
+        ).exclude(
+            purpose__in=('admin', 'social'),
+        ).with_current_status().with_requested_by().exclude(
+            requested_by=0
+        ).prefetch_related(
+            "group","group__ad_role__person"
+        )
     )
 
     status_names = {n.slug: n.name for n in SessionStatusName.objects.all()}
@@ -2154,12 +2157,34 @@ def meeting_requests(request, num=None):
     for s in sessions:
         s.current_status_name = status_names.get(s.current_status, s.current_status)
         s.requested_by_person = session_requesters.get(s.requested_by)
+        if s.group.parent and s.group.parent.type.slug == 'area':
+            s.display_area = s.group.parent
+        else:
+            s.display_area = None
+    sessions.sort(
+        key=lambda s: (
+            s.display_area.acronym if s.display_area is not None else 'zzzz',
+            s.current_status,
+            s.group.acronym,
+        ),
+    )
 
-    groups_not_meeting = Group.objects.filter(state='Active',type__in=['wg','rg','ag','rag','bof','program']).exclude(acronym__in = [session.group.acronym for session in sessions]).order_by("parent__acronym","acronym").prefetch_related("parent")
+    groups_not_meeting = groups_to_show.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,
-         "groups_not_meeting": groups_not_meeting})
+    return render(
+        request,
+        "meeting/requests.html",
+        {
+            "meeting": meeting,
+            "sessions": sessions,
+            "groups_not_meeting": groups_not_meeting,
+        },
+    )
 
 
 def get_sessions(num, acronym):


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
@@ -6398,17 +6398,86 @@ class SessionTests(TestCase):
 
     def test_meeting_requests(self):
         meeting = MeetingFactory(type_id='ietf')
+
+        # a couple non-wg group types, confirm that their has_meetings features are as expected
+        group_type_with_meetings = 'adhoc'
+        self.assertTrue(GroupFeatures.objects.get(pk=group_type_with_meetings).has_meetings)
+        group_type_without_meetings = 'editorial'
+        self.assertFalse(GroupFeatures.objects.get(pk=group_type_without_meetings).has_meetings)
+
         area = GroupFactory(type_id='area')
         requested_session = SessionFactory(meeting=meeting,group__parent=area,status_id='schedw',add_to_schedule=False)
         conflicting_session = SessionFactory(meeting=meeting,group__parent=area,status_id='schedw',add_to_schedule=False)
         ConstraintFactory(name_id='key_participant',meeting=meeting,source=requested_session.group,target=conflicting_session.group)
         not_meeting = SessionFactory(meeting=meeting,group__parent=area,status_id='notmeet',add_to_schedule=False)
+        has_meetings = SessionFactory(
+            meeting=meeting,
+            group__type_id=group_type_with_meetings,
+            status_id='schedw',
+            add_to_schedule=False,
+        )
+        has_meetings_not_meeting = SessionFactory(
+            meeting=meeting,
+            group__type_id=group_type_with_meetings,
+            status_id='notmeet',
+            add_to_schedule=False,
+        )
+        # admin and social sessions are not to be shown on the requests page
+        has_meetings_admin_session = SessionFactory(
+            meeting=meeting,
+            group__type_id=group_type_with_meetings,
+            status_id='schedw',
+            purpose_id='admin',
+            type_id='other',
+            add_to_schedule=False,
+        )
+        has_meetings_social_session = SessionFactory(
+            meeting=meeting,
+            group__type_id=group_type_with_meetings,
+            status_id='schedw',
+            purpose_id='social',
+            type_id='break',
+            add_to_schedule=False,
+        )
+        not_has_meetings = SessionFactory(
+            meeting=meeting,
+            group__type_id=group_type_without_meetings,
+            status_id='schedw',
+            add_to_schedule=False,
+        )
+
+        def _sreq_edit_link(sess):
+            return urlreverse(
+                'ietf.secr.sreq.views.edit',
+                kwargs={
+                    'num': meeting.number,
+                    'acronym': sess.group.acronym,
+                },
+            )
+
         url = urlreverse('ietf.meeting.views.meeting_requests',kwargs={'num':meeting.number})
         r = self.client.get(url)
+        # requested_session group should be listed with a link to the request
         self.assertContains(r, requested_session.group.acronym)
+        self.assertContains(r, _sreq_edit_link(requested_session))  # link to the session request
         self.assertContains(r, not_meeting.group.acronym)
+        # The admin/social session groups should be listed under "no timeslot request received"; it's easier
+        # to check that the group is listed but that there is no link to the session request than to try to
+        # parse the HTML. If the view is changed to link to the "no timeslot request received" session requests,
+        # then need to revisit.
+        self.assertContains(r, has_meetings_admin_session.group.acronym)
+        self.assertNotContains(r, _sreq_edit_link(has_meetings_admin_session))  # no link to the session request
+        self.assertContains(r, has_meetings_social_session.group.acronym)
+        self.assertNotContains(r, _sreq_edit_link(has_meetings_social_session))  # no link to the session request
         self.assertContains(r, requested_session.constraints().first().name)
         self.assertContains(r, conflicting_session.group.acronym)
+        self.assertContains(r, _sreq_edit_link(conflicting_session))  # link to the session request
+        self.assertContains(r, has_meetings.group.acronym)
+        self.assertContains(r, _sreq_edit_link(has_meetings))  # link to the session request
+        self.assertContains(r, has_meetings_not_meeting.group.acronym)
+        self.assertContains(r, _sreq_edit_link(has_meetings_not_meeting))  # link to the session request
+        self.assertNotContains(r, not_has_meetings.group.acronym)
+        self.assertNotContains(r, _sreq_edit_link(not_has_meetings))  # no link to the session request
 
     def test_request_minutes(self):
         meeting = MeetingFactory(type_id='ietf')