feat: Allow entering agenda text directly (#6792)
* 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
This commit is contained in:
parent
db8fba486b
commit
c1e40ff100
|
@ -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
|
||||
|
@ -6106,21 +6106,21 @@ class MaterialsTests(TestCase):
|
|||
|
||||
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'))
|
||||
|
@ -6128,7 +6128,7 @@ class MaterialsTests(TestCase):
|
|||
# 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')
|
||||
|
@ -6140,7 +6140,7 @@ class MaterialsTests(TestCase):
|
|||
# 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')
|
||||
|
@ -6152,7 +6152,7 @@ class MaterialsTests(TestCase):
|
|||
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')
|
||||
|
@ -6161,7 +6161,7 @@ class MaterialsTests(TestCase):
|
|||
# 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')
|
||||
|
@ -6191,7 +6191,7 @@ class MaterialsTests(TestCase):
|
|||
|
||||
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)
|
||||
|
@ -6211,7 +6211,7 @@ class MaterialsTests(TestCase):
|
|||
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')
|
||||
|
@ -6223,6 +6223,46 @@ class MaterialsTests(TestCase):
|
|||
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')
|
||||
|
|
|
@ -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 -*-
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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']
|
||||
|
@ -2738,7 +2785,11 @@ 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:
|
||||
|
@ -2746,7 +2797,11 @@ def upload_session_agenda(request, session_id, num):
|
|||
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,
|
||||
|
|
28
ietf/static/js/upload-session-agenda.js
Normal file
28
ietf/static/js/upload-session-agenda.js
Normal file
|
@ -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");
|
||||
});
|
|
@ -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 %}
|
||||
|
@ -29,6 +29,9 @@
|
|||
<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 %}
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue