8000 feat: Warn if uploading minutes before session end by jimfenton · Pull Request #8700 · ietf-tools/datatracker · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

feat: Warn if uploading minutes before session end #8700

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

Merged
merged 8 commits into from
Apr 2, 2025
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
286 changes: 156 additions & 130 deletions ietf/meeting/tests_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6541,110 +6541,130 @@ def test_upload_bluesheets_interim_chair_access(self):
self.assertIn('Upload', str(q("title")))


def test_upload_minutes_agenda(self):
for doctype in ('minutes','agenda'):
session = SessionFactory(meeting__type_id='ietf')
if doctype == 'minutes':
url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id})
else:
url = urlreverse('ietf.meeting.views.upload_session_agenda',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.exists())
self.assertFalse(q('form input[type="checkbox"]'))

session2 = SessionFactory(meeting=session.meeting,group=session.group)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('form input[type="checkbox"]'))

# test not submitting a file
r = self.client.post(url, dict(submission_method="upload"))
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')
test_file.name = "not_really.json"
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(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(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(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')
text = doc.text()
self.assertIn('Some text', text)
self.assertNotIn('<section>', text)
text = retrieve_str(doctype, f"{doc.name}-{doc.rev}.html")
self.assertIn('Some text', text)
self.assertNotIn('<section>', text)

# txt upload
test_bytes = b'This is some text for a test, with the word\nvirtual at the beginning of a line.'
test_file = BytesIO(test_bytes)
test_file.name = "some.txt"
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=False))
self.assertEqual(r.status_code, 302)
doc = session.presentations.filter(document__type_id=doctype).first().document
self.assertEqual(doc.rev,'01')
self.assertFalse(session2.presentations.filter(document__type_id=doctype))
retrieved_bytes = retrieve_bytes(doctype, f"{doc.name}-{doc.rev}.txt")
self.assertEqual(retrieved_bytes, test_bytes)

def test_label_future_sessions(self):
self.client.login(username='secretary', password='secretary+password')
for future in (True, False):
mtg_date = date_today()+datetime.timedelta(days=180 if future else -180)
session = SessionFactory(meeting__type_id='ietf', meeting__date=mtg_date)
# Verify future warning shows on the session details panel
url = urlreverse('ietf.meeting.views.session_details', kwargs={'num':session.meeting.number, 'acronym': session.group.acronym})
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn('Revise', str(q("Title")))
test_bytes = b'this is some different text for a test'
test_file = BytesIO(test_bytes)
test_file.name = "also_some.txt"
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')
self.assertTrue(session2.presentations.filter(document__type_id=doctype))
retrieved_bytes = retrieve_bytes(doctype, f"{doc.name}-{doc.rev}.txt")
self.assertEqual(retrieved_bytes, test_bytes)

# 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(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')
self.assertTrue(r.status_code==200)
if future:
self.assertContains(r, "Session has not ended yet")
else:
self.assertNotContains(r, "Session has not ended yet")

# 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_upload_minutes_agenda(self):
for doctype in ('minutes','agenda'):
for future in (True, False):
mtg_date = date_today()+datetime.timedelta(days=180 if future else -180)
session = SessionFactory(meeting__type_id='ietf', meeting__date=mtg_date)
if doctype == 'minutes':
url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id})
else:
url = urlreverse('ietf.meeting.views.upload_session_agenda',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.exists())
self.assertFalse(q('form input[type="checkbox"]'))
if future and doctype == "minutes":
self.assertContains(r, "Session has not ended yet")
else:
self.assertNotContains(r, "Session has not ended yet")

session2 = SessionFactory(meeting=session.meeting,group=session.group)
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertTrue(q('form input[type="checkbox"]'))

# test not submitting a file
r = self.client.post(url, dict(submission_method="upload"))
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')
test_file.name = "not_really.json"
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(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(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(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')
text = doc.text()
self.assertIn('Some text', text)
self.assertNotIn('<section>', text)
text = retrieve_str(doctype, f"{doc.name}-{doc.rev}.html")
self.assertIn('Some text', text)
self.assertNotIn('<section>', text)

# txt upload
test_bytes = b'This is some text for a test, with the word\nvirtual at the beginning of a line.'
test_file = BytesIO(test_bytes)
test_file.name = "some.txt"
r = self.client.post(url,dict(submission_method="upload",file=test_file,apply_to_all=False))
self.assertEqual(r.status_code, 302)
doc = session.presentations.filter(document__type_id=doctype).first().document
self.assertEqual(doc.rev,'01')
self.assertFalse(session2.presentations.filter(document__type_id=doctype))
retrieved_bytes = retrieve_bytes(doctype, f"{doc.name}-{doc.rev}.txt")
self.assertEqual(retrieved_bytes, test_bytes)

r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertIn('Revise', str(q("Title")))
test_bytes = b'this is some different text for a test'
test_file = BytesIO(test_bytes)
test_file.name = "also_some.txt"
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')
self.assertTrue(session2.presentations.filter(document__type_id=doctype))
retrieved_bytes = retrieve_bytes(doctype, f"{doc.name}-{doc.rev}.txt")
self.assertEqual(retrieved_bytes, test_bytes)

# 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(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')

# 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_upload_minutes_agenda_unscheduled(self):
for doctype in ('minutes','agenda'):
Expand All @@ -6661,6 +6681,7 @@ def test_upload_minutes_agenda_unscheduled(self):
self.assertIn('Upload', str(q("Title")))
self.assertFalse(session.presentations.exists())
self.assertFalse(q('form input[type="checkbox"]'))
self.assertNotContains(r, "Session has not ended yet")

test_file = BytesIO(b'this is some text for a test')
test_file.name = "not_really.txt"
Expand All @@ -6669,35 +6690,40 @@ def test_upload_minutes_agenda_unscheduled(self):

@override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True)
def test_upload_minutes_agenda_interim(self):
session=SessionFactory(meeting__type_id='interim')
for doctype in ('minutes','agenda'):
if doctype=='minutes':
url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id})
else:
url = urlreverse('ietf.meeting.views.upload_session_agenda',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_bytes = b'this is some text for a test'
test_file = BytesIO(test_bytes)
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')
retrieved_bytes = retrieve_bytes(doctype, f"{doc.name}-{doc.rev}.txt")
self.assertEqual(retrieved_bytes, test_bytes)

# 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)
for future in (True, False):
session=SessionFactory(meeting__type_id='interim', meeting__date = date_today()+datetime.timedelta(days=180 if future else -180))
if doctype=='minutes':
url = urlreverse('ietf.meeting.views.upload_session_minutes',kwargs={'num':session.meeting.number,'session_id':session.id})
else:
url = urlreverse('ietf.meeting.views.upload_session_agenda',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))
if future and doctype == "minutes":
self.assertContains(r, "Session has not ended yet")
else:
self.assertNotContains(r, "Session has not ended yet")
test_bytes = b'this is some text for a test'
test_file = BytesIO(test_bytes)
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')
retrieved_bytes = retrieve_bytes(doctype, f"{doc.name}-{doc.rev}.txt")
self.assertEqual(retrieved_bytes, test_bytes)

# 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)

@override_settings(MEETING_MATERIALS_SERVE_LOCALLY=True)
def test_upload_narrativeminutes(self):
Expand Down
6 changes: 6 additions & 0 deletions ietf/meeting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2522,6 +2522,8 @@ def session_details(request, num, acronym):
else:
pending_suggestions = SlideSubmission.objects.none()

tsa = session.official_timeslotassignment()
future = tsa is not None and timezone.now() < tsa.timeslot.end_time()
return render(request, "meeting/session_details.html",
{ 'scheduled_sessions':scheduled_sessions ,
'unscheduled_sessions':unscheduled_sessions ,
Expand All @@ -2532,6 +2534,7 @@ def session_details(request, num, acronym):
'can_manage_materials' : can_manage,
'can_view_request': can_view_request,
'thisweek': datetime_today()-datetime.timedelta(days=7),
'future': future,
})

class SessionDraftsForm(forms.Form):
Expand Down Expand Up @@ -2823,11 +2826,14 @@ def upload_session_minutes(request, session_id, num):
else:
form = UploadMinutesForm(show_apply_to_all_checkbox)

tsa = session.official_timeslotassignment()
future = tsa is not None and timezone.now() < tsa.timeslot.end_time()
return render(request, "meeting/upload_session_minutes.html",
{'session': session,
'session_number': session_number,
'minutes_sp' : minutes_sp,
'form': form,
'future': future,
})

@role_required("Secretariat")
Expand Down
Loading
Loading
0