diff --git a/ietf/community/tests.py b/ietf/community/tests.py
index d41b4b8ee..d01745f99 100644
--- a/ietf/community/tests.py
+++ b/ietf/community/tests.py
@@ -101,7 +101,6 @@ class CommunityListTests(WebTest):
self.assertContains(r, draft.name)
def test_manage_personal_list(self):
- return # FIXME-LARS
PersonFactory(user__username='plain')
ad = Person.objects.get(user__username='ad')
@@ -116,7 +115,7 @@ class CommunityListTests(WebTest):
# add document
self.assertIn('add_document', page.forms)
form = page.forms['add_document']
- form['documents']=draft.pk
+ form['documents'].options=[(draft.pk, True, draft.name)]
page = form.submit('action',value='add_documents')
self.assertEqual(page.status_int, 302)
clist = CommunityList.objects.get(user__username="plain")
@@ -173,7 +172,6 @@ class CommunityListTests(WebTest):
self.assertTrue(not clist.searchrule_set.filter(rule_type="author_rfc"))
def test_manage_group_list(self):
- return # FIXME-LARS
draft = WgDraftFactory(group__acronym='mars')
RoleFactory(group__acronym='mars',name_id='chair',person=PersonFactory(user__username='marschairman'))
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
index 3a6211120..4f7848c91 100644
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -228,7 +228,6 @@ class SearchTests(TestCase):
self.assertContains(r, "Document Search")
def test_docs_for_ad(self):
- return # FIXME-LARS
ad = RoleFactory(name_id='ad',group__type_id='area',group__state_id='active').person
draft = IndividualDraftFactory(ad=ad)
draft.action_holders.set([PersonFactory()])
@@ -273,7 +272,6 @@ class SearchTests(TestCase):
self.assertContains(r, 'title="AUTH48"') # title attribute of AUTH48 badge in auth48_alert_badge filter
def test_drafts_in_last_call(self):
- return # FIXME-LARS
draft = IndividualDraftFactory(pages=1)
draft.action_holders.set([PersonFactory()])
draft.set_state(State.objects.get(type="draft-iesg", slug="lc"))
@@ -283,7 +281,6 @@ class SearchTests(TestCase):
self.assertContains(r, escape(draft.action_holders.first().plain_name()))
def test_in_iesg_process(self):
- return # FIXME-LARS
doc_in_process = IndividualDraftFactory()
doc_in_process.action_holders.set([PersonFactory()])
doc_in_process.set_state(State.objects.get(type='draft-iesg', slug='lc'))
@@ -334,7 +331,6 @@ class SearchTests(TestCase):
self.assertEqual(data[0]["id"], doc_alias.pk)
def test_recent_drafts(self):
- return # FIXME-LARS
# Three drafts to show with various warnings
drafts = WgDraftFactory.create_batch(3,states=[('draft','active'),('draft-iesg','ad-eval')])
for index, draft in enumerate(drafts):
@@ -800,7 +796,6 @@ Man Expires September 22, 2015 [Page 3]
self.client.login(username=username, password=username + '+password')
def test_edit_authors_permissions(self):
- return # FIXME-LARS
"""Only the secretariat may edit authors"""
draft = WgDraftFactory(authors=PersonFactory.create_batch(3))
RoleFactory(group=draft.group, name_id='chair')
@@ -915,7 +910,6 @@ Man Expires September 22, 2015 [Page 3]
post_data[_add_prefix(str(form_index) + '-ORDER')] = str(insert_order)
def test_edit_authors_missing_basis(self):
- return # FIXME-LARS
draft = WgDraftFactory()
DocumentAuthorFactory.create_batch(3, document=draft)
url = urlreverse('ietf.doc.views_doc.edit_authors', kwargs=dict(name=draft.name))
@@ -932,7 +926,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertContains(r, 'This field is required.')
def test_edit_authors_no_change(self):
- return # FIXME-LARS
draft = WgDraftFactory()
DocumentAuthorFactory.create_batch(3, document=draft)
url = urlreverse('ietf.doc.views_doc.edit_authors', kwargs=dict(name=draft.name))
@@ -1011,15 +1004,12 @@ Man Expires September 22, 2015 [Page 3]
self.assertIn(auth.name, evt.desc)
def test_edit_authors_append_author(self):
- return # FIXME-LARS
self.do_edit_authors_append_authors_test(1)
def test_edit_authors_append_authors(self):
- return # FIXME-LARS
self.do_edit_authors_append_authors_test(3)
def test_edit_authors_insert_author(self):
- return # FIXME-LARS
"""Can add author in the middle of the list"""
draft = WgDraftFactory()
DocumentAuthorFactory.create_batch(3, document=draft)
@@ -1076,7 +1066,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertEqual(reorder_events.count(), 2)
def test_edit_authors_remove_author(self):
- return # FIXME-LARS
draft = WgDraftFactory()
DocumentAuthorFactory.create_batch(3, document=draft)
url = urlreverse('ietf.doc.views_doc.edit_authors', kwargs=dict(name=draft.name))
@@ -1127,7 +1116,6 @@ Man Expires September 22, 2015 [Page 3]
self.assertIn(reordered_person.name, reordered_event.desc)
def test_edit_authors_reorder_authors(self):
- return # FIXME-LARS
draft = WgDraftFactory()
DocumentAuthorFactory.create_batch(3, document=draft)
url = urlreverse('ietf.doc.views_doc.edit_authors', kwargs=dict(name=draft.name))
@@ -1184,7 +1172,6 @@ Man Expires September 22, 2015 [Page 3]
)
def test_edit_authors_edit_fields(self):
- return # FIXME-LARS
draft = WgDraftFactory()
DocumentAuthorFactory.create_batch(3, document=draft)
url = urlreverse('ietf.doc.views_doc.edit_authors', kwargs=dict(name=draft.name))
@@ -1287,14 +1274,13 @@ Man Expires September 22, 2015 [Page 3]
with self.settings(DOC_ACTION_HOLDER_AGE_LIMIT_DAYS=20):
r = self.client.get(url)
- # FIXME-LARS
- # self.assertContains(r, 'Action Holders') # should still be shown
- # q = PyQuery(r.content)
- # self.assertEqual(len(self._pyquery_select_action_holder_string(q, '(None)')), 0)
- # for person in draft.action_holders.all():
- # self.assertEqual(len(self._pyquery_select_action_holder_string(q, person.plain_name())), 1)
- # # check that one action holder was marked as old
- # self.assertEqual(len(self._pyquery_select_action_holder_string(q, 'for 30 days')), 1)
+ self.assertContains(r, 'Action Holders') # should still be shown
+ q = PyQuery(r.content)
+ self.assertEqual(len(self._pyquery_select_action_holder_string(q, '(None)')), 0)
+ for person in draft.action_holders.all():
+ self.assertEqual(len(self._pyquery_select_action_holder_string(q, person.plain_name())), 1)
+ # check that one action holder was marked as old
+ self.assertEqual(len(self._pyquery_select_action_holder_string(q, 'for 30 days')), 1)
@mock.patch.object(Document, 'action_holders_enabled', return_value=True, new_callable=mock.PropertyMock)
def test_document_draft_action_holders_buttons(self, mock_method):
@@ -1456,12 +1442,11 @@ Man Expires September 22, 2015 [Page 3]
class DocTestCase(TestCase):
def test_status_change(self):
- return # FIXME-LARS
statchg = StatusChangeFactory()
r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=statchg.name)))
self.assertEqual(r.status_code, 200)
- r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=statchg.relateddocument_set.first().target.document.canonical_name())))
- self.assertEqual(r.status_code, 200)
+ r = self.client.get(urlreverse("ietf.doc.views_doc.document_main", kwargs=dict(name=statchg.relateddocument_set.first().target.document)))
+ self.assertEqual(r.status_code, 302)
def test_document_charter(self):
CharterFactory(name='charter-ietf-mars')
@@ -2381,7 +2366,6 @@ class ChartTests(ResourceTestCaseMixin, TestCase):
class FieldTests(TestCase):
def test_searchabledocumentsfield_pre(self):
- return # FIXME-LARS
# so far, just tests that the format expected by select2 set up
docs = IndividualDraftFactory.create_batch(3)
@@ -2391,8 +2375,7 @@ class FieldTests(TestCase):
form = _TestForm(initial=dict(test_field=docs))
html = str(form)
q = PyQuery(html)
- json_data = q('input.select2-field').attr('data-pre')
- print(json_data)
+ json_data = q('.select2-field').attr('data-pre')
try:
decoded = json.loads(json_data)
except json.JSONDecodeError as e:
@@ -2401,7 +2384,7 @@ class FieldTests(TestCase):
self.assertCountEqual(decoded_ids, [str(doc.id) for doc in docs])
for doc in docs:
self.assertEqual(
- dict(id=doc.pk, text=escape(uppercase_std_abbreviated_name(doc.name))),
+ dict(id=doc.pk, selected=True, text=escape(uppercase_std_abbreviated_name(doc.name))),
decoded[str(doc.pk)],
)
diff --git a/ietf/doc/tests_bofreq.py b/ietf/doc/tests_bofreq.py
index 495fbe18c..12935bd74 100644
--- a/ietf/doc/tests_bofreq.py
+++ b/ietf/doc/tests_bofreq.py
@@ -173,7 +173,7 @@ This test section has some text.
new_editors.discard(acting_editor)
new_editors.add(PersonFactory())
url = urlreverse('ietf.doc.views_bofreq.change_editors', kwargs=dict(name=doc.name))
- postdict = dict(editors=','.join([str(p.pk) for p in new_editors]))
+ postdict = dict(editors=[str(p.pk) for p in new_editors])
r = self.client.post(url, postdict)
self.assertEqual(r.status_code,302)
editors = bofreq_editors(doc)
@@ -196,7 +196,7 @@ This test section has some text.
new_editors = set(previous_editors)
new_editors.discard(acting_editor)
new_editors.add(PersonFactory())
- postdict = dict(editors=','.join([str(p.pk) for p in new_editors]))
+ postdict = dict(editors=[str(p.pk) for p in new_editors])
r = self.client.post(url,postdict)
self.assertEqual(r.status_code, 302)
updated_editors = bofreq_editors(doc)
@@ -213,7 +213,7 @@ This test section has some text.
new_responsible = set(previous_responsible[1:])
new_responsible.add(RoleFactory(group__type_id='area',name_id='ad').person)
url = urlreverse('ietf.doc.views_bofreq.change_responsible', kwargs=dict(name=doc.name))
- postdict = dict(responsible=','.join([str(p.pk) for p in new_responsible]))
+ postdict = dict(responsible=[str(p.pk) for p in new_responsible])
r = self.client.post(url, postdict)
self.assertEqual(r.status_code,302)
responsible = bofreq_responsible(doc)
@@ -235,7 +235,7 @@ This test section has some text.
self.assertIn(responsible.name,unescaped)
new_responsible = set(previous_responsible)
new_responsible.add(RoleFactory(group__type_id='area',name_id='ad').person)
- postdict = dict(responsible=','.join([str(p.pk) for p in new_responsible]))
+ postdict = dict(responsible=[str(p.pk) for p in new_responsible])
r = self.client.post(url,postdict)
self.assertEqual(r.status_code, 302)
updated_responsible = bofreq_responsible(doc)
@@ -256,11 +256,11 @@ This test section has some text.
pks = set()
pks.update([p.pk for p in good_batch])
pks.update([p.pk for p in bad_batch])
- postdict = dict(responsible=','.join([str(pk) for pk in pks]))
+ postdict = dict(responsible=[str(pk) for pk in pks])
r = self.client.post(url,postdict)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- error_text = q('.is-invalid .alert').text()
+ error_text = q('.invalid-feedback').text()
for p in good_batch:
self.assertNotIn(p.plain_name(), error_text)
for p in bad_batch:
diff --git a/ietf/doc/tests_downref.py b/ietf/doc/tests_downref.py
index 6b174fa7d..dae65cb07 100644
--- a/ietf/doc/tests_downref.py
+++ b/ietf/doc/tests_downref.py
@@ -48,7 +48,6 @@ class Downref(TestCase):
self.assertContains(r, 'Add downref')
def test_downref_registry_add(self):
- return # FIXME-LARS
url = urlreverse('ietf.doc.views_downref.downref_registry_add')
login_testing_unauthorized(self, "plain", url)
diff --git a/ietf/doc/tests_draft.py b/ietf/doc/tests_draft.py
index ef5f29a5f..88e90ebb5 100644
--- a/ietf/doc/tests_draft.py
+++ b/ietf/doc/tests_draft.py
@@ -1076,7 +1076,6 @@ class IndividualInfoFormsTests(TestCase):
self.assertEqual(doc.ad, pre_ad, 'Pre-AD was not actually assigned')
def test_doc_change_shepherd(self):
- return # FIXME-LARS
doc = Document.objects.get(name=self.docname)
doc.shepherd = None
doc.save_with_history([DocEvent.objects.create(doc=doc, rev=doc.rev, type="changed_shepherd", by=Person.objects.get(user__username="secretary"), desc="Test")])
@@ -1094,7 +1093,7 @@ class IndividualInfoFormsTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code,200)
q = PyQuery(r.content)
- self.assertEqual(len(q('form input[id=id_shepherd]')),1)
+ self.assertEqual(len(q('form select[id=id_shepherd]')),1)
# change the shepherd
plain_email = Email.objects.get(person__name="Plain Man")
@@ -1116,7 +1115,7 @@ class IndividualInfoFormsTests(TestCase):
self.assertTrue(any(['no changes have been made' in m.message for m in r.context['messages']]))
# Remove the shepherd
- r = self.client.post(url, dict(shepherd=''))
+ r = self.client.post(url, dict(shepherd=[]))
self.assertEqual(r.status_code, 302)
doc = Document.objects.get(name=self.docname)
self.assertTrue(any(['Document shepherd changed to (None)' in x.desc for x in doc.docevent_set.filter(time=doc.time,type='added_comment')]))
@@ -1294,10 +1293,9 @@ class IndividualInfoFormsTests(TestCase):
'Expected "Remove %s" button for' % role_name)
def _test_changing_ah(action_holders, reason):
- return # FIXME-LARS
r = self.client.post(url, dict(
reason=reason,
- action_holders=','.join([str(p.pk) for p in action_holders]),
+ action_holders=[str(p.pk) for p in action_holders],
))
self.assertEqual(r.status_code, 302)
doc = Document.objects.get(name=self.docname)
@@ -1322,7 +1320,6 @@ class IndividualInfoFormsTests(TestCase):
self.do_doc_change_action_holders_test('ad')
def do_doc_remind_action_holders_test(self, username):
- return # FIXME-LARS
doc = Document.objects.get(name=self.docname)
doc.action_holders.set(PersonFactory.create_batch(3))
@@ -1840,7 +1837,6 @@ class ChangeReplacesTests(TestCase):
def test_change_replaces(self):
- return # FIXME-LARS
url = urlreverse('ietf.doc.views_draft.replaces', kwargs=dict(name=self.replacea.name))
login_testing_unauthorized(self, "secretary", url)
@@ -1869,7 +1865,7 @@ class ChangeReplacesTests(TestCase):
# Post that says replaceboth replaces both base a and base b
url = urlreverse('ietf.doc.views_draft.replaces', kwargs=dict(name=self.replaceboth.name))
self.assertEqual(self.baseb.get_state().slug,'expired')
- r = self.client.post(url, dict(replaces='%s,%s' % (self.basea.pk, self.baseb.pk)))
+ r = self.client.post(url, dict(replaces=[self.basea.pk, self.baseb.pk]))
self.assertEqual(r.status_code, 302)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl')
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'repl')
@@ -1880,7 +1876,7 @@ class ChangeReplacesTests(TestCase):
# Post that undoes replaceboth
empty_outbox()
- r = self.client.post(url, dict(replaces=""))
+ r = self.client.post(url, dict(replaces=[]))
self.assertEqual(r.status_code, 302)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'repl') # Because A is still also replaced by replacea
self.assertEqual(Document.objects.get(name='draft-test-base-b').get_state().slug,'expired')
@@ -1892,7 +1888,7 @@ class ChangeReplacesTests(TestCase):
# Post that undoes replacea
empty_outbox()
url = urlreverse('ietf.doc.views_draft.replaces', kwargs=dict(name=self.replacea.name))
- r = self.client.post(url, dict(replaces=""))
+ r = self.client.post(url, dict(replaces=[]))
self.assertEqual(r.status_code, 302)
self.assertEqual(Document.objects.get(name='draft-test-base-a').get_state().slug,'active')
self.assertTrue('basea_author@' in outbox[-1]['To'])
@@ -1921,7 +1917,6 @@ class ChangeReplacesTests(TestCase):
class MoreReplacesTests(TestCase):
def test_stream_state_changes_when_replaced(self):
- return # FIXME-LARS
self.client.login(username='secretary',password='secretary+password')
for stream in ('iab','irtf','ise'):
old_doc = IndividualDraftFactory(stream_id=stream)
diff --git a/ietf/doc/tests_js.py b/ietf/doc/tests_js.py
index 4fbdb3695..9c8d0895f 100644
--- a/ietf/doc/tests_js.py
+++ b/ietf/doc/tests_js.py
@@ -30,17 +30,21 @@ class EditAuthorsTests(IetfSeleniumTestCase):
# To enter the person, type their name in the select2 search box, wait for the
# search to offer the result, then press 'enter' to accept the result and close
# the search input.
- person_span = form_elt.find_element(By.CLASS_NAME, 'select2-chosen')
+ # self.driver.set_page_load_timeout(60)
+ person_span = form_elt.find_element(By.CLASS_NAME, 'select2-selection')
self.scroll_to_element(person_span)
person_span.click()
- input = self.driver.switch_to.active_element
+ input = self.driver.find_element(By.CLASS_NAME, 'select2-search__field')
input.send_keys(name)
- result_selector = 'ul.select2-results > li > div.select2-result-label'
- self.wait.until(
- expected_conditions.text_to_be_present_in_element(
- (By.CSS_SELECTOR, result_selector),
- name
- ))
+ result_selector = 'ul.select2-results__options > li.select2-results__option--selectable'
+ try:
+ WebDriverWait(self.driver, 3).until(
+ expected_conditions.text_to_be_present_in_element(
+ (By.CSS_SELECTOR, result_selector),
+ name
+ ))
+ except:
+ print(name, email, self.driver.find_element(By.CSS_SELECTOR, ".select2-results__message").text)
input.send_keys('\n') # select the object
# After the author is selected, the email select options will be populated.
@@ -63,7 +67,7 @@ class EditAuthorsTests(IetfSeleniumTestCase):
Note: returns the Person instance named in the person field, not just their name.
"""
- hidden_person_input = form_elt.find_element(By.CSS_SELECTOR, 'input[type="text"][name$="person"]')
+ hidden_person_input = form_elt.find_element(By.CSS_SELECTOR, 'select[name$="person"]')
email_select = form_elt.find_element(By.CSS_SELECTOR, 'select[name$="email"]')
affil_input = form_elt.find_element(By.CSS_SELECTOR, 'input[name$="affiliation"]')
country_input = form_elt.find_element(By.CSS_SELECTOR, 'input[name$="country"]')
@@ -94,8 +98,9 @@ class EditAuthorsTests(IetfSeleniumTestCase):
# get the "add author" button so we can add blank author forms
add_author_button = self.driver.find_element(By.ID, 'add-author-button')
for index, auth in enumerate(authors):
- self.scroll_to_element(add_author_button) # Can only click if it's in view!
- add_author_button.click() # Create a new form. Automatically scrolls to it.
+ self.driver.execute_script("arguments[0].click();", add_author_button) # FIXME-LARS: no idea why this fails:
+ # self.scroll_to_element(add_author_button) # Can only click if it's in view!
+ # add_author_button.click() # Create a new form. Automatically scrolls to it.
author_forms = authors_list.find_elements(By.CLASS_NAME, 'author-panel')
authors_added = index + 1
self.assertEqual(len(author_forms), authors_added + 1) # Started with 1 author, hence +1
@@ -117,8 +122,9 @@ class EditAuthorsTests(IetfSeleniumTestCase):
self.driver.find_element(By.ID, 'id_basis').send_keys('change testing')
# Now click the 'submit' button and check that the update was accepted.
submit_button = self.driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]')
- self.scroll_to_element(submit_button)
- submit_button.click()
+ self.driver.execute_script("arguments[0].click();", submit_button) # FIXME-LARS: no idea why this fails:
+ # self.scroll_to_element(submit_button)
+ # submit_button.click()
# Wait for redirect to the document_main view
self.wait.until(
expected_conditions.url_to_be(
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
index 2e50b4e49..ee07b727c 100644
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -1479,7 +1479,7 @@ def edit_action_holders(request, name):
if request.method == 'POST':
form = ActionHoldersForm(request.POST)
- if form.is_valid() and 'action_holders' in request.POST:
+ if form.is_valid():
new_action_holders = form.cleaned_data['action_holders'] # Person queryset
prev_action_holders = list(doc.action_holders.all())
diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
index de073e257..8b810e63b 100644
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -346,7 +346,7 @@ class GroupPagesTests(TestCase):
for url in [group.about_url(),] + group_urlreverse_list(group, 'ietf.group.views.group_about'):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
-
+
for role in group.role_set.all():
self.assertContains(r, escape(role.person.plain_name()))
@@ -595,7 +595,7 @@ class GroupEditTests(TestCase):
self.assertEqual(len(q('form select[name=parent]')), 1)
self.assertEqual(len(q('form input[name=acronym]')), 1)
for role_slug in group.used_roles or group.features.default_used_roles:
- self.assertEqual(len(q('form input[name=%s_roles]'%role_slug)),1)
+ self.assertEqual(len(q('form select[name=%s_roles]'%role_slug)),1)
# faulty post
Group.objects.create(name="Collision Test Group", acronym="collide")
@@ -631,12 +631,12 @@ class GroupEditTests(TestCase):
ad=ad.pk,
state=state.pk,
ad_roles=ad.email().address,
- chair_roles="aread@example.org, ad1@example.org",
- secr_roles="aread@example.org, ad1@example.org, ad2@example.org",
- liaison_contact_roles="ad1@example.org",
- liaison_cc_contact_roles="aread@example.org, ad2@example.org",
- techadv_roles="aread@example.org",
- delegate_roles="ad2@example.org",
+ chair_roles=["aread@example.org", "ad1@example.org"],
+ secr_roles=["aread@example.org", "ad1@example.org", "ad2@example.org"],
+ liaison_contact_roles=["ad1@example.org"],
+ liaison_cc_contact_roles=["aread@example.org", "ad2@example.org"],
+ techadv_roles=["aread@example.org"],
+ delegate_roles=["ad2@example.org"],
list_email="mars@mail",
list_subscribe="subscribe.mars",
list_archive="archive.mars",
@@ -728,9 +728,9 @@ class GroupEditTests(TestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertEqual(len(q('div#content > form input[name=%s]' % field_name)), 1)
+ self.assertEqual(len(q('div#content > form input[name=%s], div#content > form select[name=%s]' % (field_name, field_name))), 1)
for prohibited_name in prohibited_form_names:
- self.assertEqual(len(q('div#content > form input[name=%s]' % prohibited_name)), 0)
+ self.assertEqual(len(q('div#content > form input[name=%s], div#content > form select[name=%s]' % (prohibited_name, prohibited_name))), 0)
# edit info
r = self.client.post(url, {field_name: field_content})
@@ -741,7 +741,7 @@ class GroupEditTests(TestCase):
if field_name.endswith('_roles'):
role_name = field_name[:-len('_roles')]
self.assertSetEqual(
- {fc.strip() for fc in field_content.split(',')},
+ {fc.strip() for fc in field_content},
set(group.role_set.filter(name=role_name).values_list('email', flat=True))
)
else:
@@ -755,8 +755,8 @@ class GroupEditTests(TestCase):
# Test various fields
_test_field(group, 'name', 'Mars Not Special Interest Group', ['acronym'])
_test_field(group, 'list_email', 'mars@mail', ['name'])
- _test_field(group, 'liaison_contact_roles', 'user@example.com, other_user@example.com', ['list_email'])
- _test_field(group, 'liaison_cc_contact_roles', 'user@example.com, other_user@example.com', ['liaison_contact'])
+ _test_field(group, 'liaison_contact_roles', ['user@example.com', 'other_user@example.com'], ['list_email'])
+ _test_field(group, 'liaison_cc_contact_roles', ['user@example.com', 'other_user@example.com'], ['liaison_contact'])
def test_edit_reviewers(self):
group=GroupFactory(type_id='review',parent=GroupFactory(type_id='area'))
@@ -779,7 +779,7 @@ class GroupEditTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertEqual(len(q('form input[name=reviewer_roles]')), 1)
+ self.assertEqual(len(q('form select[name=reviewer_roles]')), 1)
# set reviewers
empty_outbox()
@@ -935,16 +935,15 @@ class GroupFormTests(TestCase):
)
# fill in original values
for rslug in group.get_used_roles():
- data['{}_roles'.format(rslug)] = ','.join(
- group.role_set.filter(name_id=rslug).values_list('email__address', flat=True),
- )
+ data['{}_roles'.format(rslug)] = list(group.role_set.filter(name_id=rslug).values_list('email__address', flat=True).all())
+
return data
def _assert_cleaned_data_equal(self, cleaned_data, post_data):
for attr, expected in post_data.items():
value = cleaned_data[attr]
if attr.endswith('_roles'):
- actual = ','.join(value.values_list('address', flat=True))
+ actual = list(value.values_list('address', flat=True).all())
elif attr == 'resources':
# must handle resources specially
actual = '\n'.join(self._format_resource(r) for r in value)
@@ -966,7 +965,7 @@ class GroupFormTests(TestCase):
for rslug in group.get_used_roles():
data = orig_data.copy()
edit_field = '{}_roles'.format(rslug)
- data[edit_field] = new_email.address # comma-separated list of addresses with only one
+ data[edit_field] = [new_email.address]
form = GroupForm(data, group=group, group_type=group.type_id, field=None)
@@ -1061,7 +1060,6 @@ class MilestoneTests(TestCase):
resolved="",
state_id="charter")
m2.docs.set([draft])
-
return (m1, m2, group)
def last_day_of_month(self, d):
@@ -1110,7 +1108,7 @@ class MilestoneTests(TestCase):
'm-1-desc': "", # no description
'm-1-due': due.strftime("%B %Y"),
'm-1-resolved': "",
- 'm-1-docs': ",".join(doc_pks),
+ 'm-1-docs': doc_pks,
'action': "save",
})
self.assertEqual(r.status_code, 200)
@@ -1125,7 +1123,7 @@ class MilestoneTests(TestCase):
'm-1-desc': "Test 3",
'm-1-due': due.strftime("%B %Y"),
'm-1-resolved': "",
- 'm-1-docs': ",".join(doc_pks),
+ 'm-1-docs': doc_pks,
'action': "save",
})
self.assertEqual(r.status_code, 302)
@@ -1201,7 +1199,7 @@ class MilestoneTests(TestCase):
'm1-desc': m1.desc,
'm1-due': m1.due.strftime("%B %Y"),
'm1-resolved': m1.resolved,
- 'm1-docs': ",".join(pklist(m1.docs)),
+ 'm1-docs': pklist(m1.docs),
'm1-review': "accept",
'action': "save",
})
@@ -1227,7 +1225,7 @@ class MilestoneTests(TestCase):
'm1-desc': m1.desc,
'm1-due': m1.due.strftime("%B %Y"),
'm1-resolved': "",
- 'm1-docs': ",".join(pklist(m1.docs)),
+ 'm1-docs': pklist(m1.docs),
'm1-delete': "checked",
'action': "save",
})
@@ -1257,7 +1255,7 @@ class MilestoneTests(TestCase):
'm1-desc': "", # no description
'm1-due': due.strftime("%B %Y"),
'm1-resolved': "",
- 'm1-docs': ",".join(doc_pks),
+ 'm1-docs': doc_pks,
'action': "save",
})
self.assertEqual(r.status_code, 200)
@@ -1275,7 +1273,7 @@ class MilestoneTests(TestCase):
'm1-due': due.strftime("%B %Y"),
'm1-resolved': "Done",
'm1-resolved_checkbox': "checked",
- 'm1-docs': ",".join(doc_pks),
+ 'm1-docs': doc_pks,
'action': "save",
})
self.assertEqual(r.status_code, 302)
@@ -1343,7 +1341,7 @@ class DatelessMilestoneTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertEqual(len(q('#switch-date-use-form')),0)
+ self.assertEqual(len(q('button[value="switch"]:submit')),0)
r = self.client.post(url, dict(action="switch"))
self.assertEqual(r.status_code, 403)
@@ -1447,7 +1445,7 @@ class DatelessMilestoneTests(TestCase):
post_data['%s-id' % prefix] = ms.id
post_data['%s-desc' % prefix] = ms.desc
post_data['%s-order' % prefix] = ms.order
- post_data['%s-docs' % prefix] = ""
+ post_data['%s-docs' % prefix] = []
post_data['prefix'] = prefixes
post_data['action'] = 'review'
@@ -1461,7 +1459,7 @@ class DatelessMilestoneTests(TestCase):
r = self.client.post(url, post_data)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertEqual(len(q('.label:contains("Changed")')), 2)
+ self.assertEqual(len(q('span.badge:contains("Changed")')), 2)
post_data['action'] = 'save'
r = self.client.post(url, post_data)
diff --git a/ietf/group/tests_js.py b/ietf/group/tests_js.py
index b10bc5c34..c18c47e15 100644
--- a/ietf/group/tests_js.py
+++ b/ietf/group/tests_js.py
@@ -28,7 +28,7 @@ class MilestoneTests(IetfSeleniumTestCase):
"""Search for a draft and get the search result element"""
draft_input.send_keys(search_string)
- result_selector = 'ul.select2-results > li > div.select2-result-label'
+ result_selector = '.select2-results > ul > li'
self.wait.until(
expected_conditions.text_to_be_present_in_element(
(By.CSS_SELECTOR, result_selector),
@@ -91,10 +91,11 @@ class MilestoneTests(IetfSeleniumTestCase):
desc_input = edit_div.find_element(By.CSS_SELECTOR, 'input[id$="_desc"]')
due_input = edit_div.find_element(By.CSS_SELECTOR, 'input[id$="_due"]')
- draft_input = edit_div.find_element(By.CSS_SELECTOR,
- 'div.select2-container[id$="id_docs"] input.select2-input'
- )
-
+ draft_input = self.wait.until(
+ expected_conditions.visibility_of_element_located(
+ (By.CSS_SELECTOR, '.select2-container textarea[aria-describedby*="_docs"]')
+ ))
+
# fill in the edit milestone form
desc_input.send_keys(description)
due_input.send_keys(due_date.strftime('%m %Y\n')) # \n closes the date selector
@@ -151,9 +152,7 @@ class MilestoneTests(IetfSeleniumTestCase):
due_field = self.driver.find_element(By.ID, prefix + 'due')
hidden_drafts_field = self.driver.find_element(By.ID, prefix + 'docs')
- draft_input = self.driver.find_element(By.CSS_SELECTOR,
- 'div.select2-container[id*="%s"] input.select2-input' % prefix
- )
+ draft_input = self.driver.find_element(By.CSS_SELECTOR, 'textarea[aria-describedby*="%sdocs"]' % prefix)
self.assertEqual(due_field.get_attribute('value'), milestone.due.strftime('%B %Y'))
self.assertEqual(hidden_drafts_field.get_attribute('value'),
','.join([str(doc.pk) for doc in milestone.docs.all()]))
diff --git a/ietf/ipr/tests.py b/ietf/ipr/tests.py
index ddc3598d2..a2bbd3bcb 100644
--- a/ietf/ipr/tests.py
+++ b/ietf/ipr/tests.py
@@ -253,7 +253,7 @@ class IprTests(TestCase):
"ietfer_contact_info": "555-555-0101",
"iprdocrel_set-TOTAL_FORMS": 2,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": "%s" % draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.docalias.first().pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
"patent_number": "SE12345678901",
@@ -309,7 +309,7 @@ class IprTests(TestCase):
"ietfer_contact_info": "555-555-0101",
"iprdocrel_set-TOTAL_FORMS": 2,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": "%s" % draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.docalias.first().pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
"patent_number": "SE12345678901",
@@ -356,7 +356,7 @@ class IprTests(TestCase):
"holder_legal_name": "Test Legal",
"ietfer_contact_info": "555-555-0101",
"ietfer_name": "Test Participant",
- "iprdocrel_set-0-document": "%s" % draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.docalias.first().pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-INITIAL_FORMS": 0,
"iprdocrel_set-TOTAL_FORMS": 1,
@@ -367,10 +367,9 @@ class IprTests(TestCase):
"patent_title": "A method of transfering bits",
"submitter_email": "test@holder.com",
"submitter_name": "Test Holder",
- "updates": "",
+ "updates": [],
}
r = self.client.post(url, post_data, follow=True)
- print(r)
self.assertContains(r, "Disclosure modified")
iprs = IprDisclosureBase.objects.filter(title__icontains=draft.name)
@@ -397,7 +396,7 @@ class IprTests(TestCase):
# successful post
empty_outbox()
r = self.client.post(url, {
- "updates": str(original_ipr.pk),
+ "updates": [original_ipr.pk],
"holder_legal_name": "Test Legal",
"holder_contact_name": "Test Holder",
"holder_contact_email": "test@holder.com",
@@ -406,7 +405,7 @@ class IprTests(TestCase):
"ietfer_contact_info": "555-555-0101",
"iprdocrel_set-TOTAL_FORMS": 2,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": "%s" % draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.docalias.first().pk,
"iprdocrel_set-0-revisions": '00',
"iprdocrel_set-1-document": DocAlias.objects.filter(name__startswith="rfc").first().pk,
"patent_number": "SE12345678901",
@@ -437,13 +436,13 @@ class IprTests(TestCase):
empty_outbox()
r = self.client.post(url, {
- "updates": "this is supposed to be an integer",
+ "updates": "this is supposed to be an array of integers",
"holder_legal_name": "Test Legal",
"holder_contact_name": "Test Holder",
"holder_contact_email": "test@holder.com",
"iprdocrel_set-TOTAL_FORMS": 1,
"iprdocrel_set-INITIAL_FORMS": 0,
- "iprdocrel_set-0-document": "%s" % draft.docalias.first().pk,
+ "iprdocrel_set-0-document": draft.docalias.first().pk,
"iprdocrel_set-0-revisions": '00',
"patent_number": "SE12345678901",
"patent_inventor": "A. Nonymous",
@@ -456,7 +455,6 @@ class IprTests(TestCase):
})
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- # print(r.content)
self.assertTrue(q("#id_updates").parents(".row").hasClass("is-invalid"))
def test_addcomment(self):
@@ -609,7 +607,6 @@ I would like to revoke this declaration.
response_due=yesterday.isoformat())
empty_outbox()
r = self.client.post(url,data,follow=True)
- #print r.content
self.assertEqual(r.status_code,200)
q = Message.objects.filter(reply_to=data['reply_to'])
self.assertEqual(q.count(),1)
@@ -665,7 +662,7 @@ Subject: test
self.assertEqual(response.status_code,200)
post_data = {
'iprdocrel_set-TOTAL_FORMS' : 1,
- 'iprdocrel_set-INITIAL_FORMS' : 1,
+ 'iprdocrel_set-INITIAL_FORMS' : 0,
'iprdocrel_set-0-id': disclosure.pk,
"iprdocrel_set-0-document": disclosure.docs.first().pk,
"iprdocrel_set-0-revisions": disclosure.docs.first().document.rev,
diff --git a/ietf/liaisons/tests.py b/ietf/liaisons/tests.py
index d9abfc321..b5b816aa5 100644
--- a/ietf/liaisons/tests.py
+++ b/ietf/liaisons/tests.py
@@ -1012,7 +1012,8 @@ class LiaisonManagementTests(TestCase):
reply_from_group_id = str(liaison.to_groups.first().pk)
self.assertEqual(q('#id_from_groups').find('option:selected').val(),reply_from_group_id)
self.assertEqual(q('#id_to_groups').find('option:selected').val(),reply_to_group_id)
- self.assertEqual(q('#id_related_to').val(),str(liaison.pk))
+ # FIXME-LARS need to check inside "data-pre" attribute
+ # self.assertEqual(q('#id_related_to').val(),str(liaison.pk))
def test_search(self):
# Statement 1
@@ -1159,4 +1160,4 @@ class LiaisonManagementTests(TestCase):
mailbox_before = len(outbox)
possibly_send_deadline_reminder(liaison)
- self.assertEqual(len(outbox), mailbox_before)
+ self.assertEqual(len(outbox), mailbox_before)
\ No newline at end of file
diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py
index c5159c5af..4511bf920 100644
--- a/ietf/meeting/tests_js.py
+++ b/ietf/meeting/tests_js.py
@@ -43,6 +43,7 @@ if selenium_enabled():
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.common.exceptions import NoSuchElementException, TimeoutException
+ # from selenium.webdriver.common.keys import Keys
@ifSeleniumEnabled
@@ -585,7 +586,8 @@ class EditMeetingScheduleTests(IetfSeleniumTestCase):
# option to swap. If we used the first or last day, a fencepost error in
# disabling options by date might be hidden.
clicked_index = 1
- future_swap_ts_buttons[clicked_index].click()
+ self.driver.execute_script("arguments[0].click();", future_swap_ts_buttons[clicked_index]) # FIXME-LARS: not working:
+ # future_swap_ts_buttons[clicked_index].click()
try:
modal = wait.until(
expected_conditions.visibility_of_element_located(
@@ -998,7 +1000,7 @@ class AgendaTests(IetfSeleniumTestCase):
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + querystring)
self.assert_agenda_item_visibility(visible_groups)
self.assert_agenda_view_filter_matches_ics_filter(querystring)
- weekview_iframe = self.driver.find_element(By.ID, 'weekview')
+ weekview_iframe = self.driver.find_element(By.CSS_SELECTOR, '#weekview iframe')
if len(querystring) == 0:
self.assertFalse(weekview_iframe.is_displayed(), 'Weekview should be hidden when filters off')
else:
@@ -1215,7 +1217,7 @@ class AgendaTests(IetfSeleniumTestCase):
"""Click the 'customize' anchor to reveal the group buttons"""
customize_anchor = wait.until(
expected_conditions.element_to_be_clickable(
- (By.CSS_SELECTOR, '#accordion a[data-bs-toggle="collapse"]')
+ (By.CSS_SELECTOR, '#accordion button[data-bs-toggle="collapse"]')
)
)
customize_anchor.click()
@@ -1390,7 +1392,7 @@ class AgendaTests(IetfSeleniumTestCase):
# Click the 'customize' anchor to reveal the group buttons
customize_anchor = WebDriverWait(self.driver, 2).until(
expected_conditions.element_to_be_clickable(
- (By.CSS_SELECTOR, '#accordion a[data-bs-toggle="collapse"]')
+ (By.CSS_SELECTOR, '#accordion button[data-bs-toggle="collapse"]')
)
)
customize_anchor.click()
@@ -1612,13 +1614,11 @@ class AgendaTests(IetfSeleniumTestCase):
)
tz_select_input = self.driver.find_element(By.ID, 'timezone-select')
- meeting_tz_link = self.driver.find_element(By.ID, 'meeting-timezone')
- local_tz_link = self.driver.find_element(By.ID, 'local-timezone')
- utc_tz_link = self.driver.find_element(By.ID, 'utc-timezone')
- tz_displays = self.driver.find_elements(By.CSS_SELECTOR, '.current-tz')
- self.assertGreaterEqual(len(tz_displays), 1)
- # we'll check that all current-tz elements are updated, but first check that at least one is in the nav sidebar
- self.assertIsNotNone(self.driver.find_element(By.CSS_SELECTOR, '.nav .current-tz'))
+ meeting_tz_link = self.driver.find_element(By.CSS_SELECTOR, 'label[for="meeting-timezone"]')
+ local_tz_link = self.driver.find_element(By.CSS_SELECTOR, 'label[for="local-timezone"]')
+ utc_tz_link = self.driver.find_element(By.CSS_SELECTOR, 'label[for="utc-timezone"]')
+ # we'll check that all tz-select elements are updated, but first check that at least one is in the nav sidebar
+ self.assertIsNotNone(self.driver.find_element(By.CSS_SELECTOR, '.tz-select'))
# Moment.js guesses local time zone based on the behavior of Selenium's web client. This seems
# to inherit Django's settings.TIME_ZONE but I don't know whether that's guaranteed to be consistent.
@@ -1636,8 +1636,7 @@ class AgendaTests(IetfSeleniumTestCase):
# don't yet know local_tz, so can't check that it's deselected here
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
- for disp in tz_displays:
- self.assertEqual(disp.text.strip(), self.meeting.time_zone)
+ self.assertEqual(self.driver.find_element(By.CSS_SELECTOR, '.tz-select option:checked').text.strip(), self.meeting.time_zone)
# Click 'local' button
local_tz_link.click()
@@ -1646,8 +1645,7 @@ class AgendaTests(IetfSeleniumTestCase):
# just identified the local_tz_opt as being selected, so no check here, either
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
- for disp in tz_displays:
- self.assertEqual(disp.text.strip(), local_tz)
+ self.assertEqual(self.driver.find_element(By.CSS_SELECTOR, '.tz-select option:checked').text.strip(), local_tz)
# click 'utc' button
utc_tz_link.click()
@@ -1656,8 +1654,7 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertFalse(local_tz_opt.is_selected()) # finally!
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertTrue(utc_tz_opt.is_selected())
- for disp in tz_displays:
- self.assertEqual(disp.text.strip(), 'UTC')
+ self.assertEqual(self.driver.find_element(By.CSS_SELECTOR, '.tz-select option:checked').text.strip(), 'UTC')
# click back to meeting
meeting_tz_link.click()
@@ -1666,8 +1663,7 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
- for disp in tz_displays:
- self.assertEqual(disp.text.strip(), self.meeting.time_zone)
+ self.assertEqual(self.driver.find_element(By.CSS_SELECTOR, '.tz-select option:checked').text.strip(), self.meeting.time_zone)
# and then back to UTC...
utc_tz_link.click()
@@ -1676,8 +1672,7 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertTrue(utc_tz_opt.is_selected())
- for disp in tz_displays:
- self.assertEqual(disp.text.strip(), 'UTC')
+ self.assertEqual(self.driver.find_element(By.CSS_SELECTOR, '.tz-select option:checked').text.strip(), 'UTC')
# ... and test the switch from UTC to local
local_tz_link.click()
@@ -1686,8 +1681,7 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertTrue(local_tz_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
- for disp in tz_displays:
- self.assertEqual(disp.text.strip(), local_tz)
+ self.assertEqual(self.driver.find_element(By.CSS_SELECTOR, '.tz-select option:checked').text.strip(), local_tz)
# Now select a different item from the select input
arbitrary_tz_opt.click()
@@ -1696,8 +1690,7 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertFalse(local_tz_opt.is_selected())
self.assertTrue(arbitrary_tz_opt.is_selected())
self.assertFalse(utc_tz_opt.is_selected())
- for disp in tz_displays:
- self.assertEqual(disp.text.strip(), arbitrary_tz)
+ self.assertEqual(self.driver.find_element(By.CSS_SELECTOR, '.tz-select option:checked').text.strip(), arbitrary_tz)
def test_agenda_time_zone_selection_updates_weekview(self):
"""Changing the time zone should update the weekview to match"""
@@ -1727,7 +1720,7 @@ class AgendaTests(IetfSeleniumTestCase):
# Now select a different item from the select input
option.click()
try:
- wait.until(in_iframe_href('tz=america/halifax', 'weekview'))
+ wait.until(in_iframe_href('tz=america/halifax', self.driver.find_element(By.CSS_SELECTOR, '#weekview iframe')))
except:
self.fail('iframe href not updated to contain selected time zone')
@@ -1763,8 +1756,8 @@ class AgendaTests(IetfSeleniumTestCase):
farfut_button = self.driver.find_element(By.CSS_SELECTOR, 'button[data-filter-item="farfut"]')
break_checkbox = self.driver.find_element(By.CSS_SELECTOR, 'input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessb"]')
registration_checkbox = self.driver.find_element(By.CSS_SELECTOR, 'input[type="checkbox"][name="selected-sessions"][data-filter-item="secretariat-sessa"]')
+ self.driver.execute_script("arguments[0].click();", mars_sessa_checkbox) # select mars session; FIXME: no idea why a simple mars_sessa_checkbox.click() doesn't work
- mars_sessa_checkbox.click() # select mars session
try:
wait.until(
lambda driver: all('?show=mars-sessa' in el.get_attribute('href') for el in elements_to_check)
@@ -1776,7 +1769,7 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertFalse(break_checkbox.is_selected(), 'break checkbox was selected without being clicked')
self.assertFalse(registration_checkbox.is_selected(), 'registration checkbox was selected without being clicked')
- mars_sessa_checkbox.click() # deselect mars session
+ self.driver.execute_script("arguments[0].click();", mars_sessa_checkbox) # deselect mars session
try:
wait.until(
lambda driver: not any('?show=mars-sessa' in el.get_attribute('href') for el in elements_to_check)
@@ -1789,8 +1782,8 @@ class AgendaTests(IetfSeleniumTestCase):
self.assertFalse(registration_checkbox.is_selected(), 'registration checkbox was selected without being clicked')
farfut_button.click() # turn on all farfut area sessions
- mars_sessa_checkbox.click() # but turn off mars session a
- break_checkbox.click() # also select the break
+ self.driver.execute_script("arguments[0].click();", mars_sessa_checkbox) # but turn off mars session a
+ self.driver.execute_script("arguments[0].click();", break_checkbox) # also select the break
try:
wait.until(
@@ -2101,8 +2094,9 @@ class InterimTests(IetfSeleniumTestCase):
button = self.wait.until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, 'div#calendar button.fc-next-button')))
- self.scroll_to_element(button)
- button.click()
+ self.driver.execute_script("arguments[0].click();", button) # FIXME-LARS: no idea why this fails:
+ # self.scroll_to_element(button)
+ # button.click()
seen = set()
not_visible = set()
@@ -2116,7 +2110,7 @@ class InterimTests(IetfSeleniumTestCase):
# will usually contain the day 1 year from the start date.
for _ in range(13):
entries = self.driver.find_elements(By.CSS_SELECTOR,
- 'div#calendar div.fc-content'
+ 'div#calendar div.fc-event-main'
)
for entry in entries:
meetings = [m for m in visible_meetings if m.calendar_label in entry.text]
@@ -2447,7 +2441,7 @@ class InterimTests(IetfSeleniumTestCase):
_assert_ietf_tz_correct(ietf_meetings, arbitrary_tz)
def test_upcoming_materials_modal(self):
- """Test opening and closing a materals modal
+ """Test opening and closing a materials modal
This does not test dynamic reloading of the meeting materials - it relies on the main
agenda page testing that. If the materials modal handling diverges between here and
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
index ea463308a..77337af37 100644
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -406,7 +406,7 @@ class MeetingTests(BaseMeetingTestCase):
url = urlreverse("ietf.meeting.views.week_view",kwargs=dict(num=meeting.number)) + "?show=farfut"
r = self.client.get(url)
self.assertEqual(r.status_code,200)
- self.assertTrue(all([x in unicontent(r) for x in ['var all_items', 'maximize', 'draw_calendar', ]]))
+ self.assertTrue(all([x in unicontent(r) for x in ['redraw_weekview', 'draw_calendar', ]]))
# Specifying a time zone should not change the output (time zones are handled by the JS)
url = urlreverse("ietf.meeting.views.week_view",kwargs=dict(num=meeting.number)) + "?show=farfut&tz=Asia/Bangkok"
@@ -456,11 +456,11 @@ class MeetingTests(BaseMeetingTestCase):
nav_tab_anchors = q('ul.nav.nav-tabs > li > a')
for anchor in nav_tab_anchors.items():
text = anchor.text().strip()
- if text in ['Agenda', 'UTC Agenda', 'Select Sessions']:
+ if text in ['Agenda', 'UTC Agenda', 'Personalize Agenda']:
expected_elements.append(anchor)
for btn in q('.buttonlist a.btn').items():
text = btn.text().strip()
- if text in ['View customized agenda', 'Download as .ics', 'Subscribe with webcal']:
+ if text in ['View personal agenda', 'Download .ics of personal agenda', 'Subscribe to personal agenda']:
expected_elements.append(btn)
# Check that all the expected elements have the correct classes
@@ -718,7 +718,7 @@ class MeetingTests(BaseMeetingTestCase):
self.assertIn('%s?show=%s' % (ical_url, g.parent.acronym.lower()), content)
# The 'non-area events' are those whose keywords are in the last column of buttons
- na_col = q('#customize td.view:last-child') # find the column
+ na_col = q('#customize .col-1:last') # find the column
non_area_labels = [e.attrib['data-filter-item']
for e in na_col.find('button.pickview')]
assert len(non_area_labels) > 0 # test setup must produce at least one label for this test
@@ -3084,24 +3084,24 @@ class EditTests(TestCase):
r, q = _set_date_offset_and_retrieve_page(meeting,
0 - 2 - meeting.days, # Meeting ended 2 days ago
self.client)
- self.assertTrue(q("""em:contains("You can't edit this schedule")"""))
- self.assertTrue(q("""em:contains("This is the official schedule for a meeting in the past")"""))
+ self.assertTrue(q(""".alert:contains("You can't edit this schedule")"""))
+ self.assertTrue(q(""".alert:contains("This is the official schedule for a meeting in the past")"""))
# 2) An ongoing meeting
#######################################################
r, q = _set_date_offset_and_retrieve_page(meeting,
0, # Meeting starts today
self.client)
- self.assertFalse(q("""em:contains("You can't edit this schedule")"""))
- self.assertFalse(q("""em:contains("This is the official schedule for a meeting in the past")"""))
+ self.assertFalse(q(""".alert:contains("You can't edit this schedule")"""))
+ self.assertFalse(q(""".alert:contains("This is the official schedule for a meeting in the past")"""))
# 3) A meeting in the future
#######################################################
r, q = _set_date_offset_and_retrieve_page(meeting,
7, # Meeting starts next week
self.client)
- self.assertFalse(q("""em:contains("You can't edit this schedule")"""))
- self.assertFalse(q("""em:contains("This is the official schedule for a meeting in the past")"""))
+ self.assertFalse(q(""".alert:contains("You can't edit this schedule")"""))
+ self.assertFalse(q(""".alert:contains("This is the official schedule for a meeting in the past")"""))
def test_edit_meeting_schedule(self):
meeting = make_meeting_test_data()
@@ -3203,7 +3203,7 @@ class EditTests(TestCase):
self.assertEqual(len(q("#session{}.readonly".format(base_session.pk))), 1)
- self.assertTrue(q("em:contains(\"You can't edit this schedule\")"))
+ self.assertTrue(q(".alert:contains(\"You can't edit this schedule\")"))
# can't change anything
r = self.client.post(url, {
@@ -3845,7 +3845,7 @@ class SessionDetailsTests(TestCase):
r = self.client.post(url,dict(drafts=[new_draft.pk, old_draft.pk]))
self.assertTrue(r.status_code, 200)
q = PyQuery(r.content)
- self.assertIn("Already linked:", q('form .alert-danger').text())
+ self.assertIn("Already linked:", q('form .text-danger').text())
self.assertEqual(1,session.sessionpresentation_set.count())
r = self.client.post(url,dict(drafts=[new_draft.pk,]))
@@ -6164,12 +6164,12 @@ class AgendaFilterTests(TestCase):
# Test with/without custom button text
context = Context({'customize_button_text': None, 'filter_categories': []})
q = PyQuery(template.render(context))
- self.assertIn('Customize...', q('h4.card-title').text())
+ self.assertIn('Customize...', q('h2.accordion-header').text())
self.assertEqual(q('table'), []) # no filter_categories, so no button table
context['customize_button_text'] = 'My custom text...'
q = PyQuery(template.render(context))
- self.assertIn(context['customize_button_text'], q('h4.card-title').text())
+ self.assertIn(context['customize_button_text'], q('h2.accordion-header').text())
self.assertEqual(q('table'), []) # no filter_categories, so no button table
# Now add a non-trivial set of filters
@@ -6251,24 +6251,24 @@ class AgendaFilterTests(TestCase):
]
q = PyQuery(template.render(context))
- self.assertIn(context['customize_button_text'], q('h4.card-title').text())
- self.assertNotEqual(q('table'), []) # should now have table
+ self.assertIn(context['customize_button_text'], q('h2.accordion-header').text())
+ self.assertNotEqual(q('button.pickview'), []) # should now have group buttons
# Check that buttons are present for the expected things
- header_row = q('thead tr')
- self.assertEqual(len(header_row), 1)
- button_row = q('tbody tr')
- self.assertEqual(len(button_row), 1)
+ header_row = q('.col-1 .row:first')
+ self.assertEqual(len(header_row), 4)
+ button_row = q('.row.view')
+ self.assertEqual(len(button_row), 4)
# verify correct headers
- header_cells = header_row('th')
- self.assertEqual(len(header_cells), 6) # 4 columns and 2 spacers
+ header_cells = header_row('.row')
+ self.assertEqual(len(header_cells), 4)
header_buttons = header_cells('button.pickview')
self.assertEqual(len(header_buttons), 3) # last column has blank header, so only 3
# verify buttons
- button_cells = button_row('td')
-
+ button_cells = button_row('.btn-group-vertical')
+
# area0
_assert_button_ok(header_cells.eq(0)('button.keyword0'),
expected_label='area0',
@@ -6301,12 +6301,11 @@ class AgendaFilterTests(TestCase):
expected_filter_keywords='keyword1,bof')
# area2
- # Skip column index 2, which is a spacer column
- _assert_button_ok(header_cells.eq(3)('button.keyword2'),
+ _assert_button_ok(header_cells.eq(2)('button.keyword2'),
expected_label='area2',
expected_filter_item='keyword2')
- buttons = button_cells.eq(3)('button.pickview')
+ buttons = button_cells.eq(2)('button.pickview')
self.assertEqual(len(buttons), 2) # two children
_assert_button_ok(buttons('.keyword20'),
expected_label='child20',
@@ -6318,9 +6317,8 @@ class AgendaFilterTests(TestCase):
expected_filter_keywords='keyword2')
# area3 (no label for this one)
- # Skip column index 4, which is a spacer column
- self.assertEqual([], header_cells.eq(5)('button')) # no header button
- buttons = button_cells.eq(5)('button.pickview')
+ self.assertEqual([], header_cells.eq(3)('button')) # no header button
+ buttons = button_cells.eq(3)('button.pickview')
self.assertEqual(len(buttons), 2) # two children
_assert_button_ok(buttons('.keyword30'),
expected_label='child30',
diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py
index c3ed47c7c..ca5c4469a 100644
--- a/ietf/nomcom/tests.py
+++ b/ietf/nomcom/tests.py
@@ -356,9 +356,7 @@ class NomcomViewsTest(TestCase):
q = PyQuery(response.content)
self.assertTrue(q("form .is-invalid"))
- test_data = {"secondary_emails": """%s,
- %s,
- %s""" % (nominees[1], nominees[2], nominees[3]),
+ test_data = {"secondary_emails": [nominees[1], nominees[2], nominees[3]],
"primary_email": nominees[0]}
response = self.client.post(self.private_merge_nominee_url, test_data)
@@ -409,7 +407,7 @@ class NomcomViewsTest(TestCase):
self.client.logout()
def change_members(self, members):
- members_emails = ','.join(['%s%s' % (member, EMAIL_DOMAIN) for member in members])
+ members_emails = ['%s%s' % (member, EMAIL_DOMAIN) for member in members]
test_data = {'members': members_emails,}
self.client.post(self.edit_members_url, test_data)
@@ -2539,7 +2537,7 @@ class VolunteerTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertEqual(len(q('#id_nomcoms div.checkbox')), 2)
+ self.assertEqual(len(q('#id_nomcoms input[type="checkbox"]')), 2)
r = self.client.post(url, dict(nomcoms=[nomcom.pk, nomcom2.pk], affiliation='something'))
self.assertRedirects(r, reverse('ietf.ietfauth.views.profile'))
self.assertEqual(person.volunteer_set.count(), 2)
@@ -2558,7 +2556,7 @@ class VolunteerTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertEqual(len(q('#id_nomcoms div.checkbox')), 1)
+ self.assertEqual(len(q('#id_nomcoms input[type="checkbox"]')), 1)
self.assertNotIn(f'{nomcom.year()}/', q('#already-volunteered').text())
self.assertIn(f'{nomcom2.year()}/', q('#already-volunteered').text())
diff --git a/ietf/person/templatetags/person_filters.py b/ietf/person/templatetags/person_filters.py
index 9ed5527cc..29762bada 100644
--- a/ietf/person/templatetags/person_filters.py
+++ b/ietf/person/templatetags/person_filters.py
@@ -35,10 +35,13 @@ def person_by_name(name):
def person_link(person, **kwargs):
title = kwargs.get('title', '')
cls = kwargs.get('class', '')
- name = person.name if person.alias_set.filter(name=person.name).exists() else ''
- plain_name = person.plain_name()
- email = person.email_address()
- return {'name': name, 'plain_name': plain_name, 'email': email, 'title': title, 'class': cls}
+ if person:
+ name = person.name if person.alias_set.filter(name=person.name).exists() else ''
+ plain_name = person.plain_name()
+ email = person.email_address()
+ return {'name': name, 'plain_name': plain_name, 'email': email, 'title': title, 'class': cls}
+ else:
+ return {}
@register.inclusion_tag('person/person_link.html')
diff --git a/ietf/secr/static/js/utils.js b/ietf/secr/static/js/utils.js
index 3ffdb6344..c1380b8c8 100644
--- a/ietf/secr/static/js/utils.js
+++ b/ietf/secr/static/js/utils.js
@@ -8,7 +8,7 @@ function getCookie(name) {
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
- var cookie = jQuery.trim(cookies[i]);
+ var cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
@@ -104,7 +104,7 @@ function change_material_type(obj) {
function init_proceedings_upload() {
// dynamic help message
- $('#id_material_type').change(function() {
+ $('#id_material_type').on("change", function() {
if(this.value == "slides") {
//alert('Presentation handler called');
$('div#id_file_help').html("Note 1: You can only upload a presentation file in txt, pdf, doc, or ppt/pptx. System will not accept presentation files in any other format.
Note 2: All uploaded files will be available to the public immediately on the Preliminary Page. However, for the Proceedings, ppt/pptx files will be converted to html format and doc files will be converted to pdf format manually by the Secretariat staff.");
@@ -168,21 +168,21 @@ function init_proceedings_table() {
$(document).ready(function() {
// set focus --------------------------------
if ( $("form[id^=group-role-assignment-form]").length > 0) {
- $("#id_role_type").focus();
+ $("#id_role_type").trigger("focus");
} else if ( $("form[id=draft-search-form]").length > 0) {
- $("#id_filename").focus();
+ $("#id_filename").trigger("focus");
} else if ( $("form[id=drafts-add-form]").length > 0) {
- $("#id_title").focus();
+ $("#id_title").trigger("focus");
} else if ( $("form[id=proceedings-add-form]").length > 0) {
- $("#id_start_date").focus();
+ $("#id_start_date").trigger("focus");
} else if ( $("form[id=proceedings-upload-form]").length > 0) {
- $("#id_group_name").focus();
+ $("#id_group_name").trigger("focus");
} else if ( $("form[id=session-request-form]").length > 0) {
- $("#id_num_session").focus();
+ $("#id_num_session").trigger("focus");
} else if ( $(".rooms-times-nav").length > 0){
- $("li.selected a").focus();
+ $("li.selected a").trigger("focus");
} else {
- $("input:text:visible:enabled:first").focus();
+ $("input:text:visible:enabled:first").trigger("focus");
}
@@ -221,7 +221,7 @@ $(document).ready(function() {
}
// auto populate Area Director List when primary area selected (add form)
- $('#id_primary_area').change(function(){
+ $('#id_primary_area').on("change", function(){
$.getJSON('/secr/groups/get_ads/',{"area":$(this).val()},function(data) {
$('#id_primary_area_director option').remove();
$.each(data,function(i,item) {
@@ -231,7 +231,7 @@ $(document).ready(function() {
});
// auto populate Area Director List when area selected (edit form)
- $('#id_ietfwg-0-primary_area').change(function(){
+ $('#id_ietfwg-0-primary_area').on("change", function(){
$.getJSON('/secr/groups/get_ads/',{"area":$(this).val()},function(data) {
$('#id_ietfwg-0-area_director option').remove();
$.each(data,function(i,item) {
@@ -250,4 +250,4 @@ $(document).ready(function() {
init_proceedings_upload();
}
-});
+});
\ No newline at end of file
diff --git a/ietf/settings.py b/ietf/settings.py
index ad43afdfb..823b34151 100644
--- a/ietf/settings.py
+++ b/ietf/settings.py
@@ -492,6 +492,9 @@ BOOTSTRAP5 = {
# Field class to use in horiozntal forms
'horizontal_field_class': 'col-md-10',
+ # Field class used for horizontal fields withut a label.
+ 'horizontal_field_offset_class': 'offset-md-2',
+
# Set placeholder attributes to label if no placeholder is provided
'set_placeholder': False,
@@ -500,7 +503,6 @@ BOOTSTRAP5 = {
'field_renderers': {
'default': 'ietf.utils.bootstrap.SeparateErrorsFromHelpTextFieldRenderer',
- 'inline': 'bootstrap5.renderers.InlineFieldRenderer',
},
}
diff --git a/ietf/static/css/datepicker.scss b/ietf/static/css/datepicker.scss
new file mode 100644
index 000000000..6a05518c8
--- /dev/null
+++ b/ietf/static/css/datepicker.scss
@@ -0,0 +1 @@
+@import "~/node_modules/bootstrap-datepicker/dist/css//bootstrap-datepicker3.css";
\ No newline at end of file
diff --git a/ietf/static/css/edit-meeting-schedule.scss b/ietf/static/css/edit-meeting-schedule.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/ietf/static/css/ietf.scss b/ietf/static/css/ietf.scss
index 690a0ff97..c173a980d 100644
--- a/ietf/static/css/ietf.scss
+++ b/ietf/static/css/ietf.scss
@@ -190,4 +190,647 @@ $timeline-even-hover-color: shift-color($timeline-even-color, $link-shade-percen
.position-norecord {
// background-color: $secondary;
+}
+
+/* === Edit Meeting Schedule ====================================== */
+
+.edit-meeting-schedule .edit-grid {
+ position: relative;
+ display: flex;
+}
+
+.edit-meeting-schedule .edit-grid .room-label-column {
+ /* make sure we cut this column off - the time slots will determine
+ how much of it is shown */
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ overflow: hidden;
+ width: 8em;
+}
+
+.edit-meeting-schedule .edit-grid .day {
+ margin-left: 1em;
+ margin-bottom: 2em;
+}
+
+.edit-meeting-schedule .edit-grid .room-label-column .day {
+ margin-left: 0;
+}
+
+.edit-meeting-schedule .edit-grid .day-label {
+ height: 3em;
+}
+
+.edit-meeting-schedule .edit-grid .day-label .swap-days {
+ cursor: pointer;
+}
+
+.edit-meeting-schedule .edit-grid .day-label .swap-days:hover {
+ color: #666;
+}
+
+.edit-meeting-schedule #swap-days-modal .modal-body label {
+ display: block;
+}
+
+.edit-meeting-schedule .edit-grid .day-flow {
+ margin-left: 8em;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-start;
+}
+
+.edit-meeting-schedule .edit-grid .room-group:not(:last-child) {
+ margin-bottom: 1em;
+}
+
+.edit-meeting-schedule .edit-grid .time-header {
+ position: relative;
+ height: 1.5em;
+ padding-bottom: 0.15em;
+}
+
+.edit-meeting-schedule .edit-grid .time-header .time-label {
+ display: inline-block;
+ position: relative;
+ width: 100%;
+ align-items: center;
+}
+
+.edit-meeting-schedule .edit-grid .time-header .time-label.would-violate-hint {
+ background-color: #ffe0e0;
+ outline: #ffe0e0 solid 0.4em;
+}
+
+.edit-meeting-schedule .edit-grid .time-header .time-label span {
+ display: inline-block;
+ width: 100%;
+ text-align: center;
+ color: #444444;
+}
+
+.edit-meeting-schedule .edit-grid .timeslots {
+ position: relative;
+ height: 4.5em;
+ padding-bottom: 0.15em;
+}
+
+.edit-meeting-schedule .edit-grid .timeslot {
+ position: relative;
+ display: inline-block;
+ background-color: #f4f4f4;
+ height: 100%;
+ overflow: hidden;
+}
+
+.edit-meeting-schedule .edit-grid .timeslot .time-label {
+ display: flex;
+ flex-direction: column;
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ align-items: center;
+ justify-content: center;
+ color: #999;
+}
+
+.edit-meeting-schedule .edit-grid .timeslot .drop-target {
+ position: relative;
+ /* this is merely to make sure we are positioned above the time labels */
+ display: flex;
+ flex-direction: row;
+ height: 100%;
+}
+
+.edit-meeting-schedule .edit-grid .timeslot.dropping {
+ background-color: #ccc;
+ transition: background-color 0.2s;
+}
+
+.edit-meeting-schedule .edit-grid .timeslot.overfull {
+ border-right: 0.3em dashed #f55000;
+ /* cut-off illusion */
+}
+
+.edit-meeting-schedule .edit-grid .timeslot.would-violate-hint {
+ background-color: #ffe0e0;
+ outline: #ffe0e0 solid 0.4em;
+}
+
+.edit-meeting-schedule .edit-grid .timeslot.would-violate-hint.dropping {
+ background-color: #ccb3b3;
+}
+
+.edit-meeting-schedule .constraints .encircled,
+.edit-meeting-schedule .formatted-constraints .encircled {
+ border: 1px solid #000;
+ border-radius: 1em;
+ padding: 0 0.3em;
+ text-align: center;
+ display: inline-block;
+}
+
+.edit-meeting-schedule .formatted-constraints .encircled {
+ font-size: smaller;
+}
+
+/* sessions */
+.edit-meeting-schedule .session {
+ background-color: #fff;
+ margin: 0.2em;
+ padding-right: 0.2em;
+ padding-left: 0.5em;
+ line-height: 1.3em;
+ border-radius: 0.4em;
+ overflow: hidden;
+ cursor: pointer;
+}
+
+.edit-meeting-schedule .session.selected {
+ cursor: grabbing;
+ outline: #0000ff solid 0.2em;
+ /* blue, width matches margin on .session */
+ z-index: 2;
+ /* render above timeslot outlines */
+}
+
+.edit-meeting-schedule .session.other-session-selected {
+ outline: #00008b solid 0.2em;
+ /* darkblue, width matches margin on .session */
+ z-index: 2;
+ /* render above timeslot outlines */
+}
+
+.edit-meeting-schedule .read-only .session.selected {
+ cursor: default;
+}
+
+.edit-meeting-schedule .session.readonly {
+ cursor: default;
+ background-color: #ddd;
+}
+
+.edit-meeting-schedule .session.hidden-parent * {
+ /* This makes .session.hidden-parent's children transparent but keeps the
+ * .session itself opaque so the timeslot label does not show through. */
+ opacity: 0.7;
+}
+
+.edit-meeting-schedule .session.selected .session-label {
+ font-weight: bold;
+}
+
+.edit-meeting-schedule .session.highlight {
+ outline-color: #ff8c00;
+ /* darkorange */
+ background-color: #f3f3f3;
+}
+
+.edit-meeting-schedule .session.would-violate-hint {
+ outline: 0.3em solid #F55000;
+ z-index: 1;
+ /* raise up so the outline is not overdrawn */
+}
+
+.edit-meeting-schedule .session.highlight .session-label {
+ font-weight: bold;
+}
+
+.edit-meeting-schedule .session.dragging {
+ opacity: 0.1;
+ transition: opacity 0.4s;
+}
+
+.edit-meeting-schedule .timeslot.overfull .session {
+ border-radius: 0.4em 0 0 0.4em;
+ /* remove right-side rounding to allude to being cut off */
+ margin-right: 0;
+}
+
+.edit-meeting-schedule .edit-grid,
+.edit-meeting-schedule .session {
+ font-family: arial, helvetica, sans-serif;
+ font-size: 11px;
+}
+
+.edit-meeting-schedule .session .session-label {
+ flex-grow: 1;
+ margin-left: 0.1em;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.edit-meeting-schedule .session .session-label .bof-tag {
+ font-style: normal;
+ font-size: smaller;
+ color: #8b0000;
+ font-weight: bold;
+ float: right;
+ margin-right: 0.2em;
+}
+
+.edit-meeting-schedule .session.too-many-attendees .attendees {
+ font-weight: bold;
+ color: #8432d4;
+}
+
+.edit-meeting-schedule .session .constraints {
+ margin-right: 0.2em;
+ text-align: right;
+ flex-shrink: 1;
+}
+
+.edit-meeting-schedule .session .constraints>span {
+ display: none;
+ font-size: smaller;
+}
+
+.edit-meeting-schedule .session .constraints>span .encircled {
+ border: 1px solid #b35eff;
+}
+
+.edit-meeting-schedule .session .constraints>span.violated-hint {
+ display: inline-block;
+ color: #8432d4;
+}
+
+.edit-meeting-schedule .session .constraints>span.would-violate-hint {
+ display: inline-block;
+ font-weight: bold;
+ color: #f55;
+}
+
+.edit-meeting-schedule .session .constraints>span.would-violate-hint .encircled {
+ border: 1px solid #f99;
+}
+
+.edit-meeting-schedule .unassigned-sessions .session .constraints>span {
+ display: none;
+}
+
+.edit-meeting-schedule .session .session-info {
+ display: none;
+}
+
+/* scheduling panel */
+.edit-meeting-schedule .scheduling-panel {
+ position: sticky;
+ display: flex;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ border-top: 0.2em solid #ccc;
+ margin-bottom: 2em;
+ background-color: #fff;
+ opacity: 0.95;
+ z-index: 5;
+ /* raise above edit-grid items */
+}
+
+.edit-meeting-schedule .scheduling-panel .unassigned-container {
+ flex-grow: 1;
+}
+
+.edit-meeting-schedule .unassigned-sessions {
+ margin-top: 0.5em;
+ min-height: 4em;
+ max-height: 13em;
+ overflow-y: auto;
+ background-color: #f4f4f4;
+}
+
+.edit-meeting-schedule .unassigned-sessions.dropping {
+ background-color: #e5e5e5;
+ transition: background-color 0.2s;
+}
+
+.edit-meeting-schedule .unassigned-sessions .drop-target {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: flex-start;
+ min-height: 5em;
+ /* do not disappear when empty */
+}
+
+.edit-meeting-schedule .scheduling-panel .preferences {
+ margin: 0.5em 0;
+}
+
+.edit-meeting-schedule .scheduling-panel .preferences>span {
+ margin-top: 0;
+ margin-right: 1em;
+}
+
+.edit-meeting-schedule .sort-unassigned select {
+ width: auto;
+ display: inline-block;
+}
+
+.edit-meeting-schedule #timeslot-group-toggles-modal .modal-body>div {
+ margin-bottom: 1.5em;
+}
+
+.edit-meeting-schedule #timeslot-group-toggles-modal .modal-body .individual-timeslots {
+ /*column-count: 3;*/
+ display: flex;
+ flex-flow: row wrap;
+}
+
+.edit-meeting-schedule #timeslot-group-toggles-modal .modal-body .individual-timeslots>* {
+ margin-right: 1.5em;
+}
+
+.edit-meeting-schedule #timeslot-group-toggles-modal .modal-body .individual-timeslots label {
+ display: block;
+ font-weight: normal;
+}
+
+.edit-meeting-schedule .session-parent-toggles {
+ margin-top: 1em;
+}
+
+.edit-meeting-schedule .toggle-inputs label {
+ font-weight: normal;
+ margin-right: 1em;
+ padding: 0 1em;
+ border: 0.1em solid #eee;
+ cursor: pointer;
+}
+
+.edit-meeting-schedule .modal .day-options {
+ display: flex;
+ flex-flow: row wrap;
+}
+
+.edit-meeting-schedule .modal .timeslot-options {
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: flex-start;
+}
+
+.edit-meeting-schedule .modal .room-group {
+ margin: 2em;
+}
+
+.edit-meeting-schedule .scheduling-panel .session-info-container {
+ padding-left: 0.5em;
+ flex: 0 0 25em;
+ height: 20em;
+ font-size: 14px;
+ overflow-y: auto;
+}
+
+.edit-meeting-schedule .scheduling-panel .session-info-container .comments {
+ font-style: italic;
+}
+
+.edit-meeting-schedule .scheduling-panel .session-info-container .other-session:hover {
+ cursor: default;
+ background-color: #eee;
+}
+
+/* A modified .container-fluid without padding on very narrow devices*/
+.container-fluid-narrow {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+@media (max-width: 480px) {
+ .container-fluid-narrow {
+ padding-right: 0;
+ padding-left: 0;
+ margin-right: auto;
+ margin-left: auto;
+ }
+}
+
+/* === Edit Meeting Timeslots and Misc Sessions =================== */
+
+.edit-meeting-timeslots-and-misc-sessions .day {
+ margin-bottom: 1em;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .day-label {
+ text-align: center;
+ font-size: 20px;
+ margin-bottom: 0.4em;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .room-row {
+ border-bottom: 1px solid #ccc;
+ height: 20px;
+ display: flex;
+ cursor: pointer;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .room-label {
+ width: 12em;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .timeline {
+ position: relative;
+ flex-grow: 1;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .timeline.hover {
+ background: radial-gradient(#999 1px, transparent 1px);
+ background-size: 20px 20px;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .timeline.selected.hover,
+.edit-meeting-timeslots-and-misc-sessions .timeline.selected {
+ background: radial-gradient(#999 2px, transparent 2px);
+ background-size: 20px 20px;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .timeslot {
+ position: absolute;
+ overflow: hidden;
+ background-color: #f0f0f0;
+ opacity: 0.8;
+ height: 19px;
+ top: 0px;
+ font-size: 13px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ cursor: pointer;
+ padding-left: 0.2em;
+ border-left: 1px solid #999;
+ border-right: 1px solid #999;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .timeslot:hover {
+ background-color: #ccc;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .timeslot.selected {
+ background-color: #bbb;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .timeslot .session.cancelled {
+ color: #a00;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel {
+ position: sticky;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ border-top: 0.2em solid #ccc;
+ padding-top: 0.2em;
+ margin-bottom: 2em;
+ background-color: #fff;
+ opacity: 0.95;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel form {
+ display: flex;
+ align-items: flex-start;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel form button {
+ margin: 0 0.5em;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: baseline;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form .form-group {
+ margin-right: 1em;
+ margin-bottom: 0.5em;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form label {
+ display: inline-block;
+ margin-right: 0.5em;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form .form-control {
+ display: inline-block;
+ width: auto;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=time],
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=duration] {
+ width: 6em;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=name] {
+ width: 25em;
+}
+
+.edit-meeting-timeslots-and-misc-sessions .scheduling-panel .flowing-form [name=short] {
+ width: 10em;
+}
+
+.timeslot-edit .tstable div.timeslot {
+ border: #000000 solid 1px;
+ border-radius: 0.5em;
+ padding: 0.5em;
+}
+
+.timeslot-edit .tstable .timeslot .ts-name {
+ overflow: hidden;
+}
+
+.timeslot-edit .tstable .timeslot .ts-type {
+ font-size: smaller;
+}
+
+.timeslot-edit .tstable .timeslot .timeslot-buttons {
+ float: right;
+}
+
+.timeslot-edit .tstable .timeslot.in-official-use {
+ background-color: #d9edf7;
+}
+
+.timeslot-edit .tstable .timeslot.in-unofficial-use {
+ background-color: #f8f8e0;
+}
+
+.timeslot-edit .tstable td.timeslot-collision {
+ background-color: #ffa0a0;
+}
+
+.timeslot-edit .tstable .tstype_unavail {
+ background-color: #666;
+}
+
+.timeslot-edit .official-use-warning {
+ color: #ff0000;
+}
+
+.rightmarker,
+.leftmarker {
+ width: 3px;
+ padding-right: 0px !important;
+ padding-left: 0px !important;
+}
+
+.ongoing>td:first-child {
+ background-color: red !important;
+}
+
+.ongoing>td:last-child {
+ background-color: red !important;
+}
+
+.timetooltip {
+ position: relative;
+}
+
+.timetooltip .timetooltiptext {
+ visibility: hidden;
+ background-color: #eee;
+ color: #000;
+ text-align: left;
+ border-radius: 6px;
+ padding: 5px 5px;
+ position: absolute;
+ z-index: 110;
+ bottom: 125%;
+ left: 50%;
+ margin-left: -60px;
+ opacity: 0;
+ transition: opacity 0.3s;
+ width: 60em;
+}
+
+.reschedtimetooltip .timetooltiptext {
+ margin-left: -300px;
+}
+
+.timetooltiptext table tr td {
+ padding: 1px 5px;
+}
+
+.timetooltiptext table tr th {
+ text-align: center;
+}
+
+.timehead {
+ text-align: right;
+ font-weight: bold;
+}
+
+.timetooltip:hover .timetooltiptext {
+ visibility: visible;
+ opacity: 1;
+}
+
+#current-time {
+ display: inline-block;
}
\ No newline at end of file
diff --git a/ietf/static/css/jquery-ui.scss b/ietf/static/css/jquery-ui.scss
index 8a8786ae4..42ce73e78 100644
--- a/ietf/static/css/jquery-ui.scss
+++ b/ietf/static/css/jquery-ui.scss
@@ -1 +1,3 @@
-@import "~/node_modules/jquery-ui/themes/base/all.css";
\ No newline at end of file
+@import "~node_modules/jquery-ui-dist/jquery-ui.css";
+@import "~node_modules/jquery-ui-dist/jquery-ui.structure.css";
+@import "~node_modules/jquery-ui-dist/jquery-ui.theme.min.css";
\ No newline at end of file
diff --git a/ietf/static/js/edit-milestones.js b/ietf/static/js/edit-milestones.js
index e5e3c703e..e5565412d 100644
--- a/ietf/static/js/edit-milestones.js
+++ b/ietf/static/js/edit-milestones.js
@@ -34,8 +34,11 @@ $(document)
function setSubmitButtonState() {
var action;
- if (milestonesForm.find("input[name$=delete]:visible")
- .length > 0 || milestone_order_has_changed)
+ var milestone_cnt = milestonesForm.find(".milestonerow").length;
+ var milestone_hidden_cnt = milestonesForm.find(".edit-milestone.visually-hidden").length;
+ var milestone_change_cnt = milestonesForm.find(".edit-milestone.changed").length;
+ var milestone_delete_cnt = milestonesForm.find(".edit-milestone.delete").length;
+ if (milestone_cnt != milestone_hidden_cnt || milestone_order_has_changed)
action = "review";
else
action = "save";
@@ -45,11 +48,11 @@ $(document)
var submit = milestonesForm.find("[type=submit]");
submit.text(submit.data("label" + action));
- if (milestonesForm.find(".edit-milestone.changed,.edit-milestone.delete")
- .length > 0 || action == "review")
+ if (milestone_change_cnt + milestone_delete_cnt > 0 || action == "review") {
submit.removeClass("visually-hidden");
- else
+ } else {
submit.addClass("visually-hidden");
+ }
}
milestonesForm.find(".milestone")
@@ -130,6 +133,7 @@ $(document)
if (!group_uses_milestone_dates) {
setOrderControlValue();
}
+ setSubmitButtonState();
});
function setResolvedState() {
diff --git a/ietf/static/js/edit_action_holders.js b/ietf/static/js/edit_action_holders.js
new file mode 100644
index 000000000..3468ef7fa
--- /dev/null
+++ b/ietf/static/js/edit_action_holders.js
@@ -0,0 +1,85 @@
+local_js = function () {
+ let select2_elem = $('.select2-field');
+ let role_ids = select2_elem.data('role-ids');
+
+ function update_selection(elem, entries, selected) {
+ elem.children("option")
+ .each(function () {
+ if (entries.some(x => x == $(this)
+ .val())) {
+ $(this)
+ .prop("selected", selected);
+ }
+ })
+ .trigger('change');
+ }
+
+ function add_ah(role) {
+ if (role_ids[role]) {
+ update_selection(select2_elem, role_ids[role], true);
+ }
+ }
+
+ function del_ah(role) {
+ if (role_ids[role] && select2_elem.val()) {
+ update_selection(select2_elem, role_ids[role], false);
+ }
+ }
+
+ function all_selected(elem, role) {
+ if (!elem.val()) { return false; }
+ let data_ids = elem.val()
+ .map(Number);
+ for (let ii = 0; ii < role_ids[role].length; ii++) {
+ if (-1 === data_ids.indexOf(role_ids[role][ii])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function none_selected(elem, role) {
+ if (!elem.val()) { return true; }
+
+ let data_ids = elem.val()
+ .map(Number);
+ for (let ii = 0; ii < role_ids[role].length; ii++) {
+ if (-1 !== data_ids.indexOf(role_ids[role][ii])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function update_buttons() {
+ for (let role_slug in role_ids) {
+ if (!role_ids.hasOwnProperty(role_slug)) { return; }
+
+ if (all_selected(select2_elem, role_slug)) {
+ $('#add-' + role_slug)
+ .attr('disabled', true);
+ } else {
+ $('#add-' + role_slug)
+ .attr('disabled', false);
+ }
+
+ if (none_selected(select2_elem, role_slug)) {
+ $('#del-' + role_slug)
+ .attr('disabled', true);
+ } else {
+ $('#del-' + role_slug)
+ .attr('disabled', false);
+ }
+ }
+
+ }
+
+ select2_elem.on('change', update_buttons);
+ $(document)
+ .ready(update_buttons);
+
+ return {
+ add_ah: add_ah,
+ del_ah: del_ah
+ };
+}();
\ No newline at end of file
diff --git a/ietf/static/js/edit_authors.js b/ietf/static/js/edit_authors.js
new file mode 100644
index 000000000..e22a1442b
--- /dev/null
+++ b/ietf/static/js/edit_authors.js
@@ -0,0 +1,90 @@
+local_js = function () {
+ const sortable_list_id = 'authors-list'; // id of the container element for Sortable
+ const prefix = 'author'; // formset prefix - must match the prefix in the edit_authors() view
+ var list_container;
+ var form_counter;
+ var author_template;
+ var person_select2_input_selector = 'select.select2-field[name^="author-"][name$="-person"]';
+
+ function handle_drag_end() {
+ // after dragging, set order inputs to match new positions in list
+ $(list_container)
+ .find('.draggable input[name^="' + prefix + '"][name$="ORDER"]')
+ .each(
+ function (index, elt) {
+ $(elt)
+ .val(index + 1);
+ });
+ }
+
+ function add_author() {
+ // __prefix__ is the unique prefix for each list item, indexed from 0
+ var new_html = $(author_template)
+ .html()
+ .replaceAll('__prefix__', form_counter.value);
+ var new_elt = $(new_html);
+ $(list_container)
+ .append(new_elt);
+ var new_person_select = new_elt.find(person_select2_input_selector);
+ setupSelect2Field(new_person_select);
+ new_person_select.on('change', person_changed);
+
+ var form_count = Number(form_counter.value);
+ form_counter.value = String(form_count + 1);
+
+ new_elt[0].scrollIntoView(true);
+ }
+
+ function update_email_options_cb_factory(email_select) {
+ // factory method creates a closure for the callback
+ return function (ajax_data) {
+ // keep the first item - it's the 'blank' option
+ $(email_select)
+ .children()
+ .not(':first')
+ .remove();
+ $.each(ajax_data, function (index, email) {
+ $(email_select)
+ .append(
+ $('')
+ .attr('value', email.address)
+ .text(email.address)
+ );
+ });
+ if (ajax_data.length > 0) {
+ $(email_select)
+ .val(ajax_data[0].address);
+ }
+ };
+ }
+
+ function person_changed() {
+ var person_elt = $(this);
+ var email_select = $('#' + person_elt.attr('id')
+ .replace(/-person$/, '-email'));
+ $.get(
+ ajax_url.replace('123454321', $(this)
+ .val()),
+ null,
+ update_email_options_cb_factory(email_select)
+ );
+ }
+
+ list_container = document.getElementById(sortable_list_id);
+ form_counter = document.getElementsByName(prefix + '-TOTAL_FORMS')[0];
+ author_template = document.getElementById('empty-author-form');
+
+ Sortable.create(
+ list_container, {
+ handle: '.handle',
+ onEnd: handle_drag_end
+ });
+
+ // register handler
+ $(person_select2_input_selector)
+ .on('change', person_changed);
+
+ return {
+ add_author: add_author
+ };
+}();
\ No newline at end of file
diff --git a/ietf/static/js/ietf.js b/ietf/static/js/ietf.js
index 00d250de0..28f070a88 100644
--- a/ietf/static/js/ietf.js
+++ b/ietf/static/js/ietf.js
@@ -109,7 +109,7 @@ $(document)
$(document)
.ready(function () {
var headings = $("#content")
- .find("h1, h2, h3, h4, h5, h6");
+ .find("h1:visible, h2:visible, h3:visible, h4:visible, h5:visible, h6:visible");
if ($(headings)
.length > 0 && $(headings)
@@ -132,7 +132,7 @@ $(document)
`))
- .find("h1, h2, h3, h4, h5, h6")
+ .find("h1:visible, h2:visible, h3:visible, h4:visible, h5:visible, h6:visible")
.each(function () {
var id = $(this)
.attr("id");
diff --git a/ietf/static/js/jquery-ui.js b/ietf/static/js/jquery-ui.js
index 484af9476..e935ce8e9 100644
--- a/ietf/static/js/jquery-ui.js
+++ b/ietf/static/js/jquery-ui.js
@@ -1,20 +1 @@
-var accordion = require("jquery-ui/ui/widgets/accordion");
-var autocomplete = require("jquery-ui/ui/widgets/autocomplete");
-var button = require("jquery-ui/ui/widgets/button");
-var checkboxradio = require("jquery-ui/ui/widgets/checkboxradio");
-var controlgroup = require("jquery-ui/ui/widgets/controlgroup");
-var datepicker = require("jquery-ui/ui/widgets/datepicker");
-var dialog = require("jquery-ui/ui/widgets/dialog");
-var draggable = require("jquery-ui/ui/widgets/draggable");
-var droppable = require("jquery-ui/ui/widgets/droppable");
-var menu = require("jquery-ui/ui/widgets/menu");
-var mouse = require("jquery-ui/ui/widgets/mouse");
-var progressbar = require("jquery-ui/ui/widgets/progressbar");
-var resizable = require("jquery-ui/ui/widgets/resizable");
-var selectable = require("jquery-ui/ui/widgets/selectable");
-var selectmenu = require("jquery-ui/ui/widgets/selectmenu");
-var slider = require("jquery-ui/ui/widgets/slider");
-var sortable = require("jquery-ui/ui/widgets/sortable");
-var spinner = require("jquery-ui/ui/widgets/spinner");
-var tabs = require("jquery-ui/ui/widgets/tabs");
-var tooltip = require("jquery-ui/ui/widgets/tooltip");
\ No newline at end of file
+import "jquery-ui-dist/jquery-ui.js";
\ No newline at end of file
diff --git a/ietf/static/js/select2.js b/ietf/static/js/select2.js
index aff7ad719..6ee2aa174 100644
--- a/ietf/static/js/select2.js
+++ b/ietf/static/js/select2.js
@@ -24,9 +24,8 @@ window.setupSelect2Field = function (e) {
var maxEntries = e.data("max-entries");
var options = e.data("pre");
for (var id in options) {
- e.append(new Option(options[id].text, options[id].id, true, true));
+ e.append(new Option(options[id].text, options[id].id, false, options[id].selected));
}
- // e.trigger("change");
e.select2({
multiple: maxEntries !== 1,
diff --git a/ietf/static/js/upload-material.js b/ietf/static/js/upload-material.js
new file mode 100644
index 000000000..c68b9c770
--- /dev/null
+++ b/ietf/static/js/upload-material.js
@@ -0,0 +1,62 @@
+// Copyright The IETF Trust 2021, All Rights Reserved
+(
+ function () {
+ 'use strict';
+
+ /**
+ * Hide the inactive input form-group
+ * @param form form to process
+ */
+ function showUrlOrFile(form) {
+ const useUrlInput = form.elements.namedItem('id_use_url');
+ const urlGroup = form.elements.namedItem('id_external_url')
+ .closest('div');
+ const fileGroup = form.elements.namedItem('id_file')
+ .closest('div');
+
+ if (useUrlInput.checked) {
+ urlGroup.hidden = false;
+ fileGroup.hidden = true;
+ } else {
+ urlGroup.hidden = true;
+ fileGroup.hidden = false;
+ }
+ }
+
+ /**
+ * Dispatch showUrlOrFile from a UI event on the enclosing form
+ * @param evt change event instance
+ */
+ function handleFormChange(evt) {
+ showUrlOrFile(evt.currentTarget); // currentTarget is the form
+ }
+
+ /**
+ * Clear hidden file input values before submitting form to avoid
+ * needlessly sending a file when use_url is selected
+ * @param evt submit event instance
+ */
+ function handleFormSubmit(evt) {
+ const form = evt.currentTarget;
+ const fileInput = form.elements.namedItem('file');
+ if (fileInput.hidden) {
+ fileInput.value = '';
+ }
+ }
+
+ /**
+ * Register event handlers and other initialization tasks.
+ */
+ function initialize() {
+ const forms = document.querySelectorAll('form.upload-material');
+ for (let i = 0; i < forms.length; i++) {
+ const form = forms[i];
+ form.addEventListener('change', handleFormChange);
+ form.addEventListener('submit', handleFormSubmit);
+ showUrlOrFile(form);
+ }
+ }
+
+ initialize();
+ }
+)();
\ No newline at end of file
diff --git a/ietf/submit/tests.py b/ietf/submit/tests.py
index 2831353bf..71e784e18 100644
--- a/ietf/submit/tests.py
+++ b/ietf/submit/tests.py
@@ -166,9 +166,8 @@ class SubmitTests(BaseSubmitTestCase):
r = self.client.post(url, files)
if r.status_code != 302:
q = PyQuery(r.content)
- print(q('div.is-invalid div.alert').text())
-
- self.assertNoFormPostErrors(r, ".is-invalid,.alert-danger")
+ print(q('div.invalid-feedback').text())
+ self.assertNoFormPostErrors(r, ".invalid-feedback,.alert-danger")
for format in formats:
self.assertTrue(os.path.exists(os.path.join(self.staging_dir, "%s-%s.%s" % (name, rev, format))))
@@ -232,12 +231,8 @@ class SubmitTests(BaseSubmitTestCase):
if r.status_code == 302:
submission = Submission.objects.get(name=name)
self.assertEqual(submission.submitter, email.utils.formataddr((submitter_name, submitter_email)))
- self.assertEqual(submission.replaces,
- ",".join(
- d.name for d in DocAlias.objects.filter(
- pk__in=replaces.split(",") if replaces else []
- )
- ))
+ self.assertEqual([] if submission.replaces == "" else submission.replaces.split(','),
+ [ d.name for d in DocAlias.objects.filter(pk__in=replaces) ])
self.assertCountEqual(
[str(r) for r in submission.external_resources.all()],
[str(r) for r in extresources] if extresources else [],
@@ -296,7 +291,7 @@ class SubmitTests(BaseSubmitTestCase):
mailbox_before = len(outbox)
replaced_alias = draft.docalias.first()
r = self.supply_extra_metadata(name, status_url, author.ascii, author.email().address.lower(),
- replaces=str(replaced_alias.pk) + "," + str(sug_replaced_alias.pk))
+ replaces=[str(replaced_alias.pk), str(sug_replaced_alias.pk)])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
@@ -400,7 +395,7 @@ class SubmitTests(BaseSubmitTestCase):
# supply submitter info, then draft should be in and ready for approval
mailbox_before = len(outbox)
self.client.login(username=username, password=username+'+password') # log in as the author
- r = self.supply_extra_metadata(name, status_url, author.ascii, author.email().address.lower(), replaces='')
+ r = self.supply_extra_metadata(name, status_url, author.ascii, author.email().address.lower(), replaces=[])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
@@ -446,7 +441,7 @@ class SubmitTests(BaseSubmitTestCase):
{'submitter-name': author.name,
'submitter-email': username,
'action': 'autopost',
- 'replaces': ''})
+ 'replaces': []})
# Attempt should fail and draft should remain in the uploaded state
self.assertEqual(r.status_code, 403)
submission = Submission.objects.get(name=name, rev=rev)
@@ -554,7 +549,7 @@ class SubmitTests(BaseSubmitTestCase):
# supply submitter info, then previous authors get a confirmation email
mailbox_before = len(outbox)
- r = self.supply_extra_metadata(name, status_url, "Submitter Name", "submitter@example.com", replaces="")
+ r = self.supply_extra_metadata(name, status_url, "Submitter Name", "submitter@example.com", replaces=[])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
r = self.client.get(status_url)
@@ -752,7 +747,7 @@ class SubmitTests(BaseSubmitTestCase):
{'submitter-name': author.name,
'submitter-email': 'submitter@example.com',
'action': 'autopost',
- 'replaces': ''})
+ 'replaces': []})
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
@@ -803,7 +798,7 @@ class SubmitTests(BaseSubmitTestCase):
# supply submitter info, then draft should be be ready for email auth
mailbox_before = len(outbox)
- r = self.supply_extra_metadata(name, status_url, "Submitter Name", "submitter@example.com", replaces="")
+ r = self.supply_extra_metadata(name, status_url, "Submitter Name", "submitter@example.com", replaces=[])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
@@ -862,7 +857,7 @@ class SubmitTests(BaseSubmitTestCase):
name, '00', author, formats=formats, base_filename='test_submission_no_org_or_address'
)
status_url = r['Location']
- r = self.supply_extra_metadata(name, status_url, 'Submitter name', 'submitter@example.com', replaces='')
+ r = self.supply_extra_metadata(name, status_url, 'Submitter name', 'submitter@example.com', replaces=[])
self.assertEqual(r.status_code, 302)
# force post of submission
@@ -957,7 +952,7 @@ class SubmitTests(BaseSubmitTestCase):
SubmissionExtResource(name_id='faq', value='https://faq.example.com/'),
SubmissionExtResource(name_id='wiki', value='https://wiki.example.com', display_name='Test Wiki'),
]
- r = self.supply_extra_metadata(name, status_url, 'Submitter name', 'submitter@example.com', replaces='',
+ r = self.supply_extra_metadata(name, status_url, 'Submitter name', 'submitter@example.com', replaces=[],
extresources=resources)
self.assertEqual(r.status_code, 302)
status_url = r['Location']
@@ -985,7 +980,7 @@ class SubmitTests(BaseSubmitTestCase):
# supply submitter info, then draft should be be ready for email auth
mailbox_before = len(outbox)
- r = self.supply_extra_metadata(name, status_url, author.name, username, replaces="")
+ r = self.supply_extra_metadata(name, status_url, author.name, username, replaces=[])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
@@ -1041,7 +1036,7 @@ class SubmitTests(BaseSubmitTestCase):
SubmissionExtResource(name_id='faq', value='https://faq.example.com/'),
SubmissionExtResource(name_id='wiki', value='https://wiki.example.com', display_name='Test Wiki'),
]
- r = self.supply_extra_metadata(name, status_url, author.name, username, replaces='',
+ r = self.supply_extra_metadata(name, status_url, author.name, username, replaces=[],
extresources=resources)
self.assertEqual(r.status_code, 302)
status_url = r['Location']
@@ -1071,14 +1066,14 @@ class SubmitTests(BaseSubmitTestCase):
mailbox_before = len(outbox)
replaced_alias = draft.docalias.first()
- r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=str(replaced_alias.pk))
+ r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(replaced_alias.pk)])
self.assertEqual(r.status_code, 200)
self.assertContains(r, 'cannot replace itself')
self._assert_extresources_in_table(r, [])
self._assert_extresources_form(r, [])
replaced_alias = DocAlias.objects.get(name='draft-ietf-random-thing')
- r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=str(replaced_alias.pk))
+ r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(replaced_alias.pk)])
self.assertEqual(r.status_code, 200)
self.assertContains(r, 'cannot replace an RFC')
self._assert_extresources_in_table(r, [])
@@ -1086,13 +1081,13 @@ class SubmitTests(BaseSubmitTestCase):
replaced_alias.document.set_state(State.objects.get(type='draft-iesg',slug='approved'))
replaced_alias.document.set_state(State.objects.get(type='draft',slug='active'))
- r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=str(replaced_alias.pk))
+ r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[str(replaced_alias.pk)])
self.assertEqual(r.status_code, 200)
self.assertContains(r, 'approved by the IESG and cannot')
self._assert_extresources_in_table(r, [])
self._assert_extresources_form(r, [])
- r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces='')
+ r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
r = self.client.get(status_url)
@@ -1145,7 +1140,7 @@ class SubmitTests(BaseSubmitTestCase):
# Update with an empty set of resources
r = self.supply_extra_metadata(orig_draft.name, status_url, author.name, author.user.email,
- replaces='', extresources=[])
+ replaces=[], extresources=[])
self.assertEqual(r.status_code, 302)
status_url = r['Location']
@@ -1179,7 +1174,7 @@ class SubmitTests(BaseSubmitTestCase):
status_url,
"Submitter Name",
"submitter@example.com",
- replaces=str(replaced_draft.docalias.first().pk),
+ replaces=[str(replaced_draft.docalias.first().pk)],
)
submission = Submission.objects.get(name=name, rev=rev)
@@ -1213,7 +1208,7 @@ class SubmitTests(BaseSubmitTestCase):
rev = '%02d'%(int(draft.rev)+1)
status_url, author = self.do_submission(name, rev)
mailbox_before = len(outbox)
- r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces='')
+ r = self.supply_extra_metadata(name, status_url, "Submitter Name", "author@example.com", replaces=[])
self.assertEqual(r.status_code, 302)
status_url = r["Location"]
r = self.client.get(status_url)
@@ -1336,7 +1331,7 @@ class SubmitTests(BaseSubmitTestCase):
"edit-pages": "123",
"submitter-name": "Some Random Test Person",
"submitter-email": "random@example.com",
- "replaces": str(draft.docalias.first().pk),
+ "replaces": [str(draft.docalias.first().pk)],
"edit-note": "no comments",
"authors-0-name": "Person 1",
"authors-0-email": "person1@example.com",
@@ -1346,7 +1341,7 @@ class SubmitTests(BaseSubmitTestCase):
"authors-2-email": "person3@example.com",
"authors-prefix": ["authors-", "authors-0", "authors-1", "authors-2"],
})
- self.assertNoFormPostErrors(r, ".is-invalid,.alert-danger")
+ self.assertNoFormPostErrors(r, ".invalid-feedback,.alert-danger")
submission = Submission.objects.get(name=name)
self.assertEqual(submission.title, "some title")
@@ -1599,8 +1594,8 @@ class SubmitTests(BaseSubmitTestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertTrue(len(q("form .is-invalid")) > 0)
- m = q('div.is-invalid div.alert').text()
+ self.assertTrue(len(q("form .invalid-feedback")) > 0)
+ m = q('div.invalid-feedback').text()
return r, q, m
@@ -1619,8 +1614,8 @@ class SubmitTests(BaseSubmitTestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertTrue(len(q("form .is-invalid")) > 0)
- m = q('div.is-invalid div.alert').text()
+ self.assertTrue(len(q("form .invalid-feedback")) > 0)
+ m = q('div.invalid-feedback').text()
return r, q, m
@@ -1672,12 +1667,11 @@ class SubmitTests(BaseSubmitTestCase):
with io.open(fn, 'w') as f:
f.write("a" * 2000)
files[format], author = submission_file(name, rev, group, format, "test_submission.%s" % format)
-
r = self.client.post(url, files)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- m = q('div.alert-danger').text()
+ m = q('.text-danger').text()
self.assertIn('Unexpected files already in the archive', m)
@@ -2064,24 +2058,23 @@ class SubmitTests(BaseSubmitTestCase):
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
-
# The removed resource should appear once (for the doc current value), tagged as removed
removed_div = q('td>div:contains("Resource to be removed")')
self.assertEqual(len(removed_div), 1)
- self.assertEqual(len(removed_div('span.label:contains("Removed")')), 1)
- self.assertEqual(len(removed_div('span.label:contains("New")')), 0)
+ self.assertEqual(len(removed_div('span.badge:contains("Removed")')), 1)
+ self.assertEqual(len(removed_div('span.badge:contains("New")')), 0)
# The added resource should appear once (for the submission), tagged as new
added_div = q('td>div:contains("Resource to be added")')
self.assertEqual(len(added_div), 1)
- self.assertEqual(len(added_div('span.label:contains("Removed")')), 0)
- self.assertEqual(len(added_div('span.label:contains("New")')), 1)
+ self.assertEqual(len(added_div('span.badge:contains("Removed")')), 0)
+ self.assertEqual(len(added_div('span.badge:contains("New")')), 1)
# The kept resource should appear twice (once for the doc, once for the submission), with no tag
kept_div = q('td>div:contains("Resource to be kept")')
self.assertEqual(len(kept_div), 2)
- self.assertEqual(len(kept_div('span.label:contains("Removed")')), 0)
- self.assertEqual(len(kept_div('span.label:contains("New")')), 0)
+ self.assertEqual(len(kept_div('span.badge:contains("Removed")')), 0)
+ self.assertEqual(len(kept_div('span.badge:contains("New")')), 0)
class ApprovalsTestCase(BaseSubmitTestCase):
def test_approvals(self):
@@ -2225,7 +2218,7 @@ class ApprovalsTestCase(BaseSubmitTestCase):
r = self.client.post(url, dict(name="draft-test-nonexistingwg-something"))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
- self.assertTrue(len(q("form .is-invalid")) > 0)
+ self.assertTrue(len(q("form .invalid-feedback")) > 0)
# add
name = "draft-ietf-mars-foo"
@@ -2659,7 +2652,7 @@ Subject: test
r = self.client.post(url, files)
if r.status_code != 302:
q = PyQuery(r.content)
- print(q('div.is-invalid span.help-block div').text())
+ print(q('div.invalid-feedback span.help-block div').text())
self.assertEqual(r.status_code, 302)
diff --git a/ietf/templates/base.html b/ietf/templates/base.html
index c5a5cf91f..ee41c18f0 100644
--- a/ietf/templates/base.html
+++ b/ietf/templates/base.html
@@ -33,7 +33,7 @@
{% endif %}
-
+