More test fixes

- Legacy-Id: 19803
This commit is contained in:
Lars Eggert 2022-01-05 11:25:25 +00:00
parent 7f918b00da
commit 5132661b06
77 changed files with 1766 additions and 1187 deletions

View file

@ -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'))

View file

@ -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)],
)

View file

@ -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:

View file

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

View file

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

View file

@ -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(

View file

@ -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())

View file

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

View file

@ -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()]))

View file

@ -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,

View file

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

View file

@ -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

View file

@ -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',

View file

@ -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())

View file

@ -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')

View file

@ -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.<br><br>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();
}
});
});

View file

@ -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',
},
}

View file

@ -0,0 +1 @@
@import "~/node_modules/bootstrap-datepicker/dist/css//bootstrap-datepicker3.css";

View file

@ -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;
}

View file

@ -1 +1,3 @@
@import "~/node_modules/jquery-ui/themes/base/all.css";
@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";

View file

@ -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() {

View file

@ -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
};
}();

View file

@ -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(
$('<option></option>')
.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
};
}();

View file

@ -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)
</nav>
</div>
`))
.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");

View file

@ -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");
import "jquery-ui-dist/jquery-ui.js";

View file

@ -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,

View file

@ -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();
}
)();

View file

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

View file

@ -33,7 +33,7 @@
{% endif %}
<link rel="apple-touch-icon"
href="{% static 'ietf/images/apple-touch-icon.png' %}"/>
<script src="{% static 'ietf/js/ietf.js' %}"/></script>
<script src="{% static 'ietf/js/ietf.js' %}"></script>
</head>
<body class="position-relative"
data-group-menu-data-url="{% url 'ietf.group.views.group_menu_data' %}">
@ -74,7 +74,7 @@
in
</a>
{% endif %}
<form class="d-flex d-none d-lg-block" action="/doc/search/">
<form class="navbar-form d-flex d-none d-lg-block" action="/doc/search/">
<input class="form-control"
type="text"
name="name"
@ -116,7 +116,7 @@
class="btn-close float-end"
data-bs-dismiss="alert"
aria-label="Close"></button>
{{ message }}
{{ message.message }}
</div>
{% endfor %}
{% block content %}

View file

@ -20,7 +20,7 @@
{% else %}
{% regroup reqs by get_state_slug as grouped_reqs %}
{% for req_group in grouped_reqs %}
<div class="panel ">
<div class="card ">
<div class="card-header">{{req_group.grouper|capfirst}} BOF Requests</div>
<div class="card-body">
<table id="bofreqs-{{req_group.grouper}}" class="table table-sm table-striped tablesorter">

View file

@ -164,7 +164,7 @@
{% endif %}
{% endif %}
<div class="panel ">
<div class="card ">
<div class="card-header">{{doc.name}}-{{doc.rev}}</div>
<div class="card-body">
{{ content|sanitize|safe }}

View file

@ -133,7 +133,7 @@
<div id='materials-content'>
{% if doc.rev and content != None %}
{% if content_is_html %}
<div class="panel ">
<div class="card ">
<div class="card-body">
{{ content|sanitize|safe }}
</div>

View file

@ -1,38 +1,24 @@
{# bs5ok #}
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load static %}
{% load django_bootstrap5 %}
{% block title %}Change documents replaced by {{ doc }}{% endblock %}
{% block pagehead %}
{{ form.media.css }}
{% endblock %}
{% block pagehead %}{{ form.media.css }}{% endblock %}
{% block content %}
{% origin %}
<h1>Change documents replaced by<br><small class="text-muted">{{ doc }}</small></h1>
<h3>Instructions</h3>
<p class="alert alert-warning">Do not include <code>-00</code> and do not include a file extension like <code>.txt</code> in the document name.</p>
<h1>
Change documents replaced by
<br>
<small class="text-muted">{{ doc }}</small>
</h1>
<h2>Instructions</h2>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Save</button>
<a class="btn btn-secondary float-end" href="{{ doc.get_absolute_url }}">Back</a>
</form>
{% endblock %}
{% block js %}
{{ form.media.js }}
{% endblock %}
{% block js %}{{ form.media.js }}{% endblock %}

View file

@ -1,23 +1,20 @@
[# bs5ok #}
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load django_bootstrap5 %}
{% block title %}Change stream for {{ doc }}{% endblock %}
{% block content %}
{% origin %}
<h1>Change stream<br><small class="text-muted">{{ doc }}</small></h1>
<h1>
Change stream
<br>
<small class="text-muted">{{ doc }}</small>
</h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Save</button>
<a class="btn btn-secondary float-end" href="{{ doc.get_absolute_url }}">Back</a>
</form>
{% endblock %}
{% endblock %}

View file

@ -54,7 +54,7 @@
{% endif %}
</td>
<td>{{ doc.intended_std_level.name }}</td>
<td>{% person_link doc.ad %}</td>
<td><a href="mailto:{{ doc.ad.email_address|urlencode }}">{{ doc.ad.plain_name }}</a><</td>
</tr>
{% endfor %}
</tbody>

View file

@ -1,138 +1,52 @@
{# bs5ok #]}
{% extends "base.html" %}
{# Copyright The IETF Trust 2020, All Rights Reserved #}
{% load origin %}
{% load static %}
{% load django_bootstrap5 %}
{% block title %}
Edit action holders for {{ titletext }}
{% endblock %}
{% block pagehead %}
{{ form.media.css}}
{% endblock %}
{% block title %}Edit action holders for {{ titletext }}{% endblock %}
{% block pagehead %}{{ form.media.css }}{% endblock %}
{% block content %}
{% origin %}
<h1>Edit action holders<br><small class="text-muted">{{titletext}}</small></h1>
<h1>
Edit action holders
<br>
<small class="text-muted">{{ titletext }}</small>
</h1>
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
<div class="form-group">
<label for="role-toolbar">Related people</label>
<div class="btn-toolbar" role="toolbar" id="role-toolbar-{{ role_type_label|slugify }}">
<div class="mb-3">
<label class="form-label" for="role-toolbar">Related people</label>
<div class="btn-toolbar"
role="toolbar"
id="role-toolbar-{{ role_type_label|slugify }}">
{% for doc_role in role_labels %}
<div class="btn-group-vertical btn-group-sm" role="group">
<button type="button" class="btn btn-primary"
id="add-{{ doc_role.slug }}"
onclick="local_js.add_ah('{{ doc_role.slug }}')">
<button type="button"
class="btn btn-primary"
id="add-{{ doc_role.slug }}"
onclick="local_js.add_ah('{{ doc_role.slug }}')">
Add {{ doc_role.label }}
</button>
<button type="button" class="btn btn-primary"
id="del-{{ doc_role.slug }}"
onclick="local_js.del_ah('{{ doc_role.slug }}')">
<button type="button"
class="btn btn-primary"
id="del-{{ doc_role.slug }}"
onclick="local_js.del_ah('{{ doc_role.slug }}')">
Remove {{ doc_role.label }}
</button>
</div>
{% endfor %}
</div>
</div>
<button type="submit" class="btn btn-primary" name="submit" value="Save">Submit</button>
<a class="btn btn-secondary float-end" href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">Back</a>
<a class="btn btn-secondary float-end"
href="{% url "ietf.doc.views_doc.document_main" name=doc.canonical_name %}">
Back
</a>
</form>
{% endblock %}
{% block js %}
{{ form.media.js }}
<script type="text/javascript">
local_js = function () {
let select2_elem = $('.select2-field');
let role_ids = select2_elem.data('role-ids');
/* Updates select2 selection in element elem. Data should be an array of
* objects with id and text as keys. */
function update_selection(elem, entries) {
elem.val(entries.join(',')).trigger('change');
}
function add_ah(role) {
if (role_ids[role]) {
let ids;
if (select2_elem.val()) {
ids = select2_elem.val().split(',').map(Number).concat(role_ids[role]);
} else {
ids = role_ids[role];
}
update_selection(select2_elem, ids);
}
}
function del_ah(role) {
if (role_ids[role] && select2_elem.val()) {
update_selection(select2_elem, select2_elem.val().split(',').filter(
function(id){return -1 === role_ids[role].indexOf(Number(id))}
));
}
}
function all_selected(elem, role) {
if (!elem.val()) {return false}
let data_ids = elem.val().split(',').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().split(',').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
};
}();
</script>
{% endblock %}
<script src="{% static "ietf/js/edit_action_holders.js" %}"></script>
{% endblock %}

View file

@ -36,7 +36,7 @@
{% bootstrap_form formset.management_form %}
<div id="authors-list" class="well">
{% for form in formset %}
<div class="panel author-panel">
<div class="card author-panel">
<div class="card-body draggable">
<span class="handle bi bi-list"></span>
<div class="form-horizontal">
@ -47,7 +47,7 @@
{% endfor %}
</div>
<div id="empty-author-form" class="template">
<div class="panel author-panel">
<div class="card author-panel">
<div class="card-body draggable">
<span class="handle bi bi-list"></span>
<div class="form-horizontal">
@ -71,92 +71,9 @@
{% block js %}
<script src="{% static 'ietf/js/sortable.js' %}"></script>
<script src="{% static 'ietf/js/select2.js' %}"></script>
<script type="text/javascript">
const 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 ajax_url = '{% url "ietf.person.ajax.person_email_json" personid="123454321" %}';
var person_select2_input_selector = 'input.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(
$('<option></option>')
.attr('value', email.address)
.text(email.address)
);
});
if (ajax_data.length > 0) {
$(email_select).val(ajax_data[0].address);
}
}
}
function person_changed(event) {
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)
);
}
function initialize() {
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,
initialize: initialize
}
}
)()
$(document).ready(local_js.initialize);
var ajax_url = '{% url "ietf.person.ajax.person_email_json" personid="123454321" %}';
</script>
<script src="{% static 'ietf/js/edit_authors.js' %}"></script>
{% endblock %}

View file

@ -20,7 +20,7 @@
<div class="col-md-11">
{% for letter in alphabet_blocks %}
<div class="row anchor-target" id="{{letter.grouper}}">
<div class="panel ">
<div class="card ">
<div class="card-header">{{letter.grouper}}</div>
<div class="card-body">
<ul class="list-inline">

View file

@ -31,7 +31,7 @@
{% for label, groups in sections.items %}
<div class="anchor-target" id="{{label}}">
<div class="panel ">
<div class="card ">
<div class="card-header">{{label}}</div>
<div class="card-body">
@ -56,7 +56,7 @@
{% regroup groups by parent as grouped_by_areas %}
{% for area_grouping in grouped_by_areas %}
<div class="anchor-target" id="{{label}}-{{area_grouping.grouper.name|default:'unknown'|slugify}}">
<div class="panel ">
<div class="card ">
<div class="card-header">{{area_grouping.grouper.name|default:'Unknown area'}}</div>
<div class="card-body">
<table class="table table-sm table-striped tablesorter">

View file

@ -20,7 +20,7 @@
<div class="col-md-10">
{% for role_name in role_groups %}
<div class="row anchor-target" id="{{role_name.grouper|urlencode}}">
<div class="panel ">
<div class="card ">
<div class="card-header">{{role_name.grouper}}{{role_name.list|pluralize}}</div>
<div class="card-body">
<ul class="list-inline">

View file

@ -32,7 +32,7 @@
<form class="review-requests" method="post">{% csrf_token %}
{% for r in review_requests %}
<div class="panel review-request">
<div class="card review-request">
<div class="card-header">
<h5 class="card-title">

View file

@ -1,48 +1,35 @@
{# bs5ok #}
{% extends "base.html" %}
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}
{% load static %}
{% load ietf_filters %}
{% load django_bootstrap5 %}
{% block title %}Manage {{ group.name }} RFC stream{% endblock %}
{% block pagehead %}
{{ form.media.css }}
{% endblock %}
{% block pagehead %}{{ form.media.css }}{% endblock %}
{% block content %}
{% origin %}
<h1>Manage {{ group.name }} RFC stream</h1>
<p>
<b>Chair{{ chairs|pluralize }}:</b>
{% for chair in chairs %}
{{ chair.person.plain_name }} &lt;{{ chair.address }}&gt;
{% if not forloop.last %}, {% endif %}
{% if not forloop.last %},{% endif %}
{% endfor %}
</p>
<p>
Delegates can be assigned with permission to do the tasks of the
chair{{ chairs|pluralize }}. Note that in order to actually do so, the delegates need a
datatracker account. New accounts can be <a href="{% url "ietf.ietfauth.views.create_account" %}">created here</a>.
datatracker account. New accounts can be
<a href="{% url "ietf.ietfauth.views.create_account" %}">created here</a>
.
</p>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">Save</button>
<a class="btn btn-secondary float-end" href="{% url "ietf.group.views.streams" %}{{ group.acronym }}">Back</a>
<a class="btn btn-secondary float-end"
href="{% url "ietf.group.views.streams" %}{{ group.acronym }}">Back</a>
</form>
{% endblock %}
{% block js %}
{{ form.media.js }}
{% endblock %}
{% block js %}{{ form.media.js }}{% endblock %}

View file

@ -13,7 +13,7 @@
<h1>Milestones under review</h1>
{% for ad in ads %}
<div class="panel ">
<div class="card ">
<div class="card-header">{{ ad.plain_name }}</div>
<div class="card-body">
{% for g in ad.groups_needing_review %}

View file

@ -19,7 +19,7 @@
{% endif %}
<div class="alert alert-info">This form will link additional drafts to this session with a revision of "Current at time of presentation". For more fine grained control of versions, or to remove a draft from a session, adjust the sessions associated with a draft from the draft's main page.</div>
<div class="panel ">
<div class="card ">
<div class="card-header">Drafts already linked to this sesssion</div>
<div class="card-body">
<table class="table table-sm table-striped">
@ -37,7 +37,7 @@
</div>
</div>
<div class="panel ">
<div class="card ">
<div class="card-header">Additional drafts to link to this session</div>
<div class="card-body">
<form method="post">

View file

@ -24,8 +24,6 @@
#weekview iframe { height: 25em; }
{% endblock %}
{% block bodyAttrs %}data-bs-spy="scroll" data-bs-target="#affix" data-bs-offset="0" tabindex="0"{% endblock %}
{% block content %}
{% origin %}
@ -523,7 +521,6 @@
{% if personalize %}
agenda_filter.set_update_callback(function (e) {
handleFilterParamUpdate(e);
update_view(e);
});
document.getElementById('agenda-table')

View file

@ -5,7 +5,7 @@ Required parameter: meeting - meeting being displayed
{% endcomment %}
{% load agenda_custom_tags %}
<div class="mb-3">
<div class="mb-3 buttonlist">
<a class="btn btn-sm btn-outline-primary visually-hidden ical-link agenda-link filterable"
href="{% webcal_url 'ietf.meeting.views.agenda_ical' num=meeting.number %}">
Subscribe to personal agenda

View file

@ -25,7 +25,7 @@
.edit-meeting-schedule .edit-grid .timeslot.hidden-timeslot-type .time-label { color: transparent; ); }
.edit-meeting-schedule .session.hidden-purpose,
.edit-meeting-schedule .session.hidden-timeslot-type { filter: blur(3px); }
{% endblock morecss %}
{% endblock %}
{% block title %}{{ schedule.name }}: IETF {{ meeting.number }} meeting agenda{% endblock %}
@ -44,35 +44,29 @@
<p class="float-end">
{% if can_edit_properties %}
<a href="{% url "ietf.meeting.views.edit_schedule_properties" schedule.meeting.number schedule.owner_email schedule.name %}">Edit properties</a>
&middot;
<a class="btn btn-primary" href="{% url "ietf.meeting.views.edit_schedule_properties" schedule.meeting.number schedule.owner_email schedule.name %}">Edit properties</a>
{% endif %}
<a href="{% url "ietf.meeting.views.new_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">Copy agenda</a>
&middot;
<a class="btn btn-primary" href="{% url "ietf.meeting.views.new_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">Copy agenda</a>
<a href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>
<a class="btn btn-primary" href="{% url "ietf.meeting.views.list_schedules" num=meeting.number %}">Other Agendas</a>
</p>
<p>
<h1>
Agenda name: {{ schedule.name }}
&middot;
Owner: {{ schedule.owner }}
<br>
<small class="text-muted">
Owner: {{ schedule.owner }}</small>
</h1>
{% if not can_edit %}
&middot;
<strong>
<em>
<div class="alert alert-info">
You can't edit this schedule.
{% if schedule.is_official_record %}This is the official schedule for a meeting in the past.{% endif %}
Make a <a href="{% url "ietf.meeting.views.new_meeting_schedule" num=meeting.number owner=schedule.owner_email name=schedule.name %}">new agenda from this</a>.
</em>
</strong>
</div>
{% endif %}
</p>
<div class="edit-grid {% if not can_edit %}read-only{% endif %}">

View file

@ -37,7 +37,7 @@
<span class="time pull-right"></span>
{{ session.scheduling_label }} &middot; {{ session.requested_duration_in_hours }}h
{% if session.purpose_label %} &middot; {{ session.purpose_label }} {% endif %}
{% if session.attendees != None %} &middot; {{ session.attendees }} <i class="fa fa-user-o"></i> {% endif %}
{% if session.attendees != None %} &middot; {{ session.attendees }} <i class="bi bi-person"></i> {% endif %}
</strong>
</div>

View file

@ -7,7 +7,7 @@
{% block content %}
{% origin %}
<div class="panel col-md-5">
<div class="card col-md-5">
<div class="card-header">
Finalize IETF{{meeting.number}} Proceedings
</div>

View file

@ -8,7 +8,7 @@
{# Note: if called with show_agenda=True, calling template must load agenda_materials.js, needed by session_agenda_include.html #}
{% include "meeting/session_agenda_include.html" with slug=item.slug session=session timeslot=item.timeslot only %}
<!-- agenda pop-up button -->
<a data-bs-toggle="modal" data-bs-target="#modal-{{item.slug}}" title="Show meeting materials"><span class="bi bi-arrows-fullscreen"></span></a>
<a data-bs-toggle="modal" data-bs-target="#modal-{{item.slug}}"><span title="Show meeting materials" class="bi bi-arrows-fullscreen"></span></a>
<!-- materials tar file -->
<a href="/meeting/{{meeting.number}}/agenda/{{acronym}}-drafts.tgz" title="Download meeting materials as .tar archive"><span class="bi bi-file-zip"></span></a>
<!-- materials PDF file -->

View file

@ -8,8 +8,6 @@
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
{% endblock %}
{% block bodyAttrs %}data-bs-spy="scroll" data-bs-target="#affix"{% endblock %}
{% block title %}IETF {{ meeting.number }} preliminary &amp; interim materials{% endblock %}
{% block content %}
@ -286,4 +284,4 @@
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -54,30 +54,30 @@
{% if user|has_role:"Secretariat,Area Director,IAB" %}
{% if schedule != meeting.schedule %}
<li class="nav-item">
<a class="nav-link agenda-link filterable {% if selected == "by-room" %}active{% endif %}"
<a class="nav-link {% if selected == "by-room" %}active{% endif %}"
href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by
Room</a></li>
<li class="nav-item">
<a class="nav-link agenda-link filterable {% if selected == "by-type" %}active{% endif %}"
<a class="nav-link {% if selected == "by-type" %}active{% endif %}"
href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number name=schedule.name owner=schedule.owner.email %}">by
Type</a></li>
<li class="nav-item">
<a class="nav-link agenda-link filterable {% if selected == "room-view" %}active{% endif %}"
<a class="nav-link {% if selected == "room-view" %}active{% endif %}"
href="{% url 'ietf.meeting.views.room_view' num=meeting.number name=schedule.name owner=schedule.owner.email %}">Room
grid</a></li>
{% else %}
<li class="nav-item">
<a class="nav-link agenda-link filterable {% if selected == "by-room" %}active{% endif %}" href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number %}">by Room</a></li>
<a class="nav-link {% if selected == "by-room" %}active{% endif %}" href="{% url 'ietf.meeting.views.agenda_by_room' num=meeting.number %}">by Room</a></li>
<li class="nav-item">
<a class="nav-link agenda-link filterable {% if selected == "by-type" %}active{% endif %}" href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number %}">by Type</a></li>
<a class="nav-link {% if selected == "by-type" %}active{% endif %}" href="{% url 'ietf.meeting.views.agenda_by_type' num=meeting.number %}">by Type</a></li>
<li class="nav-item">
<a class="nav-link agenda-link filterable {% if selected == "room-view" %}active{% endif %}" href="{% url 'ietf.meeting.views.room_view' num=meeting.number %}">Room grid</a></li>
<a class="nav-link {% if selected == "room-view" %}active{% endif %}" href="{% url 'ietf.meeting.views.room_view' num=meeting.number %}">Room grid</a></li>
{% endif %}
{% endif %}
<li class="nav-item">
<a class="nav-link agenda-link filterable {% if selected == "floor-plan" %}active{% endif %}" href="{% url 'ietf.meeting.views.floor_plan' num=meeting.number %}">Floor plan</a></li>
<a class="nav-link {% if selected == "floor-plan" %}active{% endif %}" href="{% url 'ietf.meeting.views.floor_plan' num=meeting.number %}">Floor plan</a></li>
<li class="nav-item">
<a class="nav-link"agenda-link filterable href="{% url 'ietf.meeting.views.agenda' num=meeting.number ext='.txt' %}">
<a class="nav-link" href="{% url 'ietf.meeting.views.agenda' num=meeting.number ext='.txt' %}">
Plaintext
</a>
</li>

View file

@ -8,8 +8,6 @@
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
{% endblock %}
{% block bodyAttrs %}data-bs-spy="scroll" data-bs-target="#affix"{% endblock %}
{% block title %}Past Meetings{% endblock %}
{% block content %}
@ -71,4 +69,4 @@
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -14,7 +14,7 @@
<h1>
{% block content_header %}
Edit Proceedings Material<br>
<small>
<small class="text-muted">
{{ meeting }} {{ material_type.name }}
</small>
{% endblock %}
@ -58,4 +58,4 @@
{% endblock %}
</form>
{% endblock %}
{% endblock content %}
{% endblock content %}

View file

@ -18,7 +18,7 @@
{% endif %}
<div class="panel ">
<div class="card ">
<div class="card-header">Proceedings Materials</div>
<div class="card-body">
<table class="table table-sm table-striped">

View file

@ -1,22 +1,14 @@
{# bs5ok #}
{% extends "meeting/proceedings/edit_material_base.html" %}
{# Copyright The IETF Trust 2015-2021, All Rights Reserved #}
{% load django_bootstrap5 %}
{% block morecss %}
{{ form.media.css }}
{% endblock %}
{% block title %}
Upload {{ material_type.name }} for {{ meeting }} Proceedings
{% endblock %}
{% block morecss %}{{ form.media.css }}{% endblock %}
{% block title %}Upload {{ material_type.name }} for {{ meeting }} Proceedings{% endblock %}
{% block content_header %}
Upload Proceedings Material<br>
<small>
{{ meeting }} {{ material_type.name }}
</small>
Upload Proceedings Material
<br>
<small class="text-muted">{{ meeting }} {{ material_type.name }}</small>
{% endblock %}
{% block intro %}
<p>
{% if material is None %}
@ -28,16 +20,11 @@
{% endif %}
</p>
{% endblock %}
{% block edit_form %}
<form class="upload-material" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form form %}
{% block form_buttons %}{{ block.super }}{% endblock %}
</form>
{% endblock %}
{% block js %}
{{ form.media.js }}
{% endblock %}
{% block js %}{{ form.media.js }}{% endblock %}

View file

@ -8,8 +8,6 @@
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
{% endblock %}
{% block bodyAttrs %}data-bs-spy="scroll" data-bs-target="#affix"{% endblock %}
{% block title %}IETF {{ meeting.number }} timeslot requests{% endblock %}
{% block content %}
@ -142,4 +140,4 @@
{% block js %}
<script src="{% static "ietf/js/list.js" %}"></script>
{% endblock %}
{% endblock %}

View file

@ -15,7 +15,7 @@
{% endif %}
{% for schedules, own, label in schedule_groups %}
<div class="panel">
<div class="card">
<div class="card-header">
{{ label }}
{% if own %}

View file

@ -16,7 +16,7 @@
{% with acronym=session.historic_group.acronym %}
{% if session.agenda and show_agenda %}
<!-- agenda pop-up button -->
<a class="btn btn-outline-primary" role="button" data-bs-toggle="modal" data-bs-target="#modal-{{slug}}" title="Show meeting materials"><span class="bi bi-arrows-fullscreen"></span></a>
<button class="btn btn-outline-primary" role="button" data-bs-toggle="modal" data-bs-target="#modal-{{slug}}"><span title="Show meeting materials" class="bi bi-arrows-fullscreen"></span></button>
<!-- materials tar file -->
<a class="btn btn-outline-primary" role="button" href="/meeting/{{meeting.number}}/agenda/{{acronym}}-drafts.tgz" title="Download meeting materials as .tar archive"><span class="bi bi-file-zip"></span></a>
<!-- materials PDF file -->

View file

@ -30,7 +30,7 @@
{% with use_panels=unscheduled_sessions %}
{% if use_panels %}
<div class="panel ">
<div class="card ">
<div class="card-header">Scheduled Sessions</div>
<div class="card-body">
{% endif %}

View file

@ -52,7 +52,7 @@
</div>
{% endif %}
<div class="panel ">
<div class="card ">
<div class="card-header">Agenda, Minutes, and Bluesheets</div>
<div class="card-body">
<table class="table table-sm table-striped">
@ -93,7 +93,7 @@
{% endif %}
</div>
</div>
<div class="panel ">
<div class="card ">
<div class="card-header" data-bs-toggle="tooltip" title="Drag and drop to reorder slides">Slides</div>
<div class="card-body">
<table class="table table-sm table-striped slides" id="slides_{{session.pk}}">
@ -125,7 +125,7 @@
<div class="small">Drag-and-drop to reorder slides</div>
{% endif %}
</div>
<div class="panel ">
<div class="card ">
<div class="card-header">Drafts
</div>
<div class="card-body">

View file

@ -66,7 +66,7 @@
<th class="day-label"
colspan="{{date_slices|colWidth:day}}">
{{day|date:'D'}}&nbsp;({{day}})
<span class="fa fa-trash delete-button"
<span class="bi bi-trash delete-button"
title="Delete all on this day"
data-delete-scope="day"
data-date-id="{{ day.isoformat }}">
@ -85,7 +85,7 @@
{% for slot in slot_slices|lookup:day %}
<th class="time-label">
{{slot.time|date:'H:i'}}-{{slot.end_time|date:'H:i'}}
<span class="fa fa-trash delete-button"
<span class="bi bi-trash delete-button"
data-delete-scope="column"
data-date-id="{{ day.isoformat }}"
data-col-id="{{ day.isoformat }}T{{slot.time|date:'H:i'}}-{{slot.end_time|date:'H:i'}}"
@ -118,7 +118,7 @@
{% endif %}
<a class="new-timeslot-link {% if cell_ts %}hidden{% endif %}"
href="{% url "ietf.meeting.views.create_timeslot" num=meeting.number %}?day={{ day.toordinal }}&date={{ day|date:"Y-m-d" }}&location={{ room.pk }}&time={{ slot.time|date:'H:i' }}&duration={{ slot.duration }}">
<span class="fa fa-plus-square"></span>
<span class="bi bi-plus-square"></span>
</a>
{% endwith %}{% endfor %}
</td>

View file

@ -18,9 +18,9 @@
</div>
<div class="timeslot-buttons">
<a href="{% url 'ietf.meeting.views.edit_timeslot' num=ts.meeting.number slot_id=ts.id %}">
<span class="fa fa-pencil-square-o"></span>
<span class="bi bi-pencil"></span>
</a>
<span class="fa fa-trash delete-button" data-delete-scope="timeslot" title="Delete this timeslot"></span>
<span class="bi bi-trash delete-button" data-delete-scope="timeslot" title="Delete this timeslot"></span>
</div>
<div class="ts-type">{{ ts.type }}</div>
</div>

View file

@ -6,8 +6,7 @@
{% block pagehead %}
<link rel="stylesheet" href="{% static "ietf/css/list.css" %}">
<link rel="stylesheet" href="{% static "ietf/css/fullcalendar.css" %}">
<link rel="stylesheet" href="{% static "ietf/js/fullcalendar.css" %}">
{% endblock %}
{% block title %}Upcoming Meetings{% endblock %}
@ -35,12 +34,7 @@
{% block content %}
{% origin %}
<div class="row">
<div class="col-md-10">
<div class="row">
<div class="col-xs-6">
<h1>Upcoming Meetings</h1>
</div>
<div class="title-buttons col-xs-6">
<div>
<a title="iCalendar subscription for upcoming meetings" href="webcal://{{request.get_host}}{% url 'ietf.meeting.views.upcoming_ical' %}">
<i class="bi bi-share"></i>
@ -62,8 +56,6 @@
</select>
</div>
</div>
</div>
<p>For more on regular IETF meetings see <a href="https://www.ietf.org/meeting/upcoming.html">here</a></p>
<p>Meeting important dates are not included in upcoming meeting calendars. They have <a href="{% url 'ietf.meeting.views.important_dates' %}">their own calendar</a></p>
@ -150,10 +142,8 @@
<h3>No upcoming meetings</h3>
{% endif %}
{% endcache %}
</div>
</div>
<div class="row">
<div class="col-md-10">
<div class="tz-display text-right">
<label for="timezone-select-bottom">Time zone: </label>
<small>
@ -162,13 +152,10 @@
</small>
<select class="tz-select" id="timezone-select-bottom"></select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-10">
<div id="calendar"></div>
</div>
</div>
{% endblock %}
{% block js %}
@ -279,14 +266,14 @@
*/
var calendarEl = document.getElementById('calendar')
event_calendar = new FullCalendar(calendarEl, {
plugins: ['dayGridPlugin'],
plugins: [ dayGridPlugin ],
initialView: 'dayGridMonth',
// displayEventTime: false,
// events: function (fInfo, success) {success(display_events)},
// eventRender: function (info) {
// $(info.el).tooltip({ title: info.event.title })
// },
// timeFormat: 'H:mm',
displayEventTime: false,
events: function (fInfo, success) {success(display_events)},
eventDidMount: function (info) {
$(info.el).tooltip({ title: info.event.title })
},
eventDisplay: 'block'
})
event_calendar.render()
}

View file

@ -1,6 +1,7 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{% load origin %}{% origin %}
{% load static %}
{# FIXME: the weekview only renders correctly in quirks mode, i.e., not in HTML5 #}
<html>
<head>
<meta charset="utf-8"/>

View file

@ -1,4 +1,4 @@
<div class="panel ">
<div class="card ">
<div class="card-header"><h3>{{title}}</h3></div>
<div class="card-body">
{% for n in list %}{{n.email.address}}{% if not forloop.last %}, {%endif %}{% endfor %}

View file

@ -18,7 +18,7 @@
{% with title='Accepted Nominees Without a Questionnaire Response' list=noresp %}
{% include 'nomcom/email_list_panel.html' %}
{% endwith %}
<div class="panel ">
<div class="card ">
<div class="card-header"><h3>Accepted Nominees by Position</h3></div>
<div class="card-body">
{% for pos, accepts in bypos.items %}

View file

@ -116,7 +116,7 @@
</form>
{% if form.topic %}
<div class="panel ">
<div class="card ">
<div class="card-header">Description: {{form.topic.subject}}</div>
<div class="card-body">{{form.topic.get_description|safe}}</div>
</div>

View file

@ -1,10 +1,10 @@
<div class="panel ">
<div class="card ">
<div class="card-header"><a data-bs-toggle="collapse" href="#generic_iesg_reqs_{{position.name|slugify}}" class="generic_iesg_reqs_header">General IESG Requirements</a></div>
<div id="generic_iesg_reqs_{{position.name|slugify}}" class="card-body collapse in">
{{generic_iesg_reqs|safe}}
</div>
</div>
<div class="panel ">
<div class="card ">
<div class="card-header">{{position.name}} Specific Requirements</div>
<div class="card-body">
{{specific_reqs|safe}}

View file

@ -16,7 +16,7 @@
{% if positions %}
{% regroup positions by is_open as posgroups %}
{% for group in posgroups %}
<div class="panel ">
<div class="card ">
<div class="card-header"><h3>{{ group.grouper| yesno:"Open Positions,Closed Positions"}}</h3></div>
<div class="card-body">
{% for position in group.list %}

View file

@ -31,7 +31,7 @@
{% endif %}
</dl>
<div class = "panel ">
<div class = "card ">
<p class='pasted'>{{ template.content }}</p>
</div>

View file

@ -20,7 +20,7 @@
{% regroup nominees_feedback by nominee.staterank as stateranked_nominees %}
{% for staterank in stateranked_nominees %}
<div class="panel ">
<div class="card ">
<div class="card-header">
{% if staterank.grouper == 0 %}
<h6 class="anchor-target" id="accepted">Accepted nomination for at least one position</h6>
@ -70,7 +70,7 @@
<h2 class="anchor-target" id="topics">Feedback related to topics</h2>
<div class="panel ">
<div class="card ">
<div class="card-body">
<table class="table table-sm table-striped">
<thead>

View file

@ -17,7 +17,7 @@
{% regroup volunteers by eligible as volunteers_by_eligibility %}
{% for eligibility_group in volunteers_by_eligibility %}
<div class="panel ">
<div class="card ">
<div class="card-header">{{ eligibility_group.grouper|yesno:"Eligible, Not Eligible"}}</div>
<div class="card-body">
<table class="table table-sm table-striped tablesorter">

View file

@ -257,28 +257,69 @@ class SearchableField(forms.MultipleChoiceField):
)
def prepare_value(self, value):
result = super(SearchableField, self).prepare_value(value)
# print("prepare_value", 1, value)
# result = super(SearchableField, self).prepare_value(value)
if not value:
value = ""
# if not value:
# value = ""
# print("prepare_value", 2, value)
if isinstance(value, list):
if len(value) == 0:
value = None
elif len(value) == 1:
value = value[0]
else:
if not isinstance(value[0], self.model):
qs = self.get_model_instances(value[0])
for val in value[1:]:
qs = qs.union(self.get_model_instances(val))
value = qs
# print("prepare_value", 3, value)
if isinstance(value, int):
value = str(value)
if type(value) in (str, list):
value = self.get_model_instances(value)
# print("prepare_value", 4, value)
if isinstance(value, str):
if value == "":
value = self.model.objects.none()
else:
value = self.get_model_instances([value])
# print("prepare_value", 5, value)
if isinstance(value, self.model):
value = [value]
if value.count() > 0:
# print("prepare_value", 6, value)
if value:
pre = self.make_select2_data(value)
# print("value", value)
# print("pre", pre)
for d in pre:
# print("d", d)
if isinstance(value, list):
# print(dir(value[0]))
# if hasattr(value[0], "id"):
d["selected"] = any([v.pk == d["id"] for v in value])
else:
d["selected"] = value.exists() and value.filter(pk__in=[d["id"]]).exists()
self.widget.attrs["data-pre"] = json.dumps({
d['id']: d for d in self.make_select2_data(value)
d['id']: d for d in pre
})
# print(self.widget.attrs["data-pre"])
# print("prepare_value", 7, value)
# doing this in the constructor is difficult because the URL
# patterns may not have been fully constructed there yet
self.widget.attrs["data-ajax-url"] = self.ajax_url()
result = value
# print("prepare_value", 99, result)
return result
def clean(self, pks):
if pks is None:
return None
# print("clean", 1, pks)
try:
objs = self.model.objects.filter(pk__in=pks)
except ValueError as e:
@ -295,6 +336,8 @@ class SearchableField(forms.MultipleChoiceField):
'entry' if self.max_entries == 1 else 'entries',
))
# print("clean", 99, self.max_entries, objs)
return objs.first() if self.max_entries == 1 else objs

988
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,7 @@
"flot": "^4.2.2",
"highcharts": "^9.3.1",
"jquery": "^3.6.0",
"jquery-ui": "^1.13.0",
"jquery-ui-dist": "^1.13.0",
"js-cookie": "^3.0.1",
"list.js": "^2.3.1",
"moment": "^2.29.1",
@ -30,6 +30,7 @@
"ietf": {
"distDir": "ietf/static/dist/ietf",
"source": [
"ietf/static/css/datepicker.scss",
"ietf/static/css/ietf.scss",
"ietf/static/css/jquery-ui.scss",
"ietf/static/css/liaisons.css",
@ -57,6 +58,8 @@
"ietf/static/js/edit-meeting-schedule.js",
"ietf/static/js/edit-meeting-timeslots-and-misc-sessions.js",
"ietf/static/js/edit-milestones.js",
"ietf/static/js/edit_action_holders.js",
"ietf/static/js/edit_authors.js",
"ietf/static/js/flot.js",
"ietf/static/js/fullcalendar.js",
"ietf/static/js/highcharts-export-data.js",
@ -85,6 +88,7 @@
"ietf/static/js/stats.js",
"ietf/static/js/status-change-edit-relations.js",
"ietf/static/js/timezone.js",
"ietf/static/js/upload-material.js",
"ietf/static/js/upload_bofreq.js",
"ietf/static/js/week-view.js",
"ietf/static/js/zxcvbn.js"