From 1fc2042265b04616ad114336bd23329c59fcbc05 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@painless-security.com>
Date: Thu, 9 Feb 2023 19:02:41 -0400
Subject: [PATCH] feat: Allow agenda_note to be edited from the schedule editor
 (#5115)

* feat: Add agenda_note to the SessionDetailsForm

* fix: Add hidden agenda_note field to session_details_form.html

* fix: Stop using agenda_note to filter sessions in import_audio_files()

* chore: Migrate sessions with agenda_note='CANCELED' to canceled state
---
 ietf/meeting/forms.py                         |  3 +-
 .../0060_normalize_canceled_sessions.py       | 49 +++++++++++++++++++
 ietf/secr/proceedings/proc_utils.py           |  8 +--
 .../meeting/session_details_form.html         |  2 +-
 4 files changed, 54 insertions(+), 8 deletions(-)
 create mode 100644 ietf/meeting/migrations/0060_normalize_canceled_sessions.py

diff --git a/ietf/meeting/forms.py b/ietf/meeting/forms.py
index 477a1dbb9..165308124 100644
--- a/ietf/meeting/forms.py
+++ b/ietf/meeting/forms.py
@@ -743,7 +743,8 @@ class SessionDetailsForm(forms.ModelForm):
         model = Session
         fields = (
             'purpose', 'name', 'short', 'type', 'requested_duration',
-            'on_agenda', 'remote_instructions', 'attendees', 'comments',
+            'on_agenda', 'agenda_note', 'remote_instructions', 'attendees',
+            'comments',
         )
         labels = {'requested_duration': 'Length'}
 
diff --git a/ietf/meeting/migrations/0060_normalize_canceled_sessions.py b/ietf/meeting/migrations/0060_normalize_canceled_sessions.py
new file mode 100644
index 000000000..31c4a21df
--- /dev/null
+++ b/ietf/meeting/migrations/0060_normalize_canceled_sessions.py
@@ -0,0 +1,49 @@
+# Generated by Django 2.2.28 on 2023-02-08 22:41
+
+from django.db import migrations
+from django.db.models import Subquery, OuterRef, Value, TextField
+from django.db.models.functions import Coalesce
+
+
+def forward(apps, schema_editor):
+    Session = apps.get_model('meeting', 'Session')
+    SchedulingEvent = apps.get_model('meeting', 'SchedulingEvent')
+    Person = apps.get_model('person', 'Person')
+
+    # annotation borrowed from SessionQuerySet.with_current_status()
+    sessions = Session.objects.annotate(
+        # coalesce with '' to avoid nulls which give funny
+        # results, e.g. .exclude(current_status='canceled') also
+        # skips rows with null in them
+        current_status=Coalesce(
+            Subquery(
+                SchedulingEvent.objects.filter(
+                    session=OuterRef('pk')
+                ).order_by(
+                    '-time', '-id'
+                ).values('status')[:1]),
+            Value(''),
+            output_field=TextField()),
+    ).exclude(
+        current_status__in=['canceled', 'canceledpa'],
+    )
+    system_person = Person.objects.get(name='(System)')
+
+    # Cancel any uncanceled sessions marked as 'CANCELED' in the agenda_note
+    for session in sessions.filter(agenda_note='CANCELED'):
+        SchedulingEvent.objects.create(session=session, status_id='canceled', by=system_person)
+
+
+def reverse(apps, schema_editor):
+    pass  # nothing to be done
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('meeting', '0059_rename_sessions'),
+    ]
+
+    operations = [
+        migrations.RunPython(forward, reverse),
+    ]
diff --git a/ietf/secr/proceedings/proc_utils.py b/ietf/secr/proceedings/proc_utils.py
index 43d063d07..c9734534d 100644
--- a/ietf/secr/proceedings/proc_utils.py
+++ b/ietf/secr/proceedings/proc_utils.py
@@ -64,8 +64,6 @@ def import_audio_files(meeting):
     
     Example: ietf90-salonb-20140721-1710.mp3
     '''
-    from ietf.meeting.utils import add_event_info_to_session_qs
-
     unmatched_files = []
     path = os.path.join(settings.MEETING_RECORDINGS_DIR, meeting.type.slug + meeting.number)
     if not os.path.exists(path):
@@ -73,11 +71,9 @@ def import_audio_files(meeting):
     for filename in os.listdir(path):
         timeslot = get_timeslot_for_filename(filename)
         if timeslot:
-            sessions = add_event_info_to_session_qs(Session.objects.filter(
+            sessions = Session.objects.with_current_status().filter(
                 timeslotassignments__schedule=timeslot.meeting.schedule_id,
-            ).exclude(
-                agenda_note__icontains='canceled'
-            )).filter(
+            ).filter(
                 current_status='sched',
             ).order_by('timeslotassignments__timeslot__time')
             if not sessions:
diff --git a/ietf/templates/meeting/session_details_form.html b/ietf/templates/meeting/session_details_form.html
index e7b4fd2be..cdb842189 100644
--- a/ietf/templates/meeting/session_details_form.html
+++ b/ietf/templates/meeting/session_details_form.html
@@ -30,5 +30,5 @@
         </table>
     {% endif %}
     {# hidden fields included whether or not the whole form is hidden #}
-    {{ form.attendees.as_hidden }}{{ form.comments.as_hidden }}{{ form.id.as_hidden }}{{ form.on_agenda.as_hidden }}{{ form.DELETE.as_hidden }}{{ form.remote_instructions.as_hidden }}{{ form.short.as_hidden }}
+    {{ form.attendees.as_hidden }}{{ form.comments.as_hidden }}{{ form.id.as_hidden }}{{ form.on_agenda.as_hidden }}{{ form.DELETE.as_hidden }}{{ form.remote_instructions.as_hidden }}{{ form.short.as_hidden }}{{ form.agenda_note.as_hidden }}
 </div>
\ No newline at end of file