From e469addcb22ce7c8f77682ef34fac8c912ee12cc Mon Sep 17 00:00:00 2001 From: Ryan Cross <rcross@amsl.com> Date: Mon, 27 Feb 2023 14:58:59 -0800 Subject: [PATCH 1/7] feat: move IETF Activity reports from external text based to HTML pages (#5180) * feat: move IETF Activity reports from external text based to HTML pages * fix: use date_today(), fix fencepost problem * fix: use is_meeting_report template variable instead of is_monthly_report --- ietf/bin/report_id_activity | 22 ---- ietf/bin/report_progress_report | 24 ---- ietf/iesg/tests.py | 8 +- ietf/iesg/urls.py | 3 +- ietf/iesg/views.py | 34 +++++- ietf/meeting/tests_views.py | 8 +- ietf/meeting/urls.py | 2 +- ietf/meeting/views.py | 11 +- ietf/secr/proceedings/proc_utils.py | 4 +- ietf/secr/proceedings/reports.py | 106 ------------------ ietf/secr/proceedings/tests_reports.py | 36 ------ .../proceedings/report_id_activity.txt | 21 ---- .../proceedings/report_progress_report.txt | 48 -------- ietf/templates/iesg/ietf_activity_report.html | 25 +++++ ...gress_report.html => activity_report.html} | 36 ++---- .../meeting/proceedings/introduction.html | 2 +- .../meeting/proceedings_activity_report.html | 18 +++ 17 files changed, 111 insertions(+), 297 deletions(-) delete mode 100755 ietf/bin/report_id_activity delete mode 100755 ietf/bin/report_progress_report delete mode 100644 ietf/secr/proceedings/reports.py delete mode 100644 ietf/secr/proceedings/tests_reports.py delete mode 100644 ietf/secr/templates/proceedings/report_id_activity.txt delete mode 100644 ietf/secr/templates/proceedings/report_progress_report.txt create mode 100644 ietf/templates/iesg/ietf_activity_report.html rename ietf/templates/meeting/{proceedings_progress_report.html => activity_report.html} (69%) create mode 100644 ietf/templates/meeting/proceedings_activity_report.html diff --git a/ietf/bin/report_id_activity b/ietf/bin/report_id_activity deleted file mode 100755 index 7fb8ad88e..000000000 --- a/ietf/bin/report_id_activity +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -*- Python -*- -# - -# This script requires that the proper virtual python environment has been -# invoked before start - -# Set PYTHONPATH and load environment variables for standalone script ----------------- -import os, sys -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) -sys.path = [ basedir ] + sys.path -os.environ["DJANGO_SETTINGS_MODULE"] = "ietf.settings" - -import django -django.setup() -# ------------------------------------------------------------------------------------- - -from ietf.secr.proceedings.reports import report_id_activity - -print(report_id_activity(sys.argv[1], sys.argv[2]), end='') - diff --git a/ietf/bin/report_progress_report b/ietf/bin/report_progress_report deleted file mode 100755 index 9d1dad618..000000000 --- a/ietf/bin/report_progress_report +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -*- Python -*- -# - -# This script requires that the proper virtual python environment has been -# invoked before start - -# Set PYTHONPATH and load environment variables for standalone script ----------------- -import os, sys -basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")) -sys.path = [ basedir ] + sys.path -os.environ["DJANGO_SETTINGS_MODULE"] = "ietf.settings" - -import django -django.setup() -# ------------------------------------------------------------------------------------- - -from ietf.secr.proceedings.reports import report_progress_report - -# handle unicode characters before attempting to print -output = report_progress_report(sys.argv[1], sys.argv[2]) -output = output.replace(chr(160),' ') # replace NO-BREAK SPACE with space -print(output, end='') diff --git a/ietf/iesg/tests.py b/ietf/iesg/tests.py index 5ecb4ed9d..a070c44e9 100644 --- a/ietf/iesg/tests.py +++ b/ietf/iesg/tests.py @@ -96,6 +96,12 @@ class IESGTests(TestCase): ads = Role.objects.filter(group__type='area', group__state='active', name_id='ad') self.assertEqual(len(q('.photo')), ads.count()) + def test_ietf_activity(self): + url = urlreverse("ietf.iesg.views.ietf_activity") + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + + class IESGAgendaTests(TestCase): def setUp(self): super().setUp() @@ -542,4 +548,4 @@ class RescheduleOnAgendaTests(TestCase): self.assertTrue(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat")) self.assertEqual(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, d) self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").returning_item) - self.assertEqual(draft.docevent_set.count(), events_before + 1) \ No newline at end of file + self.assertEqual(draft.docevent_set.count(), events_before + 1) diff --git a/ietf/iesg/urls.py b/ietf/iesg/urls.py index fa6f3dbf7..7cf06cc6c 100644 --- a/ietf/iesg/urls.py +++ b/ietf/iesg/urls.py @@ -55,6 +55,7 @@ urlpatterns = [ url(r'^past/documents/$', views.past_documents), url(r'^agenda/telechat-(?:%(date)s-)?docs.tgz' % settings.URL_REGEXPS, views.telechat_docs_tarfile), url(r'^discusses/$', views.discusses), + url(r'^ietf-activity/$', views.ietf_activity), url(r'^milestones/$', views.milestones_needing_review), url(r'^photos/$', views.photos), -] \ No newline at end of file +] diff --git a/ietf/iesg/views.py b/ietf/iesg/views.py index 5334e8a85..15675a832 100644 --- a/ietf/iesg/views.py +++ b/ietf/iesg/views.py @@ -41,6 +41,7 @@ import json import os import tarfile import time +from dateutil import relativedelta from django import forms from django.conf import settings @@ -62,6 +63,7 @@ from ietf.iesg.models import TelechatDate from ietf.iesg.utils import telechat_page_count from ietf.ietfauth.utils import has_role, role_required, user_is_person from ietf.person.models import Person +from ietf.secr.proceedings.proc_utils import get_activity_stats from ietf.doc.utils_search import fill_in_document_table_attributes, fill_in_telechat_date from ietf.utils.timezone import date_today, datetime_from_date @@ -528,4 +530,34 @@ def photos(request): role.last_initial = role.person.last_name()[0] return render(request, 'iesg/photos.html', {'group_type': 'IESG', 'role': '', 'roles': roles }) - \ No newline at end of file +def month_choices(): + choices = [(str(n).zfill(2), str(n).zfill(2)) for n in range(1, 13)] + return choices + +def year_choices(): + this_year = date_today().year + choices = [(str(n), str(n)) for n in range(this_year, 2009, -1)] + return choices + +class ActivityForm(forms.Form): + month = forms.ChoiceField(choices=month_choices, help_text='Month', required=True) + year = forms.ChoiceField(choices=year_choices, help_text='Year', required=True) + +def ietf_activity(request): + # default date range for last month + today = date_today() + edate = today.replace(day=1) + sdate = (edate - datetime.timedelta(days=1)).replace(day=1) + if request.method == 'GET': + form = ActivityForm(request.GET) + if form.is_valid(): + month = form.cleaned_data['month'] + year = form.cleaned_data['year'] + sdate = datetime.date(int(year), int(month), 1) + edate = sdate + relativedelta.relativedelta(months=1) + + # always pass back an unbound form to avoid annoying is-valid styling + form = ActivityForm(initial={'month': str(sdate.month).zfill(2), 'year': sdate.year}) + context = get_activity_stats(sdate, edate) + context['form'] = form + return render(request, "iesg/ietf_activity_report.html", context) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index 613694cbb..cbafd3f1d 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -7530,7 +7530,7 @@ class ProceedingsTests(BaseMeetingTestCase): ) self.assertNotEqual( pq('a[href="{}"]'.format( - urlreverse('ietf.meeting.views.proceedings_progress_report', kwargs=dict(num=meeting.number))) + urlreverse('ietf.meeting.views.proceedings_activity_report', kwargs=dict(num=meeting.number))) ), [], 'Should have a link to activity report', @@ -7696,14 +7696,14 @@ class ProceedingsTests(BaseMeetingTestCase): response = self.client.get(url) self.assertContains(response, 'The Internet Engineering Task Force') - def test_proceedings_progress_report(self): + def test_proceedings_activity_report(self): make_meeting_test_data() MeetingFactory(type_id='ietf', date=datetime.date(2016,4,3), number="96") MeetingFactory(type_id='ietf', date=datetime.date(2016,7,14), number="97") - url = urlreverse('ietf.meeting.views.proceedings_progress_report',kwargs={'num':97}) + url = urlreverse('ietf.meeting.views.proceedings_activity_report',kwargs={'num':97}) response = self.client.get(url) - self.assertContains(response, 'Progress Report') + self.assertContains(response, 'Activity Report') def test_feed(self): meeting = make_meeting_test_data() diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index 3beb23b0b..db9b481d7 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -92,7 +92,7 @@ type_ietf_only_patterns_id_optional = [ url(r'^proceedings/acknowledgements/$', views.proceedings_acknowledgements), url(r'^proceedings/attendees/$', views.proceedings_attendees), url(r'^proceedings/overview/$', views.proceedings_overview), - url(r'^proceedings/progress-report/$', views.proceedings_progress_report), + url(r'^proceedings/activity-report/$', views.proceedings_activity_report), url(r'^proceedings/materials/$', views_proceedings.material_details), url(r'^proceedings/materials/(?P<material_type>[a-z_]+)/$', views_proceedings.edit_material), url(r'^proceedings/materials/(?P<material_type>[a-z_]+)/new/$', views_proceedings.upload_material), diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 44193ff9a..24f1cb87d 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -84,7 +84,7 @@ from ietf.meeting.utils import preprocess_meeting_important_dates from ietf.meeting.utils import new_doc_for_session, write_doc_for_session from ietf.message.utils import infer_message from ietf.name.models import SlideSubmissionStatusName, ProceedingsMaterialTypeName, SessionPurposeName -from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files, +from ietf.secr.proceedings.proc_utils import (get_activity_stats, post_process, import_audio_files, create_recording) from ietf.utils import markdown from ietf.utils.decorators import require_api_key @@ -3803,8 +3803,8 @@ def proceedings_overview(request, num=None): 'template': template, }) -def proceedings_progress_report(request, num=None): - '''Display Progress Report (stats since last meeting)''' +def proceedings_activity_report(request, num=None): + '''Display Activity Report (stats since last meeting)''' if not (num and num.isdigit()): raise Http404 meeting = get_meeting(num) @@ -3812,9 +3812,10 @@ def proceedings_progress_report(request, num=None): return HttpResponseRedirect(f'{settings.PROCEEDINGS_V1_BASE_URL.format(meeting=meeting)}/progress-report.html') sdate = meeting.previous_meeting().date edate = meeting.date - context = get_progress_stats(sdate,edate) + context = get_activity_stats(sdate,edate) context['meeting'] = meeting - return render(request, "meeting/proceedings_progress_report.html", context) + context['is_meeting_report'] = True + return render(request, "meeting/proceedings_activity_report.html", context) class OldUploadRedirect(RedirectView): def get_redirect_url(self, **kwargs): diff --git a/ietf/secr/proceedings/proc_utils.py b/ietf/secr/proceedings/proc_utils.py index c9734534d..bcba9bfa3 100644 --- a/ietf/secr/proceedings/proc_utils.py +++ b/ietf/secr/proceedings/proc_utils.py @@ -199,10 +199,10 @@ def send_audio_import_warning(unmatched_files): # End Recording Functions # ------------------------------------------------- -def get_progress_stats(sdate, edate): +def get_activity_stats(sdate, edate): ''' This function takes a date range and produces a dictionary of statistics / objects for - use in a progress report. Generally the end date will be the date of the last meeting + use in an activity report. Generally the end date will be the date of the last meeting and the start date will be the date of the meeting before that. Data between midnight UTC on the specified dates are included in the stats. diff --git a/ietf/secr/proceedings/reports.py b/ietf/secr/proceedings/reports.py deleted file mode 100644 index fd360774b..000000000 --- a/ietf/secr/proceedings/reports.py +++ /dev/null @@ -1,106 +0,0 @@ -import datetime - -from django.template.loader import render_to_string -from django.utils import timezone - -from ietf.meeting.models import Meeting -from ietf.doc.models import DocEvent, Document -from ietf.secr.proceedings.proc_utils import get_progress_stats -from ietf.utils.timezone import datetime_from_date - - -def report_id_activity(start,end): - - # get previous meeting - meeting = Meeting.objects.filter(date__lt=timezone.now(),type='ietf').order_by('-date')[0] - syear,smonth,sday = start.split('-') - eyear,emonth,eday = end.split('-') - sdate = datetime_from_date(datetime.date(int(syear),int(smonth),int(sday)), meeting.tz()) - edate = datetime_from_date(datetime.date(int(eyear),int(emonth),int(eday)), meeting.tz()) - - #queryset = Document.objects.filter(type='draft').annotate(start_date=Min('docevent__time')) - new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision', - docevent__newrevisiondocevent__rev='00', - docevent__time__gte=sdate, - docevent__time__lte=edate) - new = new_docs.count() - updated = 0 - updated_more = 0 - for d in new_docs: - updates = d.docevent_set.filter(type='new_revision',time__gte=sdate,time__lte=edate).count() - if updates > 1: - updated += 1 - if updates > 2: - updated_more +=1 - - # calculate total documents updated, not counting new, rev=00 - result = set() - events = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lte=edate) - for e in events.filter(type='new_revision').exclude(newrevisiondocevent__rev='00'): - result.add(e.doc) - total_updated = len(result) - - # calculate sent last call - last_call = events.filter(type='sent_last_call').count() - - # calculate approved - approved = events.filter(type='iesg_approved').count() - - # get 4 weeks - monday = datetime_from_date(Meeting.get_current_meeting().get_ietf_monday(), meeting.tz()) - cutoff = monday + datetime.timedelta(days=3) - ff1_date = cutoff - datetime.timedelta(days=28) - #ff2_date = cutoff - datetime.timedelta(days=21) - #ff3_date = cutoff - datetime.timedelta(days=14) - #ff4_date = cutoff - datetime.timedelta(days=7) - - ff_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision', - docevent__newrevisiondocevent__rev='00', - docevent__time__gte=ff1_date, - docevent__time__lte=cutoff) - ff_new_count = ff_docs.count() - ff_new_percent = format(ff_new_count / float(new),'.0%') - - # calculate total documents updated in final four weeks, not counting new, rev=00 - result = set() - events = DocEvent.objects.filter(doc__type='draft',time__gte=ff1_date,time__lte=cutoff) - for e in events.filter(type='new_revision').exclude(newrevisiondocevent__rev='00'): - result.add(e.doc) - ff_update_count = len(result) - ff_update_percent = format(ff_update_count / float(total_updated),'.0%') - - #aug_docs = augment_with_start_time(new_docs) - ''' - ff1_new = aug_docs.filter(start_date__gte=ff1_date,start_date__lt=ff2_date) - ff2_new = aug_docs.filter(start_date__gte=ff2_date,start_date__lt=ff3_date) - ff3_new = aug_docs.filter(start_date__gte=ff3_date,start_date__lt=ff4_date) - ff4_new = aug_docs.filter(start_date__gte=ff4_date,start_date__lt=edate) - ff_new_iD = ff1_new + ff2_new + ff3_new + ff4_new - ''' - context = {'meeting':meeting, - 'new':new, - 'updated':updated, - 'updated_more':updated_more, - 'total_updated':total_updated, - 'last_call':last_call, - 'approved':approved, - 'ff_new_count':ff_new_count, - 'ff_new_percent':ff_new_percent, - 'ff_update_count':ff_update_count, - 'ff_update_percent':ff_update_percent} - - report = render_to_string('proceedings/report_id_activity.txt', context) - - return report - -def report_progress_report(start_date,end_date): - syear,smonth,sday = start_date.split('-') - eyear,emonth,eday = end_date.split('-') - sdate = datetime.datetime(int(syear),int(smonth),int(sday)) - edate = datetime.datetime(int(eyear),int(emonth),int(eday)) - - context = get_progress_stats(sdate,edate) - - report = render_to_string('proceedings/report_progress_report.txt', context) - - return report diff --git a/ietf/secr/proceedings/tests_reports.py b/ietf/secr/proceedings/tests_reports.py deleted file mode 100644 index 034dbe5fa..000000000 --- a/ietf/secr/proceedings/tests_reports.py +++ /dev/null @@ -1,36 +0,0 @@ -import datetime -import debug # pyflakes:ignore - -from django.utils import timezone - -from ietf.doc.factories import DocumentFactory,NewRevisionDocEventFactory -from ietf.secr.proceedings.reports import report_id_activity, report_progress_report -from ietf.utils.test_utils import TestCase -from ietf.meeting.factories import MeetingFactory - -class ReportsTestCase(TestCase): - - def test_report_id_activity(self): - - today = timezone.now() - yesterday = today - datetime.timedelta(days=1) - last_quarter = today - datetime.timedelta(days=3*30) - next_week = today+datetime.timedelta(days=7) - - m1 = MeetingFactory(type_id='ietf',date=last_quarter) - m2 = MeetingFactory(type_id='ietf',date=next_week,number=int(m1.number)+1) - - doc = DocumentFactory(type_id='draft',time=yesterday,rev="00") - NewRevisionDocEventFactory(doc=doc,time=today,rev="01") - result = report_id_activity(m1.date.strftime("%Y-%m-%d"),m2.date.strftime("%Y-%m-%d")) - self.assertTrue('IETF Activity since last IETF Meeting' in result) - - def test_report_progress_report(self): - today = timezone.now() - last_quarter = today - datetime.timedelta(days=3*30) - next_week = today+datetime.timedelta(days=7) - - m1 = MeetingFactory(type_id='ietf',date=last_quarter) - m2 = MeetingFactory(type_id='ietf',date=next_week,number=int(m1.number)+1) - result = report_progress_report(m1.date.strftime('%Y-%m-%d'),m2.date.strftime('%Y-%m-%d')) - self.assertTrue('IETF Activity since last IETF Meeting' in result) diff --git a/ietf/secr/templates/proceedings/report_id_activity.txt b/ietf/secr/templates/proceedings/report_id_activity.txt deleted file mode 100644 index 6f2aaf097..000000000 --- a/ietf/secr/templates/proceedings/report_id_activity.txt +++ /dev/null @@ -1,21 +0,0 @@ -IETF Activity since last IETF Meeting --------------------- - -IETF Activity since last IETF Meeting ({{ meeting.city }}) - -{{ new|stringformat:"3s" }} New I-Ds ({{ updated }} of which were updated, some ({{ updated_more }}) more than once) -{{ total_updated|stringformat:"3s" }} I-Ds were updated (Some more than once) -{{ last_call|stringformat:"3s" }} I-Ds Last Called -{{ approved|stringformat:"3s" }} I-Ds approved for publication - -In the final 4 weeks before meeting - -{{ ff_new_count|stringformat:"3s" }} New I-Ds were received - {{ ff_new_percent }} of total newbies since last meeting -{{ ff_update_count|stringformat:"3s" }} I-Ds were updated - {{ ff_update_percent }} of total updated since last meeting - - Week1 0 % - Week2 0 % - Week3 0 % - Week4 0 % - -The IESG Secretary. diff --git a/ietf/secr/templates/proceedings/report_progress_report.txt b/ietf/secr/templates/proceedings/report_progress_report.txt deleted file mode 100644 index 8c3d87548..000000000 --- a/ietf/secr/templates/proceedings/report_progress_report.txt +++ /dev/null @@ -1,48 +0,0 @@ -{% load ams_filters %} - IETF Activity since last IETF Meeting - {{ start_date }} to {{ end_date }} - - 1) {{ action_events.count }} IESG Protocol and Document Actions this period -{% for event in action_events %} - {{ event.doc.title }} ({{ event.doc.intended_std_level }}) -{% endfor %} - - 2) {{ lc_events.count }} IESG Last Calls issued to the IETF this period -{% for event in lc_events %} - {{ event.doc.title }} - {{ event.doc.file_tag|safe }} ({{ event.doc.intended_std_level }}) -{% endfor %} - - 3) {{ new_groups.count }} New Working Group(s) formed this period - {% for group in new_groups %} - {{ group }} ({{ group.acronym }}) - {% endfor %} - - 4) {{ concluded_groups.count }} Working Group(s) concluded this period - {% for group in concluded_groups %} - {{ group }} ({{ group.acronym }}) - {% endfor %} - - 5) {{ new_docs|length }} new or revised Internet-Drafts this period - - (o - Revised Internet-Draft; + - New Internet-Draft) - - WG I-D Title <Filename> - ------- ------------------------------------------ - {% for doc in new_docs %} - ({{ doc.group.acronym|stringformat:"8s" }}) {% if doc.rev == "00" %} + {% else %} o {% endif %}{{ doc.title }} - {{ doc.file_tag|safe }} - {% endfor %} - - 6) {{ rfcs.count }} RFC(s) produced this period - - S - Standard; PS - Proposed Standard; DS - Draft Standard; - B - Best Current Practices; E - Experimental; I - Informational - - RFC Stat WG Published Title -------- -- ---------- ---------- ----------------------------------------- -{% for event in rfcs %} -{{ event.doc.canonical_name|upper }} {{ event.doc.intended_std_level.name|abbr_status|stringformat:"2s" }} ({{ event.doc.group.acronym|stringformat:"8s" }}) {{ event.time|date:"M d" }} {{ event.doc.title }} -{% endfor %} - - {{ counts.std }} Standards Track; {{ counts.bcp }} BCP; {{ counts.exp }} Experimental; {{ counts.inf }} Informational diff --git a/ietf/templates/iesg/ietf_activity_report.html b/ietf/templates/iesg/ietf_activity_report.html new file mode 100644 index 000000000..32bf9dc45 --- /dev/null +++ b/ietf/templates/iesg/ietf_activity_report.html @@ -0,0 +1,25 @@ +{% extends "base.html" %} +{% load ams_filters ietf_filters django_bootstrap5 %} +{% block title %}IETF Activity Report{% endblock %} +{% block content %} + <h1 class="mt-3">IETF Activity Report</h1> + <h2 class="mt-3"><small class="text-muted">{{ sdate|date:"F Y" }}</small></h2> + + <form action="." method="GET"> + <div class="row"> + <div class="col-2"> + {% bootstrap_field form.month show_label=True layout="inline" %} + </div> + <div class="col-2"> + {% bootstrap_field form.year layout="inline" %} + </div> + <div class="col-2"> + <button type="submit" class="btn btn-primary">Submit</button> + </div> + </div> + </form> + + <h3 class="mt-3">Draft Activity</h3> + {% include "meeting/activity_report.html" %} + +{% endblock %} \ No newline at end of file diff --git a/ietf/templates/meeting/proceedings_progress_report.html b/ietf/templates/meeting/activity_report.html similarity index 69% rename from ietf/templates/meeting/proceedings_progress_report.html rename to ietf/templates/meeting/activity_report.html index 27757f5fa..1552bb468 100644 --- a/ietf/templates/meeting/proceedings_progress_report.html +++ b/ietf/templates/meeting/activity_report.html @@ -1,16 +1,4 @@ -{% extends "base.html" %} -{% load ams_filters ietf_filters cache %} -{% block title %}IETF {{ meeting.number }} Proceedings - Progress Report{% endblock %} -{% block content %} - {% cache 3600 proceedings_progress_report meeting.number %} - <h1> - <a class="text-decoration-none text-reset" - href="{% url 'ietf.meeting.views.proceedings' num=meeting.number %}"> - IETF {{ meeting.number }} proceedings - </a> - </h1> - <h2 class="mt-3">IETF Progress Report</h2> - <h3 class="mt-3">{{ sdate|date:"d-F-y" }} to {{ edate|date:"d-F-y" }}</h3> + {% load ams_filters ietf_filters %} <ul class="progress-section"> <li>{{ actions_count }} IESG Protocol and Document Actions this period</li> <li>{{ last_calls_count }} IESG Last Calls issued to the IETF this period</li> @@ -18,15 +6,17 @@ {{ new_drafts_count|stringformat:"3s" }} New I-Ds ({{ new_drafts_updated_count }} of which were updated, some ({{ new_drafts_updated_more_count }}) more than once) </li> <li>{{ updated_drafts_count|stringformat:"3s" }} I-Ds were updated (Some more than once)</li> - <li> - <h3 class="mt-3">In the final 4 weeks before meeting</h3> - </li> - <li> - {{ ffw_new_count|stringformat:"3s" }} New I-Ds were received - {{ ffw_new_percent }} of total newbies since last meeting - </li> - <li> - {{ ffw_update_count|stringformat:"3s" }} I-Ds were updated - {{ ffw_update_percent }} of total updated since last meeting - </li> + {% if is_meeting_report %} + <li> + <h3 class="mt-3">In the final 4 weeks before meeting</h3> + </li> + <li> + {{ ffw_new_count|stringformat:"3s" }} New I-Ds were received - {{ ffw_new_percent }} of total newbies since last meeting + </li> + <li> + {{ ffw_update_count|stringformat:"3s" }} I-Ds were updated - {{ ffw_update_percent }} of total updated since last meeting + </li> + {% endif %} </ul> <h3 class="mt-3">{{ new_groups.count }} New Working Group(s) formed this period</h3> <ul class="progress-section"> @@ -68,5 +58,3 @@ </tbody> {% endif %} </table> - {% endcache %} -{% endblock %} \ No newline at end of file diff --git a/ietf/templates/meeting/proceedings/introduction.html b/ietf/templates/meeting/proceedings/introduction.html index 2e409ccf0..9f6f9af00 100644 --- a/ietf/templates/meeting/proceedings/introduction.html +++ b/ietf/templates/meeting/proceedings/introduction.html @@ -14,7 +14,7 @@ This renders the list of links below the title on the meeting proceedings page. <a href="{% url 'agenda' num=meeting.number %}">Meeting Agenda</a> </div> <div class="proceedings-row"> - <a href="{% url 'ietf.meeting.views.proceedings_progress_report' num=meeting.number %}">Activity Report</a> + <a href="{% url 'ietf.meeting.views.proceedings_activity_report' num=meeting.number %}">Activity Report</a> </div> <div class="proceedings-row"> <a href="{% url 'ietf.meeting.views.important_dates' num=meeting.number %}">Important Dates</a> diff --git a/ietf/templates/meeting/proceedings_activity_report.html b/ietf/templates/meeting/proceedings_activity_report.html new file mode 100644 index 000000000..dd5b082bf --- /dev/null +++ b/ietf/templates/meeting/proceedings_activity_report.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load ams_filters ietf_filters cache %} +{% block title %}IETF {{ meeting.number }} Proceedings - Activity Report{% endblock %} +{% block content %} + {% cache 3600 proceedings_activity_report meeting.number %} + <h1> + <a class="text-decoration-none text-reset" + href="{% url 'ietf.meeting.views.proceedings' num=meeting.number %}"> + IETF {{ meeting.number }} proceedings + </a> + </h1> + <h2 class="mt-3">IETF Activity Report</h2> + <h3 class="mt-3">{{ sdate|date:"d-F-y" }} to {{ edate|date:"d-F-y" }}</h3> + + {% include "meeting/activity_report.html" %} + + {% endcache %} +{% endblock %} \ No newline at end of file From 8e16b4405b28747aadba108fbbaa872bd95f7588 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@painless-security.com> Date: Tue, 28 Feb 2023 12:16:39 -0400 Subject: [PATCH 2/7] fix: Support time zones in agenda.txt; restore agenda-utc.txt (#5233) * fix: Format times in display timezone in agenda.txt template * chore: Remove unused and non-timezone-friendly TimeSlot.time_desc() * feat: Dispatch agenda-utc.txt URL * refactor: Use None to indicate lack of utc parameter to view * feat: Show display timezone in agenda.txt template * refactor: Combine URL regexes for the agenda.txt to a single entry * test: Update tests for agenda.txt/agenda-csv.txt * fix: Remove ':' added to time formats in agenda.txt template --- ietf/meeting/models.py | 4 ---- ietf/meeting/tests_views.py | 32 ++++++++++++++++++------------- ietf/meeting/urls.py | 2 +- ietf/meeting/views.py | 4 ++-- ietf/templates/meeting/agenda.txt | 11 ++++++----- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/ietf/meeting/models.py b/ietf/meeting/models.py index 3da764041..b6c6b52b4 100644 --- a/ietf/meeting/models.py +++ b/ietf/meeting/models.py @@ -602,10 +602,6 @@ class TimeSlot(models.Model): self._session_cache = self.sessions.filter(timeslotassignments__schedule__in=[self.meeting.schedule, self.meeting.schedule.base if self.meeting else None]).first() return self._session_cache - @property - def time_desc(self): - return "%s-%s" % (self.time.strftime("%H%M"), (self.time + self.duration).strftime("%H%M")) - def meeting_date(self): return self.time.date() diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index cbafd3f1d..f1b53a6b8 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -228,9 +228,6 @@ class MeetingTests(BaseMeetingTestCase): registration_text = "Registration" - # utc - time_interval = r"%s<span.*/span>-%s" % (slot.utc_start_time().strftime("%H:%M").lstrip("0"), (slot.utc_start_time() + slot.duration).strftime("%H:%M").lstrip("0")) - # Extremely rudementary test of agenda-neue - to be replaced with back-end tests as the front-end tests are developed. r = self.client.get(urlreverse("agenda", kwargs=dict(num=meeting.number,utc='-utc'))) self.assertEqual(r.status_code, 200) @@ -279,23 +276,32 @@ class MeetingTests(BaseMeetingTestCase): } ) - # plain - time_interval = r"{}<span.*/span>-{}".format( - slot.time.astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"), - slot.end_time().astimezone(meeting.tz()).strftime("%H:%M").lstrip("0"), - ) - # text - # the rest of the results don't have as nicely formatted times - time_interval = "%s-%s" % (slot.time.strftime("%H%M").lstrip("0"), (slot.time + slot.duration).strftime("%H%M").lstrip("0")) - r = self.client.get(urlreverse("ietf.meeting.views.agenda_plain", kwargs=dict(num=meeting.number, ext=".txt"))) self.assertContains(r, session.group.acronym) self.assertContains(r, session.group.name) self.assertContains(r, session.group.parent.acronym.upper()) self.assertContains(r, slot.location.name) + self.assertContains(r, "{}-{}".format( + slot.time.astimezone(meeting.tz()).strftime("%H%M"), + (slot.time + slot.duration).astimezone(meeting.tz()).strftime("%H%M"), + )) + self.assertContains(r, f"shown in the {meeting.tz()} time zone") - self.assertContains(r, time_interval) + # text, UTC + r = self.client.get(urlreverse( + "ietf.meeting.views.agenda_plain", + kwargs=dict(num=meeting.number, ext=".txt", utc="-utc"), + )) + self.assertContains(r, session.group.acronym) + self.assertContains(r, session.group.name) + self.assertContains(r, session.group.parent.acronym.upper()) + self.assertContains(r, slot.location.name) + self.assertContains(r, "{}-{}".format( + slot.time.astimezone(datetime.timezone.utc).strftime("%H%M"), + (slot.time + slot.duration).astimezone(datetime.timezone.utc).strftime("%H%M"), + )) + self.assertContains(r, "shown in UTC") # future meeting, no agenda r = self.client.get(urlreverse("ietf.meeting.views.agenda_plain", kwargs=dict(num=future_meeting.number, ext=".txt"))) diff --git a/ietf/meeting/urls.py b/ietf/meeting/urls.py index db9b481d7..1406acfad 100644 --- a/ietf/meeting/urls.py +++ b/ietf/meeting/urls.py @@ -69,7 +69,7 @@ type_interim_patterns = [ type_ietf_only_patterns_id_optional = [ url(r'^agenda(?P<utc>-utc)?(?P<ext>\.html)?/?$', views.agenda, name='agenda'), - url(r'^agenda(?P<ext>\.txt)$', views.agenda_plain), + url(r'^agenda(?P<utc>-utc)?(?P<ext>\.txt)$', views.agenda_plain), url(r'^agenda(?P<ext>\.csv)$', views.agenda_plain), url(r'^agenda/edit$', RedirectView.as_view(pattern_name='ietf.meeting.views.edit_meeting_schedule', permanent=True), diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 24f1cb87d..ef58ec25a 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1521,7 +1521,7 @@ def get_assignments_for_agenda(schedule): @ensure_csrf_cookie -def agenda_plain(request, num=None, name=None, base=None, ext=None, owner=None, utc=""): +def agenda_plain(request, num=None, name=None, base=None, ext=None, owner=None, utc=None): base = base if base else 'agenda' ext = ext if ext else '.txt' mimetype = { @@ -1571,7 +1571,7 @@ def agenda_plain(request, num=None, name=None, base=None, ext=None, owner=None, is_current_meeting = (num is None) or (num == get_current_ietf_meeting_num()) - display_timezone = 'UTC' if utc else meeting.time_zone + display_timezone = meeting.time_zone if utc is None else 'UTC' with timezone.override(display_timezone): rendered_page = render( request, diff --git a/ietf/templates/meeting/agenda.txt b/ietf/templates/meeting/agenda.txt index 34fd3ae73..7f612cfd9 100644 --- a/ietf/templates/meeting/agenda.txt +++ b/ietf/templates/meeting/agenda.txt @@ -10,22 +10,23 @@ {% filter center:72 %}Updated {{ updated|date:"Y-m-d H:i:s T" }}{% endfilter %} {% filter center:72 %}IETF agendas are subject to change, up to and during the meeting.{% endfilter %} +{% filter center:72 %}Times are shown in {% if display_timezone.lower == "utc" %}UTC{% else %}the {{ display_timezone }} time zone{% endif %}.{% endfilter %} {% for item in filtered_assignments %}{% ifchanged %} {{ item.timeslot.time|date:"l"|upper }}, {{ item.timeslot.time|date:"F j, Y" }} {% endifchanged %}{% if item.slot_type.slug == "reg" %} -{{ item.timeslot.time_desc }} {{ item.timeslot.name }}{% if schedule.meeting.reg_area %} - {{ schedule.meeting.reg_area }}{% endif %}{% endif %}{% if item.slot_type.slug == "plenary" %} -{{ item.timeslot.time_desc }} {{ item.session.name }} - {{ item.timeslot.location.name }} +{{ item.timeslot.time|date:"Hi" }}-{{ item.timeslot.end_time|date:"Hi" }} {{ item.timeslot.name }}{% if schedule.meeting.reg_area %} - {{ schedule.meeting.reg_area }}{% endif %}{% endif %}{% if item.slot_type.slug == "plenary" %} +{{ item.timeslot.time|date:"Hi" }}-{{ item.timeslot.end_time|date:"Hi" }} {{ item.session.name }} - {{ item.timeslot.location.name }} {{ item.session.agenda_text.strip|indent:"3" }} {% endif %}{% if item.slot_type.slug == 'regular' %}{% ifchanged %} -{{ item.timeslot.time_desc }} {{ item.timeslot.name }} +{{ item.timeslot.time|date:"Hi" }}-{{ item.timeslot.end_time|date:"Hi" }} {{ item.timeslot.name }} {% endifchanged %}{{ item.timeslot.location.name|ljust:14 }} {{ item.session.group_parent_at_the_time.acronym|upper|ljust:4 }} {{ item.session.group_at_the_time.acronym|ljust:10 }} {{ item.session.group_at_the_time.name }} {% if item.session.group_at_the_time.state_id == "bof" %}BOF{% elif item.session.group_at_the_time.type_id == "wg" %}WG{% endif %}{% if item.session.agenda_note %} - {{ item.session.agenda_note }}{% endif %}{% if item.session.current_status == 'canceled' %} *** CANCELLED ***{% elif item.session.current_status == 'resched' %} *** RESCHEDULED{% if item.session.rescheduled_to %} TO {{ item.session.rescheduled_to.time|date:"l G:i"|upper }}-{{ item.session.rescheduled_to.end_time|date:"G:i" }}{% endif %} ***{% endif %} {% endif %}{% if item.slot_type.slug == "break" %} -{{ item.timeslot.time_desc }} {{ item.timeslot.name }}{% if schedule.meeting.break_area and item.timeslot.show_location %} - {{ schedule.meeting.break_area }}{% endif %}{% endif %}{% if item.slot_type.slug == "other" %} -{{ item.timeslot.time_desc }} {{ item.timeslot.name }} - {{ item.timeslot.location.name }}{% endif %}{% endfor %} +{{ item.timeslot.time|date:"Hi" }}-{{ item.timeslot.end_time|date:"Hi" }} {{ item.timeslot.name }}{% if schedule.meeting.break_area and item.timeslot.show_location %} - {{ schedule.meeting.break_area }}{% endif %}{% endif %}{% if item.slot_type.slug == "other" %} +{{ item.timeslot.time|date:"Hi" }}-{{ item.timeslot.end_time|date:"Hi" }} {{ item.timeslot.name }} - {{ item.timeslot.location.name }}{% endif %}{% endfor %} ==================================================================== {% endautoescape %} From cf94b896c8c91a806762f6d869c6f59289d96548 Mon Sep 17 00:00:00 2001 From: Lars Eggert <lars@eggert.org> Date: Tue, 28 Feb 2023 18:21:39 +0200 Subject: [PATCH 3/7] feat: Add reminder that AD changes for approved documents are unusual (#5231) --- ietf/templates/doc/draft/change_ad.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ietf/templates/doc/draft/change_ad.html b/ietf/templates/doc/draft/change_ad.html index 3730546ee..efa1b27bf 100644 --- a/ietf/templates/doc/draft/change_ad.html +++ b/ietf/templates/doc/draft/change_ad.html @@ -2,6 +2,7 @@ {# Copyright The IETF Trust 2015, All Rights Reserved #} {% load origin %} {% load django_bootstrap5 %} +{% load ietf_filters %} {% block title %}Change responsible AD for {{ doc.name }}-{{ doc.rev }}{% endblock %} {% block content %} {% origin %} @@ -10,11 +11,20 @@ <br> <small class="text-muted">{{ doc.name }}-{{ doc.rev }}</small> </h1> + {% with approved=doc|state:"draft-rfceditor" %} + {% if approved %} + <div class="alert alert-warning my-3"> + It is unusual to change the responsible AD for a document that has + been sent to the RFC Editor. Please make sure this is really what + you want to do. + </div> + {% endif %} <form class="mt-3" enctype="multipart/form-data" method="post"> {% csrf_token %} {% bootstrap_form form %} - <button type="submit" class="btn btn-primary">Submit</button> + <button type="submit" class="btn {{ approved|yesno:'btn-warning,btn-primary' }}">Submit</button> <a class="btn btn-secondary float-end" href="{% url "ietf.doc.views_doc.document_main" name=doc.name %}">Back</a> </form> + {% endwith %} {% endblock %} \ No newline at end of file From 9a1de57c9e473c8a55b9f0b2f58827d9180b58c0 Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@painless-security.com> Date: Tue, 28 Feb 2023 13:47:20 -0400 Subject: [PATCH 4/7] fix: Only consider ietf meetings as "next" for agenda_ical or agenda_json (#5238) * style: Clean up get_meeting() signature and code style * chore: Remove unused parameter from agenda_ical() view's signature * fix: Only consider ietf meetings as "next" in agenda_ical * fix: Only consider ietf meetings as "next" in agenda_json * test: Test agenda_json "next meeting" and clean up agenda_ical test * style: Reformat new tests using Black style --- ietf/meeting/helpers.py | 10 ++++++---- ietf/meeting/tests_views.py | 33 +++++++++++++++++++++++++++++++++ ietf/meeting/views.py | 23 ++++++++++++++++++----- 3 files changed, 57 insertions(+), 9 deletions(-) diff --git a/ietf/meeting/helpers.py b/ietf/meeting/helpers.py index 7286f7453..a266efafa 100644 --- a/ietf/meeting/helpers.py +++ b/ietf/meeting/helpers.py @@ -36,12 +36,14 @@ from ietf.utils.pipe import pipe from ietf.utils.text import xslugify -def get_meeting(num=None,type_in=['ietf',],days=28): +def get_meeting(num=None, type_in=('ietf',), days=28): meetings = Meeting.objects - if type_in: + if type_in is not None: meetings = meetings.filter(type__in=type_in) - if num == None: - meetings = meetings.filter(date__gte=timezone.now()-datetime.timedelta(days=days)).order_by('date') + if num is None: + meetings = meetings.filter( + date__gte=timezone.now() - datetime.timedelta(days=days) + ).order_by('date') else: meetings = meetings.filter(number=num) if meetings.exists(): diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index f1b53a6b8..ccab55e0a 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -345,6 +345,39 @@ class MeetingTests(BaseMeetingTestCase): r = self.client.get(urlreverse('floor-plan', kwargs=dict(num=meeting.number))) self.assertEqual(r.status_code, 200) + def test_agenda_ical_next_meeting_type(self): + # start with no upcoming IETF meetings, just an interim + MeetingFactory( + type_id="interim", date=date_today() + datetime.timedelta(days=15) + ) + r = self.client.get(urlreverse("ietf.meeting.views.agenda_ical", kwargs={})) + self.assertEqual( + r.status_code, 404, "Should not return an interim meeting as next meeting" + ) + # create an IETF meeting after the interim - it should be found as "next" + ietf_meeting = MeetingFactory( + type_id="ietf", date=date_today() + datetime.timedelta(days=30) + ) + SessionFactory(meeting=ietf_meeting, name="Session at IETF meeting") + r = self.client.get(urlreverse("ietf.meeting.views.agenda_ical", kwargs={})) + self.assertContains(r, "Session at IETF meeting", status_code=200) + + def test_agenda_json_next_meeting_type(self): + # start with no upcoming IETF meetings, just an interim + MeetingFactory( + type_id="interim", date=date_today() + datetime.timedelta(days=15) + ) + r = self.client.get(urlreverse("ietf.meeting.views.agenda_json", kwargs={})) + self.assertEqual( + r.status_code, 404, "Should not return an interim meeting as next meeting" + ) + # create an IETF meeting after the interim - it should be found as "next" + ietf_meeting = MeetingFactory( + type_id="ietf", date=date_today() + datetime.timedelta(days=30) + ) + SessionFactory(meeting=ietf_meeting, name="Session at IETF meeting") + r = self.client.get(urlreverse("ietf.meeting.views.agenda_json", kwargs={})) + self.assertContains(r, "Session at IETF meeting", status_code=200) @override_settings(PROCEEDINGS_V1_BASE_URL='https://example.com/{meeting.number}') def test_agenda_redirects_for_old_meetings(self): diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index ef58ec25a..884fd2852 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -2041,10 +2041,13 @@ def should_include_assignment(filter_params, assignment): hidden = len(set(filter_params['hide']).intersection(assignment.filter_keywords)) > 0 return shown and not hidden -def agenda_ical(request, num=None, name=None, acronym=None, session_id=None): +def agenda_ical(request, num=None, acronym=None, session_id=None): """Agenda ical view - By default, all agenda items will be shown. A filter can be specified in + If num is None, looks for the next IETF meeting. Otherwise, uses the requested meeting + regardless of its type. + + By default, all agenda items will be shown. A filter can be specified in the querystring. It has the format ?show=...&hide=...&showtypes=...&hidetypes=... @@ -2059,8 +2062,13 @@ def agenda_ical(request, num=None, name=None, acronym=None, session_id=None): Hiding (by wg or type) takes priority over showing. """ - meeting = get_meeting(num, type_in=None) - schedule = get_schedule(meeting, name) + if num is None: + meeting = get_ietf_meeting() + if meeting is None: + raise Http404 + else: + meeting = get_meeting(num, type_in=None) # get requested meeting, whatever its type + schedule = get_schedule(meeting) updated = meeting.updated() if schedule is None and acronym is None and session_id is None: @@ -2099,7 +2107,12 @@ def agenda_ical(request, num=None, name=None, acronym=None, session_id=None): @cache_page(15 * 60) def agenda_json(request, num=None): - meeting = get_meeting(num, type_in=['ietf','interim']) + if num is None: + meeting = get_ietf_meeting() + if meeting is None: + raise Http404 + else: + meeting = get_meeting(num, type_in=None) # get requested meeting, whatever its type sessions = [] locations = set() From 74990cfcb820a2e0a48f7294edbf85670d4f308b Mon Sep 17 00:00:00 2001 From: Robert Sparks <rjsparks@nostrum.com> Date: Tue, 28 Feb 2023 11:54:45 -0600 Subject: [PATCH 5/7] fix: improve api key delete form validation and tests (#5236) --- ietf/ietfauth/tests.py | 14 +++++++++++++- ietf/ietfauth/views.py | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 4821bdb36..c5e2532c8 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -39,7 +39,7 @@ from ietf.ietfauth.utils import has_role from ietf.mailinglists.models import Subscribed from ietf.meeting.factories import MeetingFactory from ietf.nomcom.factories import NomComFactory -from ietf.person.factories import PersonFactory, EmailFactory, UserFactory +from ietf.person.factories import PersonFactory, EmailFactory, UserFactory, PersonalApiKeyFactory from ietf.person.models import Person, Email, PersonalApiKey from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory from ietf.review.models import ReviewWish, UnavailablePeriod @@ -723,8 +723,20 @@ class IetfAuthTests(TestCase): url = urlreverse('ietf.ietfauth.views.apikey_disable') r = self.client.get(url) + self.assertEqual(r.status_code, 200) self.assertContains(r, 'Disable a personal API key') self.assertContains(r, 'Key') + + # Try to delete something that doesn't exist + r = self.client.post(url, {'hash': key.hash()+'bad'}) + self.assertEqual(r.status_code, 200) + self.assertContains(r,"Key validation failed; key not disabled") + + # Try to delete someone else's key + otherkey = PersonalApiKeyFactory() + r = self.client.post(url, {'hash': otherkey.hash()}) + self.assertEqual(r.status_code, 200) + self.assertContains(r,"Key validation failed; key not disabled") # Delete a key r = self.client.post(url, {'hash': key.hash()}) diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index 01a43672d..b29b29321 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -781,7 +781,7 @@ def apikey_disable(request): # class KeyDeleteForm(forms.Form): hash = forms.ChoiceField(label='Key', choices=choices) - def clean_key(self): + def clean_hash(self): hash = force_bytes(self.cleaned_data['hash']) key = PersonalApiKey.validate_key(hash) if key and key.person == request.user.person: @@ -792,7 +792,7 @@ def apikey_disable(request): if request.method == 'POST': form = KeyDeleteForm(request.POST) if form.is_valid(): - hash = force_bytes(form.data['hash']) + hash = force_bytes(form.cleaned_data['hash']) key = PersonalApiKey.validate_key(hash) key.valid = False key.save() From 1eb16c9002af9118763b100ab9ccc8aafabb962f Mon Sep 17 00:00:00 2001 From: Jennifer Richards <jennifer@painless-security.com> Date: Tue, 28 Feb 2023 13:57:16 -0400 Subject: [PATCH 6/7] fix: Let csv.writer handle encoding for agenda_csv view (#5225) --- ietf/meeting/views.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py index 884fd2852..1fb6cca78 100644 --- a/ietf/meeting/views.py +++ b/ietf/meeting/views.py @@ -1805,18 +1805,18 @@ def agenda_extract_slide (item): } def agenda_csv(schedule, filtered_assignments): - response = HttpResponse(content_type="text/csv; charset=%s"%settings.DEFAULT_CHARSET) + encoding = 'utf-8' + response = HttpResponse(content_type=f"text/csv; charset={encoding}") writer = csv.writer(response, delimiter=str(','), quoting=csv.QUOTE_ALL) headings = ["Date", "Start", "End", "Session", "Room", "Area", "Acronym", "Type", "Description", "Session ID", "Agenda", "Slides"] def write_row(row): - encoded_row = [v.encode('utf-8') if isinstance(v, str) else v for v in row] - - while len(encoded_row) < len(headings): - encoded_row.append(None) # produce empty entries at the end as necessary - - writer.writerow(encoded_row) + if len(row) < len(headings): + padding = [None] * (len(headings) - len(row)) # produce empty entries at the end as necessary + else: + padding = [] + writer.writerow(row + padding) def agenda_field(item): agenda_doc = item.session.agenda() From e8f4ddd26b6cfb6c8c01d0b692980eb09062941b Mon Sep 17 00:00:00 2001 From: Robert Sparks <rjsparks@nostrum.com> Date: Tue, 28 Feb 2023 14:14:30 -0600 Subject: [PATCH 7/7] fix: adjust type of fk from community to doc (#5240) --- .../migrations/0010_doc_ids_are_ints.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 ietf/community/migrations/0010_doc_ids_are_ints.py diff --git a/ietf/community/migrations/0010_doc_ids_are_ints.py b/ietf/community/migrations/0010_doc_ids_are_ints.py new file mode 100644 index 000000000..a76eda92a --- /dev/null +++ b/ietf/community/migrations/0010_doc_ids_are_ints.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.28 on 2022-11-05 11:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('community', '0009_add_group_exp_rule_to_groups'), + ] + + operations = [ + migrations.RunSQL( + sql="alter table community_communitylist_added_docs modify document_id int unsigned not null;", + reverse_sql="alter table community_communitylist_added_docs modify document_id varchar(255) not null;" + ), + migrations.RunSQL( + sql="alter table community_searchrule_name_contains_index modify document_id int unsigned not null;", + reverse_sql="alter table community_searchrule_name_contains_index modify document_id varchar(255) not null;" + ), + ]