Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow entering agenda text directly #6792

Merged
merged 7 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 50 additions & 10 deletions ietf/meeting/tests_views.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line 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 %}
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
Original file line number Diff line number Diff line change
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
Loading