Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions ietf/group/tests_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -2167,7 +2167,12 @@ def _meetings_page(self, group):
def test_pending_approval_interim_shows_warning(self):
"""An interim awaiting approval (apprw) triggers the warning."""
group = GroupFactory.create(type_id='wg')
SessionFactory.create(meeting__type_id='interim', group=group, status_id='apprw')
SessionFactory.create(
meeting__type_id="interim",
meeting__date=date_today() + datetime.timedelta(days=30),
group=group,
status_id="apprw",
)
q = self._meetings_page(group)
warning = q('#pending_warning')
self.assertTrue(warning)
Expand All @@ -2182,14 +2187,24 @@ def test_pending_approval_interim_shows_warning(self):
def test_to_be_announced_interim_shows_warning(self):
"""An approved-but-unannounced interim (scheda) triggers the warning."""
group = GroupFactory.create(type_id='wg')
SessionFactory.create(meeting__type_id='interim', group=group, status_id='scheda')
SessionFactory.create(
meeting__type_id="interim",
meeting__date=date_today() + datetime.timedelta(days=30),
group=group,
status_id="scheda",
)
q = self._meetings_page(group)
self.assertTrue(q('#pending_warning'))

def test_scheduled_interim_shows_no_warning(self):
"""A fully scheduled interim (sched) does not trigger the warning."""
group = GroupFactory.create(type_id='wg')
SessionFactory.create(meeting__type_id='interim', group=group, status_id='sched')
SessionFactory.create(
meeting__type_id='interim',
meeting__date=date_today() + datetime.timedelta(days=30),
group=group,
status_id='sched',
)
q = self._meetings_page(group)
self.assertFalse(q('#pending_warning'))

Expand All @@ -2203,7 +2218,12 @@ def test_pending_interim_for_other_group_not_shown(self):
"""A pending interim belonging to another group must not warn on this group."""
group = GroupFactory.create(type_id='wg')
other = GroupFactory.create(type_id='wg')
SessionFactory.create(meeting__type_id='interim', group=other, status_id='apprw')
SessionFactory.create(
meeting__type_id='interim',
meeting__date=date_today() + datetime.timedelta(days=30),
group=other,
status_id='apprw',
)
q = self._meetings_page(group)
self.assertFalse(q('#pending_warning'))

Expand Down
25 changes: 18 additions & 7 deletions ietf/meeting/helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# Copyright The IETF Trust 2013-2026, All Rights Reserved
# -*- coding: utf-8 -*-


from collections import defaultdict
import datetime
import io
Expand Down Expand Up @@ -34,6 +31,14 @@
from ietf.utils.mail import send_mail
from ietf.utils.pipe import pipe
from ietf.utils.text import xslugify
from ietf.utils.timezone import date_today

# Ignore meetings older than this when querying for pending interims. It's expected that
# every interim should be out of the pending (apprw / scheda) states well before the
# scheduled date. Look back a few weeks so that a meeting that is somehow left in a
# pending state does not fall off the interface until people have had time to notice.
#
PENDING_INTERIM_MAX_LOOKBACK = datetime.timedelta(days=28)


def get_meeting(num=None, type_in=('ietf',), days=28):
Expand Down Expand Up @@ -858,19 +863,25 @@ def is_interim_meeting_approved(meeting):


def has_pending_interim(acronym):
'''
"""Check whether group identified by acronym has a pending interim

This function takes a group acronym and returns True if that group has at least
one pending interim meeting request or a to-be-announced interim request.
'''
"""
rv = False
pending = data_for_meetings_overview(Meeting.objects.filter(type='interim'), interim_status='apprw')
possible_meetings = Meeting.objects.filter(
type="interim", date__gte=date_today() - PENDING_INTERIM_MAX_LOOKBACK
)
pending = data_for_meetings_overview(possible_meetings, interim_status="apprw")
for m in pending:
if m.responsible_group.acronym == acronym:
rv = True
break

if not rv:
to_be_announced = data_for_meetings_overview(Meeting.objects.filter(type='interim'), interim_status='scheda')
to_be_announced = data_for_meetings_overview(
possible_meetings, interim_status="scheda"
)
for m in to_be_announced:
if m.responsible_group.acronym == acronym:
rv = True
Expand Down
191 changes: 129 additions & 62 deletions ietf/meeting/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
# Copyright The IETF Trust 2007-2024, All Rights Reserved
# -*- coding: utf-8 -*-

# Copyright The IETF Trust 2007-2026, All Rights Reserved

import csv
import datetime
Expand All @@ -20,6 +18,7 @@
from collections import OrderedDict, Counter, deque, defaultdict, namedtuple
from functools import partialmethod
import jsonschema
from icalendar import Calendar, Event
from pathlib import Path
from urllib.parse import parse_qs, unquote, urlencode, urlsplit, urlunsplit, urlparse
from tempfile import mkstemp
Expand All @@ -30,10 +29,18 @@
from django.core.cache import caches
from django.core.files.storage import storages
from django.shortcuts import render, redirect, get_object_or_404
from django.http import (HttpResponse, HttpResponseRedirect, HttpResponseForbidden,
HttpResponseNotFound, Http404, HttpResponseBadRequest,
JsonResponse, HttpResponseGone, HttpResponseNotAllowed,
FileResponse)
from django.http import (
HttpResponse,
HttpResponseRedirect,
HttpResponseForbidden,
HttpResponseNotFound,
Http404,
HttpResponseBadRequest,
JsonResponse,
HttpResponseGone,
HttpResponseNotAllowed,
FileResponse,
)
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
Expand All @@ -55,61 +62,138 @@

import debug # pyflakes:ignore

from ietf.api.ietf_utils import requires_api_token
from ietf.doc.fields import SearchableDocumentsField
from ietf.doc.models import Document, State, DocEvent, NewRevisionDocEvent
from ietf.doc.storage_utils import (
remove_from_storage,
retrieve_bytes,
store_file,
)
from ietf.doc.templatetags.ietf_filters import absurl
from ietf.group.models import Group
from ietf.group.utils import can_manage_session_materials, can_manage_some_groups, can_manage_group
from ietf.group.utils import (
can_manage_session_materials,
can_manage_some_groups,
can_manage_group,
)
from ietf.person.models import Person, User
from ietf.ietfauth.utils import role_required, has_role, user_is_person
from ietf.mailtrigger.utils import gather_address_lists
from ietf.meeting.models import Meeting, Session, Schedule, FloorPlan, \
SessionPresentation, TimeSlot, SlideSubmission, Attended
from ..blobdb.models import ResolvedMaterial
from ietf.meeting.models import ImportantDate, SessionStatusName, SchedulingEvent, SchedTimeSessAssignment, Room, TimeSlotTypeName
from ietf.meeting.models import Registration
from ietf.meeting.forms import ( CustomDurationField, SwapDaysForm, SwapTimeslotsForm, ImportMinutesForm,
TimeSlotCreateForm, TimeSlotEditForm, SessionCancelForm, SessionEditForm )
from ietf.meeting.helpers import get_person_by_email, get_schedule_by_name
from ietf.meeting.helpers import get_meeting, get_ietf_meeting, get_current_ietf_meeting_num
from ietf.meeting.helpers import get_schedule, schedule_permissions
from ietf.meeting.helpers import preprocess_assignments_for_agenda, read_agenda_file
from ietf.meeting.helpers import AgendaFilterOrganizer, AgendaKeywordTagger
from ietf.meeting.helpers import convert_draft_to_pdf, get_earliest_session_date
from ietf.meeting.helpers import can_view_interim_request, can_approve_interim_request
from ietf.meeting.helpers import can_edit_interim_request
from ietf.meeting.helpers import can_request_interim_meeting, get_announcement_initial
from ietf.meeting.helpers import sessions_post_save, is_interim_meeting_approved
from ietf.meeting.helpers import send_interim_meeting_cancellation_notice, send_interim_session_cancellation_notice
from ietf.meeting.helpers import send_interim_approval
from ietf.meeting.helpers import send_interim_approval_request
from ietf.meeting.helpers import send_interim_announcement_request, sessions_post_cancel
from ietf.meeting.models import (
Meeting,
Session,
Schedule,
FloorPlan,
SessionPresentation,
TimeSlot,
SlideSubmission,
Attended,
)
from ietf.blobdb.models import ResolvedMaterial
from ietf.blobdb.storage import BlobdbStorage, BlobFile
from ietf.meeting.models import (
ImportantDate,
SessionStatusName,
SchedulingEvent,
SchedTimeSessAssignment,
Room,
TimeSlotTypeName,
Registration,
)
from ietf.meeting.forms import (
CustomDurationField,
SwapDaysForm,
SwapTimeslotsForm,
ImportMinutesForm,
TimeSlotCreateForm,
TimeSlotEditForm,
SessionCancelForm,
SessionEditForm,
InterimMeetingModelForm,
InterimAnnounceForm,
InterimSessionModelForm,
InterimCancelForm,
InterimSessionInlineFormSet,
RequestMinutesForm,
UploadAgendaForm,
UploadBlueSheetForm,
UploadMinutesForm,
UploadSlidesForm,
UploadNarrativeMinutesForm,
)
from ietf.meeting.helpers import (
get_person_by_email,
get_schedule_by_name,
get_meeting,
get_ietf_meeting,
get_current_ietf_meeting_num,
get_schedule,
schedule_permissions,
preprocess_assignments_for_agenda,
read_agenda_file,
AgendaFilterOrganizer,
AgendaKeywordTagger,
convert_draft_to_pdf,
get_earliest_session_date,
can_view_interim_request,
can_approve_interim_request,
can_edit_interim_request,
can_request_interim_meeting,
get_announcement_initial,
sessions_post_save,
is_interim_meeting_approved,
send_interim_meeting_cancellation_notice,
send_interim_session_cancellation_notice,
send_interim_approval,
send_interim_approval_request,
send_interim_announcement_request,
sessions_post_cancel,
PENDING_INTERIM_MAX_LOOKBACK,
)
from ietf.meeting.utils import (
condition_slide_order,
finalize,
generate_proceedings_content,
organize_proceedings_sessions,
resolve_uploaded_material,
sort_accept_tuple, store_blobs_for_one_material_doc,
sort_accept_tuple,
store_blobs_for_one_material_doc,
)
from ietf.meeting.utils import (
add_event_info_to_session_qs,
session_time_for_sorting,
session_requested_by,
SaveMaterialsError,
current_session_status,
get_meeting_sessions,
SessionNotScheduledError,
data_for_meetings_overview,
handle_upload_file,
save_session_minutes_revision,
preprocess_constraints_for_meeting_schedule_editor,
diff_meeting_schedules,
prefetch_schedule_diff_objects,
swap_meeting_schedule_timeslot_assignments,
bulk_create_timeslots,
preprocess_meeting_important_dates,
new_doc_for_session,
write_doc_for_session,
get_activity_stats,
post_process,
create_recording,
delete_recording,
generate_bluesheet,
bluesheet_data,
save_bluesheet,
)
from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.meeting.utils import session_time_for_sorting
from ietf.meeting.utils import session_requested_by, SaveMaterialsError
from ietf.meeting.utils import current_session_status, get_meeting_sessions, SessionNotScheduledError
from ietf.meeting.utils import data_for_meetings_overview, handle_upload_file, save_session_minutes_revision
from ietf.meeting.utils import preprocess_constraints_for_meeting_schedule_editor
from ietf.meeting.utils import diff_meeting_schedules, prefetch_schedule_diff_objects
from ietf.meeting.utils import swap_meeting_schedule_timeslot_assignments, bulk_create_timeslots
from ietf.meeting.utils import preprocess_meeting_important_dates
from ietf.meeting.utils import new_doc_for_session, write_doc_for_session
from ietf.meeting.utils import get_activity_stats, post_process, create_recording, delete_recording
from ietf.meeting.utils import generate_bluesheet, bluesheet_data, save_bluesheet
from ietf.message.utils import infer_message
from ietf.name.models import SlideSubmissionStatusName, ProceedingsMaterialTypeName, SessionPurposeName, CountryName
from ietf.name.models import (
SlideSubmissionStatusName,
ProceedingsMaterialTypeName,
SessionPurposeName,
CountryName,
)
from ietf.utils import markdown
from ietf.utils.decorators import require_api_key
from ietf.utils.hedgedoc import Note, NoteError
Expand All @@ -124,15 +208,6 @@
from ietf.utils.timezone import datetime_today, date_today
from ietf.settings import YOUTUBE_DOMAINS

from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSessionModelForm,
InterimCancelForm, InterimSessionInlineFormSet, RequestMinutesForm,
UploadAgendaForm, UploadBlueSheetForm, UploadMinutesForm, UploadSlidesForm,
UploadNarrativeMinutesForm)

from icalendar import Calendar, Event
from ietf.doc.templatetags.ietf_filters import absurl
from ..api.ietf_utils import requires_api_token
from ..blobdb.storage import BlobdbStorage, BlobFile

request_summary_exclude_group_types = ['team']

Expand Down Expand Up @@ -4111,13 +4186,9 @@ def delete_schedule(request, num, owner, name):
# -------------------------------------------------
def interim_announce(request):
"""View which shows interim meeting requests awaiting announcement"""
# Ignore meetings older than this - gives people time to notice that a meeting was
# left as "scheda" and complain before the meeting falls off the visible list.
MAX_LOOKBACK = datetime.timedelta(days=28)

meetings = data_for_meetings_overview(
Meeting.objects.filter(
type="interim", date__gte=date_today() - MAX_LOOKBACK
type="interim", date__gte=date_today() - PENDING_INTERIM_MAX_LOOKBACK
).order_by("date"),
interim_status="scheda",
)
Expand Down Expand Up @@ -4188,13 +4259,9 @@ def interim_skip_announcement(request, number):

def interim_pending(request):
"""View which shows interim meeting requests pending approval"""
# Ignore meetings older than this - gives people time to notice that a meeting was
# left as "apprw" and complain before the meeting falls off the visible list.
MAX_LOOKBACK = datetime.timedelta(days=28)

meetings = data_for_meetings_overview(
Meeting.objects.filter(
type="interim", date__gte=date_today() - MAX_LOOKBACK
type="interim", date__gte=date_today() - PENDING_INTERIM_MAX_LOOKBACK
).order_by("date"),
interim_status="apprw",
)
Expand Down
Loading