Skip to content

Commit

Permalink
feat: upload narrative minutes (#7125)
Browse files Browse the repository at this point in the history
* feat: upload narrative minutes

* chore: cover other new URL path
  • Loading branch information
rjsparks authored Mar 4, 2024
1 parent aaf402f commit 7287e98
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 21 deletions.
3 changes: 3 additions & 0 deletions ietf/meeting/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,9 @@ def __init__(self, show_apply_to_all_checkbox, *args, **kwargs):
class UploadMinutesForm(ApplyToAllFileUploadForm):
doc_type = 'minutes'

class UploadNarrativeMinutesForm(ApplyToAllFileUploadForm):
doc_type = 'narrativeminutes'


class UploadAgendaForm(ApplyToAllFileUploadForm):
doc_type = 'agenda'
Expand Down
1 change: 0 additions & 1 deletion ietf/meeting/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,6 @@ def can_manage_materials(self, user):
return can_manage_materials(user,self.group)

def is_material_submission_cutoff(self):
debug.say("is_material_submission_cutoff got called")
return date_today(datetime.timezone.utc) > self.meeting.get_submission_correction_date()

def joint_with_groups_acronyms(self):
Expand Down
27 changes: 27 additions & 0 deletions ietf/meeting/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6233,6 +6233,33 @@ def test_upload_minutes_agenda_interim(self):
self.requests_mock.get(f'{session.notes_url()}/info', text=json.dumps({'title': 'title', 'updatetime': '2021-12-01T17:11:00z'}))
self.crawl_materials(url=url, top=top)

@override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True)
def test_upload_narrativeminutes(self):
for type_id in ["interim","ietf"]:
session=SessionFactory(meeting__type_id=type_id,group__acronym='iesg')
doctype='narrativeminutes'
url = urlreverse('ietf.meeting.views.upload_session_narrativeminutes',kwargs={'num':session.meeting.number,'session_id':session.id})
self.client.logout()
login_testing_unauthorized(self,"secretary",url)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn('Upload', str(q("title")))
self.assertFalse(session.presentations.filter(document__type_id=doctype))
test_file = BytesIO(b'this is some text for a test')
test_file.name = "not_really.txt"
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertEqual(r.status_code, 302)
doc = session.presentations.filter(document__type_id=doctype).first().document
self.assertEqual(doc.rev,'00')

# Verify that we don't have dead links
url = urlreverse('ietf.meeting.views.session_details', kwargs={'num':session.meeting.number, 'acronym': session.group.acronym})
top = '/meeting/%s/' % session.meeting.number
self.requests_mock.get(f'{session.notes_url()}/download', text='markdown notes')
self.requests_mock.get(f'{session.notes_url()}/info', text=json.dumps({'title': 'title', 'updatetime': '2021-12-01T17:11:00z'}))
self.crawl_materials(url=url, top=top)

def test_enter_agenda(self):
session = SessionFactory(meeting__type_id='ietf')
url = urlreverse('ietf.meeting.views.upload_session_agenda',kwargs={'num':session.meeting.number,'session_id':session.id})
Expand Down
1 change: 1 addition & 0 deletions ietf/meeting/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def get_redirect_url(self, *args, **kwargs):
url(r'^session/(?P<session_id>\d+)/drafts$', views.add_session_drafts),
url(r'^session/(?P<session_id>\d+)/bluesheets$', views.upload_session_bluesheets),
url(r'^session/(?P<session_id>\d+)/minutes$', views.upload_session_minutes),
url(r'^session/(?P<session_id>\d+)/narrativeminutes$', views.upload_session_narrativeminutes),
url(r'^session/(?P<session_id>\d+)/agenda$', views.upload_session_agenda),
url(r'^session/(?P<session_id>\d+)/import/minutes$', views.import_session_minutes),
url(r'^session/(?P<session_id>\d+)/propose_slides$', views.propose_session_slides),
Expand Down
25 changes: 12 additions & 13 deletions ietf/meeting/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright The IETF Trust 2016-2020, All Rights Reserved
# Copyright The IETF Trust 2016-2024, All Rights Reserved
# -*- coding: utf-8 -*-
import datetime
import itertools
Expand Down Expand Up @@ -555,7 +555,7 @@ class SaveMaterialsError(Exception):
pass


def save_session_minutes_revision(session, file, ext, request, encoding=None, apply_to_all=False):
def save_session_minutes_revision(session, file, ext, request, encoding=None, apply_to_all=False, narrative=False):
"""Creates or updates session minutes records
This updates the database models to reflect a new version. It does not handle
Expand All @@ -568,7 +568,8 @@ def save_session_minutes_revision(session, file, ext, request, encoding=None, ap
Returns (Document, [DocEvents]), which should be passed to doc.save_with_history()
if the file contents are stored successfully.
"""
minutes_sp = session.presentations.filter(document__type='minutes').first()
document_type = DocTypeName.objects.get(slug= 'narrativeminutes' if narrative else 'minutes')
minutes_sp = session.presentations.filter(document__type=document_type).first()
if minutes_sp:
doc = minutes_sp.document
doc.rev = '%02d' % (int(doc.rev)+1)
Expand All @@ -580,28 +581,26 @@ def save_session_minutes_revision(session, file, ext, request, encoding=None, ap
if not sess_time:
raise SessionNotScheduledError
if session.meeting.type_id=='ietf':
name = 'minutes-%s-%s' % (session.meeting.number,
session.group.acronym)
title = 'Minutes IETF%s: %s' % (session.meeting.number,
session.group.acronym)
name = f"{document_type.prefix}-{session.meeting.number}-{session.group.acronym}"
title = f"{document_type.name} IETF{session.meeting.number}: {session.group.acronym}"
if not apply_to_all:
name += '-%s' % (sess_time.strftime("%Y%m%d%H%M"),)
title += ': %s' % (sess_time.strftime("%a %H:%M"),)
else:
name = 'minutes-%s-%s' % (session.meeting.number, sess_time.strftime("%Y%m%d%H%M"))
title = 'Minutes %s: %s' % (session.meeting.number, sess_time.strftime("%a %H:%M"))
name =f"{document_type.prefix}-{session.meeting.number}-{sess_time.strftime('%Y%m%d%H%M')}"
title = f"{document_type.name} {session.meeting.number}: {sess_time.strftime('%a %H:%M')}"
if Document.objects.filter(name=name).exists():
doc = Document.objects.get(name=name)
doc.rev = '%02d' % (int(doc.rev)+1)
else:
doc = Document.objects.create(
name = name,
type_id = 'minutes',
type = document_type,
title = title,
group = session.group,
rev = '00',
)
doc.states.add(State.objects.get(type_id='minutes',slug='active'))
doc.states.add(State.objects.get(type_id=document_type.slug,slug='active'))
if session.presentations.filter(document=doc).exists():
sp = session.presentations.get(document=doc)
sp.rev = doc.rev
Expand All @@ -611,7 +610,7 @@ def save_session_minutes_revision(session, file, ext, request, encoding=None, ap
if apply_to_all:
for other_session in get_meeting_sessions(session.meeting.number, session.group.acronym):
if other_session != session:
other_session.presentations.filter(document__type='minutes').delete()
other_session.presentations.filter(document__type=document_type).delete()
other_session.presentations.create(document=doc,rev=doc.rev)
filename = f'{doc.name}-{doc.rev}{ext}'
doc.uploaded_filename = filename
Expand All @@ -628,7 +627,7 @@ def save_session_minutes_revision(session, file, ext, request, encoding=None, ap
file=file,
filename=doc.uploaded_filename,
meeting=session.meeting,
subdir='minutes',
subdir=document_type.slug,
request=request,
encoding=encoding,
)
Expand Down
58 changes: 57 additions & 1 deletion ietf/meeting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@

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

request_summary_exclude_group_types = ['team']

Expand Down Expand Up @@ -2662,6 +2663,61 @@ def upload_session_minutes(request, session_id, num):
'form': form,
})

@role_required("Secretariat")
def upload_session_narrativeminutes(request, session_id, num):
# num is redundant, but we're dragging it along an artifact of where we are in the current URL structure
session = get_object_or_404(Session,pk=session_id)
if session.group.acronym != "iesg":
raise Http404()

session_number = None
sessions = get_sessions(session.meeting.number,session.group.acronym)
show_apply_to_all_checkbox = len(sessions) > 1 if session.type_id == 'regular' else False
if len(sessions) > 1:
session_number = 1 + sessions.index(session)

narrativeminutes_sp = session.presentations.filter(document__type='narrativeminutes').first()

if request.method == 'POST':
form = UploadNarrativeMinutesForm(show_apply_to_all_checkbox,request.POST,request.FILES)
if form.is_valid():
file = request.FILES['file']
_, ext = os.path.splitext(file.name)
apply_to_all = session.type_id == 'regular'
if show_apply_to_all_checkbox:
apply_to_all = form.cleaned_data['apply_to_all']

# Set up the new revision
try:
save_session_minutes_revision(
session=session,
apply_to_all=apply_to_all,
file=file,
ext=ext,
encoding=form.file_encoding[file.name],
request=request,
narrative=True
)
except SessionNotScheduledError:
return HttpResponseGone(
"Cannot receive uploads for an unscheduled session. Please check the session ID.",
content_type="text/plain",
)
except SaveMaterialsError as err:
form.add_error(None, str(err))
else:
# no exception -- success!
messages.success(request, f'Successfully uploaded narrative minutes as revision {session.narrative_minutes().rev}.')
return redirect('ietf.meeting.views.session_details', num=num, acronym=session.group.acronym)
else:
form = UploadMinutesForm(show_apply_to_all_checkbox)

return render(request, "meeting/upload_session_narrativeminutes.html",
{'session': session,
'session_number': session_number,
'minutes_sp' : narrativeminutes_sp,
'form': form,
})

class UploadOrEnterAgendaForm(UploadAgendaForm):
ACTIONS = [
Expand Down
54 changes: 49 additions & 5 deletions ietf/name/fixtures/names.json
Original file line number Diff line number Diff line change
Expand Up @@ -2578,6 +2578,32 @@
"model": "doc.state",
"pk": 177
},
{
"fields": {
"desc": "",
"name": "Active",
"next_states": [],
"order": 0,
"slug": "active",
"type": "narrativeminutes",
"used": true
},
"model": "doc.state",
"pk": 178
},
{
"fields": {
"desc": "",
"name": "Deleted",
"next_states": [],
"order": 1,
"slug": "deleted",
"type": "narrativeminutes",
"used": true
},
"model": "doc.state",
"pk": 179
},
{
"fields": {
"label": "State"
Expand Down Expand Up @@ -2739,6 +2765,13 @@
"model": "doc.statetype",
"pk": "minutes"
},
{
"fields": {
"label": "State"
},
"model": "doc.statetype",
"pk": "narrativeminutes"
},
{
"fields": {
"label": "State"
Expand Down Expand Up @@ -10763,6 +10796,17 @@
"model": "name.doctypename",
"pk": "minutes"
},
{
"fields": {
"desc": "",
"name": "Narrative Minutes",
"order": 0,
"prefix": "narrative-minutes",
"used": true
},
"model": "name.doctypename",
"pk": "narrativeminutes"
},
{
"fields": {
"desc": "",
Expand Down Expand Up @@ -16734,7 +16778,7 @@
"fields": {
"command": "xym",
"switch": "--version",
"time": "2023-11-21T08:09:45.989Z",
"time": "2024-02-21T08:06:28.313Z",
"used": true,
"version": "xym 0.7.0"
},
Expand All @@ -16745,7 +16789,7 @@
"fields": {
"command": "pyang",
"switch": "--version",
"time": "2023-11-21T08:09:46.322Z",
"time": "2024-02-21T08:06:28.663Z",
"used": true,
"version": "pyang 2.6.0"
},
Expand All @@ -16756,7 +16800,7 @@
"fields": {
"command": "yanglint",
"switch": "--version",
"time": "2023-11-21T08:09:46.338Z",
"time": "2024-02-21T08:06:28.685Z",
"used": true,
"version": "yanglint SO 1.9.2"
},
Expand All @@ -16767,9 +16811,9 @@
"fields": {
"command": "xml2rfc",
"switch": "--version",
"time": "2023-11-21T08:09:47.251Z",
"time": "2024-02-21T08:06:29.492Z",
"used": true,
"version": "xml2rfc 3.18.2"
"version": "xml2rfc 3.19.4"
},
"model": "utils.versioninfo",
"pk": 4
Expand Down
2 changes: 1 addition & 1 deletion ietf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,7 @@ def skip_unreadable_post(record):
MEETING_VALID_UPLOAD_MIME_TYPES = {
'agenda': ['text/plain', 'text/html', 'text/markdown', 'text/x-markdown', ],
'minutes': ['text/plain', 'text/html', 'application/pdf', 'text/markdown', 'text/x-markdown', ],
'narrative-minutes': ['text/plain', 'text/html', 'application/pdf', 'text/markdown', 'text/x-markdown', ],
'narrativeminutes': ['text/plain', 'text/html', 'application/pdf', 'text/markdown', 'text/x-markdown', ],
'slides': [],
'bluesheets': ['application/pdf', 'text/plain', ],
'procmaterials':['application/pdf', ],
Expand Down
8 changes: 8 additions & 0 deletions ietf/templates/meeting/session_details_panel.html
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ <h3 class="mt-4">Agenda, Minutes, and Bluesheets</h3>
{% if user|has_role:"Secretariat" or can_manage_materials %}
{% if pres.document.type.slug == 'minutes' %}
{% url 'ietf.meeting.views.upload_session_minutes' session_id=session.pk num=session.meeting.number as upload_url %}
{% elif pres.document.type.slug == 'narrativeminutes' %}
{% url 'ietf.meeting.views.upload_session_narrativeminutes' session_id=session.pk num=session.meeting.number as upload_url %}
{% elif pres.document.type.slug == 'agenda' %}
{% url 'ietf.meeting.views.upload_session_agenda' session_id=session.pk num=session.meeting.number as upload_url %}
{% else %}
Expand Down Expand Up @@ -106,6 +108,12 @@ <h3 class="mt-4">Agenda, Minutes, and Bluesheets</h3>
Upload minutes
</a>
{% endif %}
{% if not session.type_counter.narrativeminutes and session.group.acronym == "iesg" %}
<a class="btn btn-primary"
href="{% url 'ietf.meeting.views.upload_session_narrativeminutes' session_id=session.pk num=session.meeting.number %}">
Upload narrative minutes
</a>
{% endif %}
{% endif %}
{% if user|has_role:"Secretariat" and not session.type_counter.bluesheets or meeting.type.slug == 'interim' and can_manage_materials and not session.type_counter.bluesheets %}
<a class="btn btn-primary"
Expand Down
34 changes: 34 additions & 0 deletions ietf/templates/meeting/upload_session_narrativeminutes.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2024, All Rights Reserved #}
{% load origin static django_bootstrap5 tz %}
{% block title %}
{% if narrativeminutes_sp %}
Revise
{% else %}
Upload
{% endif %}
Narrative Minutes for {{ session.meeting }} : {{ session.group.acronym }}
{% endblock %}
{% block content %}
{% origin %}
<h1>
{% if narrativeminutes_sp %}
Revise
{% else %}
Upload
{% endif %}
Narrative Minutes for {{ session.meeting }}
<br>
<small class="text-body-secondary">{{ session.group.acronym }}
{% if session.name %}: {{ session.name }}{% endif %}
</small>
</h1>
{% if session_number %}
<h2>Session {{ session_number }} : {{ session.official_timeslotassignment.timeslot.time|timezone:session.meeting.time_zone|date:"D M-d-Y Hi" }}</h2>
{% endif %}
<form enctype="multipart/form-data" method="post" class="my-3">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Upload</button>
</form>
{% endblock %}

0 comments on commit 7287e98

Please sign in to comment.