diff --git a/ietf/meeting/tasks.py b/ietf/meeting/tasks.py new file mode 100644 index 0000000000..43cbb0a75f --- /dev/null +++ b/ietf/meeting/tasks.py @@ -0,0 +1,12 @@ +# Copyright The IETF Trust 2024, All Rights Reserved +# +# Celery task definitions +# +from celery import shared_task + +from .views import generate_agenda_data + + +@shared_task +def agenda_data_refresh(): + generate_agenda_data(force_refresh=True) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index d227708aa0..4f83cf0cd7 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -26,6 +26,7 @@ from django.urls import reverse as urlreverse from django.conf import settings from django.contrib.auth.models import User +from django.core.serializers.json import DjangoJSONEncoder from django.test import Client, override_settings from django.db.models import F, Max from django.http import QueryDict, FileResponse @@ -49,7 +50,7 @@ from ietf.meeting.utils import add_event_info_to_session_qs, participants_for_meeting from ietf.meeting.utils import create_recording, get_next_sequence, bluesheet_data from ietf.meeting.views import session_draft_list, parse_agenda_filter_params, sessions_post_save, agenda_extract_schedule -from ietf.meeting.views import get_summary_by_area, get_summary_by_type, get_summary_by_purpose +from ietf.meeting.views import get_summary_by_area, get_summary_by_type, get_summary_by_purpose, generate_agenda_data from ietf.name.models import SessionStatusName, ImportantDateName, RoleName, ProceedingsMaterialTypeName from ietf.utils.decorators import skip_coverage from ietf.utils.mail import outbox, empty_outbox, get_payload_text @@ -245,30 +246,34 @@ def test_meeting_agenda(self): # Agenda API tests # -> Meeting data - r = self.client.get(urlreverse("ietf.meeting.views.api_get_agenda_data", kwargs=dict(num=meeting.number))) - self.assertEqual(r.status_code, 200) - rjson = json.loads(r.content.decode("utf8")) - self.assertJSONEqual( - r.content.decode("utf8"), + # First, check that the generation function does the right thing + generated_data = generate_agenda_data(meeting.number) + self.assertEqual( + generated_data, { "meeting": { "number": meeting.number, "city": meeting.city, "startDate": meeting.date.isoformat(), "endDate": meeting.end_date().isoformat(), - "updated": rjson.get("meeting").get("updated"), # Just expect the value to exist + "updated": generated_data.get("meeting").get("updated"), # Just expect the value to exist "timezone": meeting.time_zone, "infoNote": meeting.agenda_info_note, "warningNote": meeting.agenda_warning_note }, - "categories": rjson.get("categories"), # Just expect the value to exist + "categories": generated_data.get("categories"), # Just expect the value to exist "isCurrentMeeting": True, - "usesNotes": False, # make_meeting_test_data sets number=72 - "schedule": rjson.get("schedule"), # Just expect the value to exist + "usesNotes": False, # make_meeting_test_data sets number=72 + "schedule": generated_data.get("schedule"), # Just expect the value to exist "floors": [] } ) - # -> Session Materials + with patch("ietf.meeting.views.generate_agenda_data", return_value=generated_data): + r = self.client.get(urlreverse("ietf.meeting.views.api_get_agenda_data", kwargs=dict(num=meeting.number))) + self.assertEqual(r.status_code, 200) + # json.dumps using the DjangoJSONEncoder to handle timestamps consistently + self.assertJSONEqual(r.content.decode("utf8"), json.dumps(generated_data, cls=DjangoJSONEncoder)) + # -> Session MaterialM r = self.client.get(urlreverse("ietf.meeting.views.api_get_session_materials", kwargs=dict(session_id=session.id))) self.assertEqual(r.status_code, 200) rjson = json.loads(r.content.decode("utf8")) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 64fe73f484..628125776f 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -25,6 +25,7 @@ from wsgiref.handlers import format_date_time from django import forms +from django.core.cache import caches from django.shortcuts import render, redirect, get_object_or_404 from django.http import (HttpResponse, HttpResponseRedirect, HttpResponseForbidden, HttpResponseNotFound, Http404, HttpResponseBadRequest, @@ -1657,8 +1658,16 @@ def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc="" } }) -@cache_page(5 * 60) -def api_get_agenda_data (request, num=None): + +def generate_agenda_data(num=None, force_refresh=False): + """Generate data for the api_get_agenda_data endpoint + + :num: meeting number + :force_refresh: True to force a refresh of the cache + """ + cache = caches["default"] + cache_timeout = 6 * 60 + meeting = get_ietf_meeting(num) if meeting is None: raise Http404("No such full IETF meeting") @@ -1667,6 +1676,12 @@ def api_get_agenda_data (request, num=None): else: pass + cache_key = f"generate_agenda_data_{meeting.number}" + if not force_refresh: + cached_value = cache.get(cache_key) + if cached_value is not None: + return cached_value + # Select the schedule to show schedule = get_schedule(meeting, None) @@ -1685,10 +1700,8 @@ def api_get_agenda_data (request, num=None): # Get Floor Plans floors = FloorPlan.objects.filter(meeting=meeting).order_by('order') - - #debug.show('all([(item.acronym,item.session.order_number,item.session.order_in_meeting()) for item in filtered_assignments])') - - return JsonResponse({ + + result = { "meeting": { "number": schedule.meeting.number, "city": schedule.meeting.city, @@ -1704,7 +1717,13 @@ def api_get_agenda_data (request, num=None): "usesNotes": meeting.uses_notes(), "schedule": list(map(agenda_extract_schedule, filtered_assignments)), "floors": list(map(agenda_extract_floorplan, floors)) - }) + } + cache.set(cache_key, result, timeout=cache_timeout) + return result + + +def api_get_agenda_data(request, num=None): + return JsonResponse(generate_agenda_data(num, force_refresh=False)) def api_get_session_materials(request, session_id=None):