Skip to content

Commit

Permalink
feat: Allow entering agenda text directly (#6792)
Browse files Browse the repository at this point in the history
* feat: Allow entering agenda text directly (#6532)

* fix: Hide label as well as file/text input box

* refactor: Package javascript for static/dist

* fix: Fix test cases broken by view changes

* test: Add test case for entering agenda text

* refactor: assertRedirects
  • Loading branch information
pselkirk committed Dec 20, 2023
1 parent db8fba4 commit c1e40ff
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 18 deletions.
60 changes: 50 additions & 10 deletions ietf/meeting/tests_views.py
@@ -1,4 +1,4 @@
# Copyright The IETF Trust 2009-2020, All Rights Reserved
# Copyright The IETF Trust 2009-2023, All Rights Reserved
# -*- coding: utf-8 -*-
import datetime
import io
Expand Down Expand Up @@ -6106,29 +6106,29 @@ def test_upload_minutes_agenda(self):

test_file = BytesIO(b'this is some text for a test')
test_file.name = "not_really.json"
r = self.client.post(url,dict(file=test_file))
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('form .is-invalid'))

test_file = BytesIO(b'this is some text for a test'*1510000)
test_file.name = "not_really.pdf"
r = self.client.post(url,dict(file=test_file))
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('form .is-invalid'))

test_file = BytesIO(b'<html><frameset><frame src="foo.html"></frame><frame src="bar.html"></frame></frameset></html>')
test_file.name = "not_really.html"
r = self.client.post(url,dict(file=test_file))
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('form .is-invalid'))

# Test html sanitization
test_file = BytesIO(b'<html><head><title>Title</title></head><body><h1>Title</h1><section>Some text</section></body></html>')
test_file.name = "some.html"
r = self.client.post(url,dict(file=test_file))
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertEqual(r.status_code, 302)
doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document
self.assertEqual(doc.rev,'00')
Expand All @@ -6140,7 +6140,7 @@ def test_upload_minutes_agenda(self):
# txt upload
test_file = BytesIO(b'This is some text for a test, with the word\nvirtual at the beginning of a line.')
test_file.name = "some.txt"
r = self.client.post(url,dict(file=test_file,apply_to_all=False))
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=False))
self.assertEqual(r.status_code, 302)
doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document
self.assertEqual(doc.rev,'01')
Expand All @@ -6152,7 +6152,7 @@ def test_upload_minutes_agenda(self):
self.assertIn('Revise', str(q("Title")))
test_file = BytesIO(b'this is some different text for a test')
test_file.name = "also_some.txt"
r = self.client.post(url,dict(file=test_file,apply_to_all=True))
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=True))
self.assertEqual(r.status_code, 302)
doc = Document.objects.get(pk=doc.pk)
self.assertEqual(doc.rev,'02')
Expand All @@ -6161,7 +6161,7 @@ def test_upload_minutes_agenda(self):
# Test bad encoding
test_file = BytesIO('<html><h1>Title</h1><section>Some\x93text</section></html>'.encode('latin1'))
test_file.name = "some.html"
r = self.client.post(url,dict(file=test_file))
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertContains(r, 'Could not identify the file encoding')
doc = Document.objects.get(pk=doc.pk)
self.assertEqual(doc.rev,'02')
Expand Down Expand Up @@ -6191,7 +6191,7 @@ def test_upload_minutes_agenda_unscheduled(self):

test_file = BytesIO(b'this is some text for a test')
test_file.name = "not_really.txt"
r = self.client.post(url,dict(file=test_file,apply_to_all=False))
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=False))
self.assertEqual(r.status_code, 410)

@override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True)
Expand All @@ -6211,7 +6211,7 @@ def test_upload_minutes_agenda_interim(self):
self.assertFalse(session.sessionpresentation_set.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(file=test_file))
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertEqual(r.status_code, 302)
doc = session.sessionpresentation_set.filter(document__type_id=doctype).first().document
self.assertEqual(doc.rev,'00')
Expand All @@ -6223,6 +6223,46 @@ 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)

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})
redirect_url = urlreverse('ietf.meeting.views.session_details', kwargs={'num':session.meeting.number,'acronym':session.group.acronym})
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.sessionpresentation_set.exists())

test_text = 'Enter agenda from scratch'
r = self.client.post(url,dict(submission_method="enter",content=test_text))
self.assertRedirects(r, redirect_url)
doc = session.sessionpresentation_set.filter(document__type_id='agenda').first().document
self.assertEqual(doc.rev,'00')

r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn('Revise', str(q("Title")))

test_file = BytesIO(b'Upload after enter')
test_file.name = "some.txt"
r = self.client.post(url,dict(submission_method="upload",file=test_file))
self.assertRedirects(r, redirect_url)
doc = Document.objects.get(pk=doc.pk)
self.assertEqual(doc.rev,'01')

r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn('Revise', str(q("Title")))

test_text = 'Enter after upload'
r = self.client.post(url,dict(submission_method="enter",content=test_text))
self.assertRedirects(r, redirect_url)
doc = Document.objects.get(pk=doc.pk)
self.assertEqual(doc.rev,'02')

def test_upload_slides(self):

session1 = SessionFactory(meeting__type_id='ietf')
Expand Down
67 changes: 61 additions & 6 deletions ietf/meeting/views.py
@@ -1,4 +1,4 @@
# Copyright The IETF Trust 2007-2022, All Rights Reserved
# Copyright The IETF Trust 2007-2023, All Rights Reserved
# -*- coding: utf-8 -*-


Expand Down Expand Up @@ -2662,6 +2662,40 @@ def upload_session_minutes(request, session_id, num):
})


class UploadOrEnterAgendaForm(UploadAgendaForm):
ACTIONS = [
("upload", "Upload agenda"),
("enter", "Enter agenda"),
]
submission_method = forms.ChoiceField(choices=ACTIONS, widget=forms.RadioSelect)

content = forms.CharField(widget=forms.Textarea, required=False, strip=False, label="Agenda text")

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["file"].required=False
self.order_fields(["submission_method", "file", "content"])

def clean_content(self):
return self.cleaned_data["content"].replace("\r", "")

def clean_file(self):
submission_method = self.cleaned_data.get("submission_method")
if submission_method == "upload":
return super().clean_file()
return None

def clean(self):
def require_field(f):
if not self.cleaned_data.get(f):
self.add_error(f, ValidationError("You must fill in this field."))

submission_method = self.cleaned_data.get("submission_method")
if submission_method == "upload":
require_field("file")
elif submission_method == "enter":
require_field("content")

def upload_session_agenda(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)
Expand All @@ -2680,10 +2714,23 @@ def upload_session_agenda(request, session_id, num):
agenda_sp = session.sessionpresentation_set.filter(document__type='agenda').first()

if request.method == 'POST':
form = UploadAgendaForm(show_apply_to_all_checkbox,request.POST,request.FILES)
form = UploadOrEnterAgendaForm(show_apply_to_all_checkbox,request.POST,request.FILES)
if form.is_valid():
file = request.FILES['file']
_, ext = os.path.splitext(file.name)
submission_method = form.cleaned_data['submission_method']
if submission_method == "upload":
file = request.FILES['file']
_, ext = os.path.splitext(file.name)
else:
if agenda_sp:
doc = agenda_sp.document
_, ext = os.path.splitext(doc.uploaded_filename)
else:
ext = ".md"
fd, name = tempfile.mkstemp(suffix=ext, text=True)
os.close(fd)
with open(name, "w") as file:
file.write(form.cleaned_data['content'])
file = open(name, "rb")
apply_to_all = session.type.slug == 'regular'
if show_apply_to_all_checkbox:
apply_to_all = form.cleaned_data['apply_to_all']
Expand Down Expand Up @@ -2738,15 +2785,23 @@ def upload_session_agenda(request, session_id, num):
doc.uploaded_filename = filename
e = NewRevisionDocEvent.objects.create(doc=doc,by=request.user.person,type='new_revision',desc='New revision available: %s'%doc.rev,rev=doc.rev)
# The way this function builds the filename it will never trigger the file delete in handle_file_upload.
save_error = handle_upload_file(file, filename, session.meeting, 'agenda', request=request, encoding=form.file_encoding[file.name])
try:
encoding=form.file_encoding[file.name]
except AttributeError:
encoding=None
save_error = handle_upload_file(file, filename, session.meeting, 'agenda', request=request, encoding=encoding)
if save_error:
form.add_error(None, save_error)
else:
doc.save_with_history([e])
messages.success(request, f'Successfully uploaded agenda as revision {doc.rev}.')
return redirect('ietf.meeting.views.session_details',num=num,acronym=session.group.acronym)
else:
form = UploadAgendaForm(show_apply_to_all_checkbox, initial={'apply_to_all':session.type_id=='regular'})
initial={'apply_to_all':session.type_id=='regular', 'submission_method':'upload'}
if agenda_sp:
doc = agenda_sp.document
initial['content'] = doc.text()
form = UploadOrEnterAgendaForm(show_apply_to_all_checkbox, initial=initial)

return render(request, "meeting/upload_session_agenda.html",
{'session': session,
Expand Down
28 changes: 28 additions & 0 deletions ietf/static/js/upload-session-agenda.js
@@ -0,0 +1,28 @@
$(document)
.ready(function () {
var form = $("form.my-3");

// review submission selection
form.find("[name=submission_method]")
.on("click change", function () {
var val = form.find("[name=submission_method]:checked")
.val();

var shouldBeVisible = {
upload: ['[name="file"]'],
enter: ['[name="content"]']
};

for (var v in shouldBeVisible) {
for (var i in shouldBeVisible[v]) {
var selector = shouldBeVisible[v][i];
var row = form.find(selector).parent();
if ($.inArray(selector, shouldBeVisible[val]) != -1)
row.show();
else
row.hide();
}
}
})
.trigger("change");
});
7 changes: 5 additions & 2 deletions ietf/templates/meeting/upload_session_agenda.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{# Copyright The IETF Trust 2015-2023, All Rights Reserved #}
{% load origin static django_bootstrap5 tz %}
{% block title %}
{% if agenda_sp %}
Expand Down Expand Up @@ -29,6 +29,9 @@ <h2>Session {{ session_number }} : {{ session.official_timeslotassignment.timesl
<form enctype="multipart/form-data" method="post" class="my-3">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Upload</button>
<button type="submit" class="btn btn-primary">Save</button>
</form>
{% endblock %}
{% block js %}
<script src="{% static 'ietf/js/upload-session-agenda.js' %}"></script>
{% endblock %}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -152,6 +152,7 @@
"ietf/static/js/timezone.js",
"ietf/static/js/upcoming.js",
"ietf/static/js/upload-material.js",
"ietf/static/js/upload-session-agenda.js",
"ietf/static/js/upload_bofreq.js",
"ietf/static/js/upload_statement.js",
"ietf/static/js/zxcvbn.js"
Expand Down

0 comments on commit c1e40ff

Please sign in to comment.