Merged in [18651] from jennifer@painless-security.com:
Retrieve session agenda, slides, and minutes each time agenda modal is opened. Fixes #3050.
- Legacy-Id: 18658
Note: SVN reference [18651] has been migrated to Git commit bbf04c3fbe
This commit is contained in:
commit
35f4f4c8a8
|
@ -1,5 +1,7 @@
|
|||
# -*- conf-mode -*-
|
||||
|
||||
/personal/kivinen/7.19.1.dev0@18633
|
||||
|
||||
/personal/lars/7.12.1.dev0@18335
|
||||
/personal/lars/7.12.1.dev0@18330 # Merge attempted but needed re-working; some cherry-picking done
|
||||
/personal/lars/7.12.1.dev0@18329
|
||||
|
|
|
@ -18,6 +18,7 @@ from django.db.models import F
|
|||
import debug # pyflakes:ignore
|
||||
|
||||
from ietf.doc.factories import DocumentFactory
|
||||
from ietf.doc.models import State
|
||||
from ietf.group import colors
|
||||
from ietf.person.models import Person
|
||||
from ietf.group.models import Group
|
||||
|
@ -784,7 +785,7 @@ class AgendaTests(MeetingTestCase):
|
|||
ics_url = self.absreverse('ietf.meeting.views.agenda_ical')
|
||||
|
||||
# parse out the events
|
||||
agenda_rows = self.driver.find_elements_by_css_selector('[id^="row-"')
|
||||
agenda_rows = self.driver.find_elements_by_css_selector('[id^="row-"]')
|
||||
visible_rows = [r for r in agenda_rows if r.is_displayed()]
|
||||
sessions = [self.session_from_agenda_row_id(row.get_attribute("id"))
|
||||
for row in visible_rows]
|
||||
|
@ -798,6 +799,111 @@ class AgendaTests(MeetingTestCase):
|
|||
expected_event_uids=expected_uids,
|
||||
expected_event_count=len(sessions))
|
||||
|
||||
def test_session_materials_modal(self):
|
||||
"""Test opening and re-opening a session materals modal
|
||||
|
||||
This currently only tests the slides to ensure that changes to these are picked up
|
||||
without reloading the main agenda page. This should also test that the agenda and
|
||||
minutes are displayed and updated correctly, but problems with WebDriver/Selenium/Chromedriver
|
||||
are blocking this.
|
||||
"""
|
||||
session = self.meeting.session_set.filter(group__acronym="mars").first()
|
||||
assignment = session.official_timeslotassignment()
|
||||
slug = assignment.slug()
|
||||
|
||||
url = self.absreverse('ietf.meeting.views.agenda')
|
||||
self.driver.get(url)
|
||||
|
||||
# modal should start hidden
|
||||
modal_div = self.driver.find_element_by_css_selector('div#modal-%s' % slug)
|
||||
self.assertFalse(modal_div.is_displayed())
|
||||
|
||||
# Click the 'materials' button
|
||||
open_modal_button = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, '[data-target="#modal-%s"]' % slug)
|
||||
),
|
||||
'Modal open button not found or not clickable',
|
||||
)
|
||||
open_modal_button.click()
|
||||
WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.visibility_of(modal_div),
|
||||
'Modal did not become visible after clicking open button',
|
||||
)
|
||||
|
||||
# Check that we have the expected slides
|
||||
not_deleted_slides = session.materials.filter(
|
||||
type='slides'
|
||||
).exclude(
|
||||
states__type__slug='slides',states__slug='deleted'
|
||||
)
|
||||
self.assertGreater(not_deleted_slides.count(), 0) # make sure this isn't a pointless test
|
||||
for slide in not_deleted_slides:
|
||||
anchor = self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
self.assertIsNotNone(anchor)
|
||||
|
||||
deleted_slides = session.materials.filter(
|
||||
type='slides', states__type__slug='slides', states__slug='deleted'
|
||||
)
|
||||
self.assertGreater(deleted_slides.count(), 0) # make sure this isn't a pointless test
|
||||
for slide in deleted_slides:
|
||||
with self.assertRaises(NoSuchElementException):
|
||||
self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
|
||||
# Now close the modal
|
||||
close_modal_button = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, '.modal-footer button[data-dismiss="modal"]')
|
||||
),
|
||||
'Modal close button not found or not clickable',
|
||||
)
|
||||
close_modal_button.click()
|
||||
WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.invisibility_of_element(modal_div),
|
||||
'Modal was not hidden after clicking close button',
|
||||
)
|
||||
|
||||
# Modify the session info
|
||||
newly_deleted_slide = not_deleted_slides.first()
|
||||
newly_undeleted_slide = deleted_slides.first()
|
||||
newly_deleted_slide.set_state(State.objects.get(type="slides", slug="deleted"))
|
||||
newly_undeleted_slide.set_state(State.objects.get(type="slides", slug="active"))
|
||||
|
||||
# Click the 'materials' button
|
||||
open_modal_button = WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.element_to_be_clickable(
|
||||
(By.CSS_SELECTOR, '[data-target="#modal-%s"]' % slug)
|
||||
),
|
||||
'Modal open button not found or not clickable for refresh test',
|
||||
)
|
||||
open_modal_button.click()
|
||||
WebDriverWait(self.driver, 2).until(
|
||||
expected_conditions.visibility_of(modal_div),
|
||||
'Modal did not become visible after clicking open button for refresh test',
|
||||
)
|
||||
|
||||
# Check that we now see the updated slides
|
||||
not_deleted_slides = session.materials.filter(
|
||||
type='slides'
|
||||
).exclude(
|
||||
states__type__slug='slides',states__slug='deleted'
|
||||
)
|
||||
self.assertNotIn(newly_deleted_slide, not_deleted_slides)
|
||||
self.assertIn(newly_undeleted_slide, not_deleted_slides)
|
||||
for slide in not_deleted_slides:
|
||||
anchor = self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
self.assertIsNotNone(anchor)
|
||||
|
||||
deleted_slides = session.materials.filter(
|
||||
type='slides', states__type__slug='slides', states__slug='deleted'
|
||||
)
|
||||
self.assertIn(newly_deleted_slide, deleted_slides)
|
||||
self.assertNotIn(newly_undeleted_slide, deleted_slides)
|
||||
for slide in deleted_slides:
|
||||
with self.assertRaises(NoSuchElementException):
|
||||
self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
|
||||
|
||||
|
||||
@skipIf(skip_selenium, skip_message)
|
||||
class InterimTests(MeetingTestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -158,12 +158,16 @@ class MeetingTests(TestCase):
|
|||
self.assertIn(time_interval, agenda_content)
|
||||
self.assertIn(registration_text, agenda_content)
|
||||
|
||||
# Make sure there's a frame for the agenda and it points to the right place
|
||||
self.assertTrue(any([session.materials.get(type='agenda').get_href() in x.attrib["data-src"] for x in q('tr div.modal-body div.frame')]))
|
||||
|
||||
# Make sure undeleted slides are present and deleted slides are not
|
||||
self.assertTrue(any([session.materials.filter(type='slides').exclude(states__type__slug='slides',states__slug='deleted').first().title in x.text for x in q('tr div.modal-body ul a')]))
|
||||
self.assertFalse(any([session.materials.filter(type='slides',states__type__slug='slides',states__slug='deleted').first().title in x.text for x in q('tr div.modal-body ul a')]))
|
||||
# Make sure there's a frame for the session agenda and it points to the right place
|
||||
assignment = session.official_timeslotassignment()
|
||||
assignment_url = urlreverse('ietf.meeting.views.assignment_materials',
|
||||
kwargs=dict(assignment_id=assignment.pk))
|
||||
self.assertTrue(
|
||||
any(
|
||||
[assignment_url in x.attrib["data-src"]
|
||||
for x in q('tr div.modal-body div.assignment-materials')]
|
||||
)
|
||||
)
|
||||
|
||||
# future meeting, no agenda
|
||||
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=future_meeting.number)))
|
||||
|
@ -844,6 +848,50 @@ class MeetingTests(TestCase):
|
|||
self.assertIn('STATUS:CANCELLED',unicontent(r))
|
||||
self.assertNotIn('STATUS:CONFIRMED',unicontent(r))
|
||||
|
||||
def test_assignment_materials(self):
|
||||
meeting = make_meeting_test_data()
|
||||
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
|
||||
|
||||
for assignment in session.timeslotassignments.all():
|
||||
url = urlreverse('ietf.meeting.views.assignment_materials',
|
||||
kwargs=dict(assignment_id=assignment.pk))
|
||||
r = self.client.get(url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
q = PyQuery(r.content)
|
||||
|
||||
agenda_div = q('div.agenda-frame')
|
||||
self.assertIsNotNone(agenda_div)
|
||||
self.assertEqual(agenda_div.attr('data-src'), session.agenda().get_href())
|
||||
|
||||
minutes_div = q('div.minutes-frame')
|
||||
self.assertIsNotNone(minutes_div)
|
||||
self.assertEqual(minutes_div.attr('data-src'), session.minutes().get_href())
|
||||
|
||||
# Make sure undeleted slides are present and deleted slides are not
|
||||
not_deleted_slides = session.materials.filter(
|
||||
type='slides'
|
||||
).exclude(
|
||||
states__type__slug='slides',states__slug='deleted'
|
||||
)
|
||||
self.assertGreater(not_deleted_slides.count(), 0) # make sure this isn't a pointless test
|
||||
|
||||
deleted_slides = session.materials.filter(
|
||||
type='slides', states__type__slug='slides', states__slug='deleted'
|
||||
)
|
||||
self.assertGreater(deleted_slides.count(), 0) # make sure this isn't a pointless test
|
||||
|
||||
# live slides should be found
|
||||
for slide in not_deleted_slides:
|
||||
self.assertTrue(q('ul li a:contains("%s")' % slide.title))
|
||||
|
||||
# deleted slides should not be found
|
||||
for slide in deleted_slides:
|
||||
self.assertFalse(q('ul li a:contains("%s")' % slide.title))
|
||||
|
||||
|
||||
for slide in session.slides():
|
||||
self.assertContains(r, slide.title)
|
||||
|
||||
class ReorderSlidesTests(TestCase):
|
||||
|
||||
def test_add_slides_to_session(self):
|
||||
|
|
|
@ -111,6 +111,7 @@ urlpatterns = [
|
|||
# First patterns which start with unique strings
|
||||
url(r'^$', views.current_materials),
|
||||
url(r'^ajax/get-utc/?$', views.ajax_get_utc),
|
||||
url(r'^assignment/(?P<assignment_id>\d+)/materials.html$', views.assignment_materials),
|
||||
url(r'^interim/announce/?$', views.interim_announce),
|
||||
url(r'^interim/announce/(?P<number>[A-Za-z0-9._+-]+)/?$', views.interim_send_announcement),
|
||||
url(r'^interim/skip_announce/(?P<number>[A-Za-z0-9._+-]+)/?$', views.interim_skip_announcement),
|
||||
|
|
|
@ -241,7 +241,6 @@ def materials_document(request, document, num=None, ext=None):
|
|||
raise Http404("File not found: %s" % filename)
|
||||
|
||||
old_proceedings_format = meeting.number.isdigit() and int(meeting.number) <= 96
|
||||
|
||||
if settings.MEETING_MATERIALS_SERVE_LOCALLY or old_proceedings_format:
|
||||
with io.open(filename, 'rb') as file:
|
||||
bytes = file.read()
|
||||
|
@ -1300,6 +1299,17 @@ def diff_schedules(request, num):
|
|||
'to_schedule': to_schedule,
|
||||
})
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def assignment_materials(request, assignment_id):
|
||||
"""Assignment details for agenda page pop-up"""
|
||||
assignments = SchedTimeSessAssignment.objects.filter(pk=int(assignment_id))
|
||||
if len(assignments) == 0:
|
||||
raise Http404('No such assignment')
|
||||
assert len(assignments) == 1
|
||||
meeting = assignments[0].timeslot.meeting # timeslot is guaranteed to be non-null
|
||||
assignments = preprocess_assignments_for_agenda(assignments, meeting)
|
||||
assignment = assignments[0]
|
||||
return render(request, 'meeting/assignment_materials.html', dict(item=assignment))
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""):
|
||||
|
|
|
@ -25,6 +25,9 @@
|
|||
background-color: inherit !important;
|
||||
border: none !important;
|
||||
}
|
||||
.assignment-materials .agenda-frame,.minutes-frame {
|
||||
white-space: normal;
|
||||
}
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %}
|
||||
|
@ -389,39 +392,75 @@
|
|||
agenda_filter.set_update_callback(update_view);
|
||||
agenda_filter.enable();
|
||||
|
||||
/**
|
||||
* Retrieve and display materials for a session
|
||||
*
|
||||
* If output_elt exists and has a "data-src" attribute, retrieves the document
|
||||
* from that URL and displays under output_elt. Handles text/plain, text/markdown,
|
||||
* and text/html.
|
||||
*
|
||||
* @param output_elt Element, probably a div, to hold the output
|
||||
*/
|
||||
function retrieve_session_materials(output_elt) {
|
||||
if (!output_elt) {return;}
|
||||
output_elt = $(output_elt);
|
||||
var data_src = output_elt.attr("data-src");
|
||||
if (!data_src) {
|
||||
output_elt.html("<p>Error: missing data-src attribute</p>");
|
||||
} else {
|
||||
output_elt.html("<p>Loading " + data_src + "...</p>");
|
||||
outer_xhr = $.get(data_src)
|
||||
outer_xhr.done(function(data, status, xhr) {
|
||||
var t = xhr.getResponseHeader("content-type");
|
||||
if (!t) {
|
||||
data = "<p>Error retrieving " + data_src
|
||||
+ ": Missing content-type in response header</p>";
|
||||
} else if (t.indexOf("text/plain") > -1) {
|
||||
data = "<pre class='agenda'>" + data + "</pre>";
|
||||
} else if (t.indexOf("text/markdown") > -1) {
|
||||
data = "<pre class='agenda'>" + data + "</pre>";
|
||||
} else if(t.indexOf("text/html") > -1) {
|
||||
// nothing to do here
|
||||
} else {
|
||||
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
|
||||
}
|
||||
output_elt.html(data);
|
||||
}).fail(function() {
|
||||
output_elt.html("<p>Error retrieving " + data_src
|
||||
+ ": (" + outer_xhr.status.toString() + ") "
|
||||
+ outer_xhr.statusText + "</p>");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve contents of a session materials modal
|
||||
*
|
||||
* Expects output_elt to exist and have a "data-src" attribute. Retrieves the
|
||||
* contents of that URL, then attempts to populate the .agenda-frame and
|
||||
* .minutes-frame elements.
|
||||
*
|
||||
* @param output_elt Element, probably a div, to hold the output
|
||||
*/
|
||||
function retrieve_session_modal(output_elt) {
|
||||
if (!output_elt) {return;}
|
||||
output_elt = $(output_elt);
|
||||
var data_src = output_elt.attr("data-src");
|
||||
if (!data_src) {
|
||||
output_elt.html("<p>Error: missing data-src attribute</p>");
|
||||
} else {
|
||||
output_elt.html("<p>Loading...</p>");
|
||||
$.get(data_src).done(function(data) {
|
||||
output_elt.html(data);
|
||||
retrieve_session_materials(output_elt.find(".agenda-frame"));
|
||||
retrieve_session_materials(output_elt.find(".minutes-frame"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(".modal").on("show.bs.modal", function () {
|
||||
var i = $(this).find(".frame");
|
||||
if ($(i).data("src")) {
|
||||
$.get($(i).data("src"), function (data, status, xhr) {
|
||||
var t = xhr.getResponseHeader("content-type");
|
||||
if (t.indexOf("text/plain") > -1) {
|
||||
data = "<pre class='agenda'>" + data + "</pre>";
|
||||
} else if (t.indexOf("text/markdown") > -1) {
|
||||
data = "<pre class='agenda'>" + data + "</pre>";
|
||||
} else if(t.indexOf("text/html") > -1) {
|
||||
// nothing to do here
|
||||
} else {
|
||||
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
|
||||
}
|
||||
$(i).html(data);
|
||||
});
|
||||
}
|
||||
var j = $(this).find(".frame2");
|
||||
if ($(j).data("src")) {
|
||||
$.get($(j).data("src"), function (data, status, xhr) {
|
||||
var t = xhr.getResponseHeader("content-type");
|
||||
if (t.indexOf("text/plain") > -1) {
|
||||
data = "<pre class='agenda'>" + data + "</pre>";
|
||||
} else if (t.indexOf("text/markdown") > -1) {
|
||||
data = "<pre class='agenda'>" + data + "</pre>";
|
||||
} else if(t.indexOf("text/html") > -1) {
|
||||
// nothing to do here
|
||||
} else {
|
||||
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
|
||||
}
|
||||
$(j).html(data);
|
||||
});
|
||||
}
|
||||
retrieve_session_modal($(this).find(".assignment-materials"));
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
42
ietf/templates/meeting/assignment_materials.html
Normal file
42
ietf/templates/meeting/assignment_materials.html
Normal file
|
@ -0,0 +1,42 @@
|
|||
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
|
||||
{% load origin %}{% origin %}
|
||||
{% load static %}
|
||||
{% load textfilters %}
|
||||
{% load ietf_filters %}
|
||||
{% with item.session.agenda as agenda %}
|
||||
{% if agenda %}
|
||||
{% if agenda.file_extension == "txt" or agenda.file_extension == "md" or agenda.file_extension == "html" or agenda.file_extension == "htm" %}
|
||||
<h4>Agenda</h4>
|
||||
<div class="agenda-frame" data-src="{{ agenda.get_href }}"></div>
|
||||
{% else %}
|
||||
<span class="label label-info">Agenda submitted as {{ agenda.file_extension|upper }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="label label-warning">No agenda submitted</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% if item.session.slides %}
|
||||
<h4>Slides</h4>
|
||||
<ul class="fa-ul list-unstyled">
|
||||
{% for slide in item.session.slides %}
|
||||
<li>
|
||||
<span class="fa-li fa fa-file-{{ slide.file_extension|lower }}-o"></span>
|
||||
<a href="{{ slide.get_versionless_href }}">{{ slide.title|clean_whitespace }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% with item.session.minutes as minutes %}
|
||||
{% if minutes %}
|
||||
{% if minutes.file_extension == "txt" or minutes.file_extension == "md" or minutes.file_extension == "html" or minutes.file_extension == "htm" %}
|
||||
<h4>Minutes</h4>
|
||||
<div class="minutes-frame" data-src="{{ minutes.get_href }}"></div>
|
||||
{% else %}
|
||||
<span class="label label-info">Minutes submitted as {{ minutes.file_extension|upper }}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="label label-warning">No minutes submitted</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
|
@ -14,43 +14,9 @@
|
|||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% with item.session.agenda as agenda %}
|
||||
{% if agenda %}
|
||||
{% if agenda.file_extension == "txt" or agenda.file_extension == "md" or agenda.file_extension == "html" or agenda.file_extension == "htm" %}
|
||||
<h4>Agenda</h4>
|
||||
<div class="frame" data-src="{{agenda.get_href}}"></div>
|
||||
{% else %}
|
||||
<span class="label label-info">Agenda submitted as {{agenda.file_extension|upper}}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="label label-warning">No agenda submitted</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
{% if item.session.slides %}
|
||||
<h4>Slides</h4>
|
||||
<ul class="fa-ul list-unstyled">
|
||||
{% for slide in item.session.slides %}
|
||||
<li>
|
||||
<span class="fa-li fa fa-file-{{slide.file_extension|lower}}-o"></span>
|
||||
<a href="{{ slide.get_versionless_href }}">{{ slide.title|clean_whitespace }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% with item.session.minutes as minutes %}
|
||||
{% if minutes %}
|
||||
{% if minutes.file_extension == "txt" or minutes.file_extension == "md" or minutes.file_extension == "html" or minutes.file_extension == "htm" %}
|
||||
<h4>Minutes</h4>
|
||||
<div class="frame2" data-src="{{minutes.get_href}}"></div>
|
||||
{% else %}
|
||||
<span class="label label-info">Minutes submitted as {{minutes.file_extension|upper}}</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="label label-warning">No minutes submitted</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="assignment-materials"
|
||||
data-src="{% url 'ietf.meeting.views.assignment_materials' assignment_id=item.pk %}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
|
|
Loading…
Reference in a new issue