Repository: ietf-tools/datatracker

Issue:
Limit schedule generator by session purpose
### Description

Requested in the comments on #4533, 

> It would also be useful to add flags to have the tool consider only sessions and timeslots of a particular purpose.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:

Resolving patch:
diff --git a/ietf/meeting/management/commands/generate_schedule.py b/ietf/meeting/management/commands/generate_schedule.py
--- a/ietf/meeting/management/commands/generate_schedule.py
+++ b/ietf/meeting/management/commands/generate_schedule.py
@@ -25,6 +25,8 @@
 from ietf.person.models import Person
 from ietf.meeting import models
 from ietf.meeting.helpers import get_person_by_email
+from ietf.name.models import SessionPurposeName
+
 
 # 40 runs of the optimiser for IETF 106 with cycles=160 resulted in 16
 # zero-violation invocations, with a mean number of runs of 91 and 
@@ -72,18 +74,31 @@ def add_arguments(self, parser):
                                 'Base schedule for generated schedule, specified as "[owner/]name"'
                                 ' (default is no base schedule; owner not required if name is unique)'
                             ))
+        parser.add_argument('-p', '--purpose',
+                            dest='purposes',
+                            action='append',
+                            choices=[
+                                spn.slug for spn in SessionPurposeName.objects.all()
+                                if 'regular' in spn.timeslot_types  # scheduler only works with "regular" timeslots
+                            ],
+                            default=None,
+                            help=(
+                                'Limit scheduling to specified purpose '
+                                '(use option multiple times to specify more than one purpose; default is all purposes)'
+                            ))
 
-    def handle(self, meeting, name, max_cycles, verbosity, base_id, *args, **kwargs):
-        ScheduleHandler(self.stdout, meeting, name, max_cycles, verbosity, base_id).run()
+    def handle(self, meeting, name, max_cycles, verbosity, base_id, purposes, *args, **kwargs):
+        ScheduleHandler(self.stdout, meeting, name, max_cycles, verbosity, base_id, purposes).run()
 
 
 class ScheduleHandler(object):
     def __init__(self, stdout, meeting_number, name=None, max_cycles=OPTIMISER_MAX_CYCLES,
-                 verbosity=1, base_id=None):
+                 verbosity=1, base_id=None, session_purposes=None):
         self.stdout = stdout
         self.verbosity = verbosity
         self.name = name
         self.max_cycles = max_cycles
+        self.session_purposes = session_purposes
         if meeting_number:
             try:
                 self.meeting = models.Meeting.objects.get(type="ietf", number=meeting_number)
@@ -114,6 +129,10 @@ def __init__(self, stdout, meeting_number, name=None, max_cycles=OPTIMISER_MAX_C
                 msgs.append('Applying schedule {} as base schedule'.format(ScheduleId.from_schedule(self.base_schedule)))
             self.stdout.write('\n{}\n\n'.format('\n'.join(msgs)))
         self._load_meeting()
+        if len(self.schedule.sessions) == 0:
+            raise CommandError('No sessions found to schedule')
+        if len(self.schedule.timeslots) == 0:
+            raise CommandError('No timeslots found for schedule')
 
     def run(self):
         """Schedule all sessions"""
@@ -194,6 +213,8 @@ def _sessions_to_schedule(self, *args, **kwargs):
         Extra arguments are passed to the Session constructor.
         """
         sessions_db = self.meeting.session_set.that_can_be_scheduled().filter(type_id='regular')
+        if self.session_purposes is not None:
+            sessions_db = sessions_db.filter(purpose__slug__in=self.session_purposes)
         if self.base_schedule is None:
             fixed_sessions = models.Session.objects.none()
         else:


Test patch: