Commit b4698e40 by Matjaz Gregoric

Add ability for staff to always view the results.

Course staff users can now view the results without voting.
The can also see the results for polls/surveys marked as 'private'.

If user has sufficient permissions, a 'View results' link is shown
on the bottom of the component in the LMS.
parent 9a8c6c65
......@@ -34,6 +34,11 @@ from xblock.fragment import Fragment
from xblockutils.publish_event import PublishEventMixin
from xblockutils.resources import ResourceLoader
try:
from courseware.access import has_access
except ImportError:
has_access = None
class ResourceMixin(object):
loader = ResourceLoader(__name__)
......@@ -166,6 +171,16 @@ class PollBase(XBlock, ResourceMixin, PublishEventMixin):
return True
return False
def can_view_private_results(self):
"""
Checks to see if the user has permissions to view private results.
This only works inside the LMS.
"""
if has_access and hasattr(self.runtime, 'user') and hasattr(self.runtime, 'course_id'):
return has_access(self.runtime.user, 'staff', self, self.runtime.course_id)
else:
return False
@staticmethod
def get_max_submissions(data, result, private_results):
"""
......@@ -305,6 +320,7 @@ class PollBlock(PollBase):
'can_vote': self.can_vote(),
'max_submissions': self.max_submissions,
'submissions_count': self.submissions_count,
'can_view_private_results': self.can_view_private_results(),
})
if self.choice:
......@@ -346,7 +362,7 @@ class PollBlock(PollBase):
@XBlock.json_handler
def get_results(self, data, suffix=''):
if self.private_results:
if self.private_results and not self.can_view_private_results():
detail, total = {}, None
else:
self.publish_event_from_dict(self.event_namespace + '.view_results', {})
......@@ -512,6 +528,7 @@ class SurveyBlock(PollBase):
'can_vote': self.can_vote(),
'submissions_count': self.submissions_count,
'max_submissions': self.max_submissions,
'can_view_private_results': self.can_view_private_results(),
})
return self.create_fragment(
......@@ -554,7 +571,7 @@ class SurveyBlock(PollBase):
tally = []
questions = OrderedDict(self.markdown_items(self.questions))
default_answers = OrderedDict([(answer, 0) for answer, __ in self.answers])
choices = self.choices
choices = self.choices or {}
total = 0
self.clean_tally()
source_tally = self.tally
......@@ -585,7 +602,7 @@ class SurveyBlock(PollBase):
highest = 0
top_index = None
for index, answer in enumerate(question['answers']):
if answer['key'] == choices[question['key']]:
if answer['key'] == choices.get(question['key']):
answer['choice'] = True
# Find the most popular choice.
if answer['count'] > highest:
......@@ -595,7 +612,8 @@ class SurveyBlock(PollBase):
answer['percent'] = round(answer['count'] / float(total) * 100)
except ZeroDivisionError:
answer['percent'] = 0
question['answers'][top_index]['top'] = True
if top_index is not None:
question['answers'][top_index]['top'] = True
return tally, total
......@@ -661,7 +679,7 @@ class SurveyBlock(PollBase):
@XBlock.json_handler
def get_results(self, data, suffix=''):
if self.private_results:
if self.private_results and not self.can_view_private_results():
detail, total = {}, None
else:
self.publish_event_from_dict(self.event_namespace + '.view_results', {})
......
......@@ -211,3 +211,8 @@ th.survey-answer {
.poll-submissions-count {
font-weight: bold;
}
.view-results-button-wrapper {
text-align: right;
cursor: pointer;
}
<script id="poll-results-template" type="text/html">
<h3 class="poll-header">{{display_name}}</h3>
<div class="poll-question-container">{{{question}}}</div>
<ul class="poll-answers-results">
<ul class="poll-answers-results poll-results">
{{#each tally}}
<li class="poll-result">
<div class="poll-result-input-container">
......
<script id="survey-results-template" type="text/html">
<h3 class="poll-header">{{block_name}}</h3>
<table class="survey-table">
<table class="survey-table poll-results">
<thead>
<tr>
<th></th>
......
......@@ -26,19 +26,23 @@
</ul>
<input class="input-main" type="button" name="poll-submit" value="{% if choice %}Resubmit{% else %}Submit{% endif %}" disabled />
</form>
<div class="poll-voting-thanks{% if not choice or can_vote %} poll-hidden{% endif %}"><span>Thank you for your submission!</span></div>
<div class="poll-submissions-count poll-hidden">
You have used <span class="poll-current-count">{{ submissions_count }}</span>
out of <span class="poll-max-submissions">{{ max_submissions }}</span> submissions.
<div class="poll-voting-thanks{% if not choice or can_vote %} poll-hidden{% endif %}"><span>Thank you for your submission!</span></div>
<div class="poll-submissions-count poll-hidden">
You have used <span class="poll-current-count">{{ submissions_count }}</span>
out of <span class="poll-max-submissions">{{ max_submissions }}</span> submissions.
</div>
{% if feedback %}
<div class="poll-feedback-container{% if not choice %} poll-hidden{% endif %}">
<hr />
<h3 class="poll-header">Feedback</h3>
<div class="poll-feedback">
{{feedback|safe}}
</div>
{% if feedback %}
<div class="poll-feedback-container{% if not choice %} poll-hidden{% endif %}">
<hr />
<h3 class="poll-header">Feedback</h3>
<div class="poll-feedback">
{{feedback|safe}}
</div>
</div>
{% endif %}
</div>
{% endif %}
{% if can_view_private_results %}
<div class="view-results-button-wrapper"><a class="view-results-button">View results</a></div>
{% endif %}
{% endif %}
</div>
......@@ -47,5 +47,9 @@
</div>
</div>
{% endif %}
{% if can_view_private_results %}
<div class="view-results-button-wrapper"><a class="view-results-button">View results</a></div>
{% endif %}
{% endif %}
</div>
......@@ -10,10 +10,12 @@ function PollUtil (runtime, element, pollType) {
this.submit = $('input[type=button]', element);
this.answers = $('input[type=radio]', element);
this.resultsTemplate = Handlebars.compile($("#" + pollType + "-results-template", element).html());
this.viewResultsButton = $('.view-results-button', element);
this.viewResultsButton.click(this.getResults);
// If the submit button doesn't exist, the user has already
// selected a choice. Render results instead of initializing machinery.
if (! self.submit.length) {
self.getResults({'success': true});
self.onSubmit({'success': true});
return false;
}
var max_submissions = parseInt($('.poll-max-submissions', element).text());
......@@ -42,7 +44,7 @@ function PollUtil (runtime, element, pollType) {
type: "POST",
url: self.voteUrl,
data: JSON.stringify({"choice": choice}),
success: self.getResults
success: self.onSubmit
});
});
// If the user has already reached their maximum submissions, all inputs should be disabled.
......@@ -73,7 +75,7 @@ function PollUtil (runtime, element, pollType) {
type: "POST",
url: self.voteUrl,
data: JSON.stringify(self.surveyChoices()),
success: self.getResults
success: self.onSubmit
})
});
// If the user has refreshed the page, they may still have an answer
......@@ -86,7 +88,7 @@ function PollUtil (runtime, element, pollType) {
var choices = {};
self.answers.each(function(index, el) {
el = $(el);
choices[el.prop('name')] = $(self.checkedElement(el)).val();
choices[el.prop('name')] = $(self.checkedElement(el), element).val();
});
return choices;
};
......@@ -111,7 +113,7 @@ function PollUtil (runtime, element, pollType) {
}
};
this.getResults = function (data) {
this.onSubmit = function (data) {
// Fetch the results from the server and render them.
if (!data['success']) {
alert(data['errors'].join('\n'));
......@@ -135,6 +137,11 @@ function PollUtil (runtime, element, pollType) {
return;
}
// Used if results are not private, to show the user how other students voted.
self.getResults();
};
this.getResults = function () {
// Used if results are not private, to show the user how other students voted.
$.ajax({
// Semantically, this would be better as GET, but we can use helper
// functions with POST.
......
......@@ -3,4 +3,4 @@ markdown
-e git+https://github.com/edx-solutions/xblock-utils.git#egg=xblock-utils
-e git://github.com/nosedjango/nosedjango.git@ed7d7f9aa969252ff799ec159f828eaa8c1cbc5a#egg=nosedjango-dev
ddt
mock
......@@ -23,11 +23,29 @@
from ddt import ddt, unpack, data
from selenium.common.exceptions import NoSuchElementException
from poll.poll import PollBase
from base_test import PollBaseTest, DEFAULT_SURVEY_NAMES, DEFAULT_POLL_NAMES
scenarios = ('Survey Private', DEFAULT_SURVEY_NAMES), ('Poll Private', DEFAULT_POLL_NAMES)
def stub_view_permission(can_view):
"""
Patches the 'can_view_private_results' function to return specified answer.
"""
def stub_view_permissions_decorator(test_fn):
def test_patched(self, page_name, names):
original = PollBase.can_view_private_results
try:
PollBase.can_view_private_results = lambda self: can_view
test_fn(self, page_name, names)
finally:
PollBase.can_view_private_results = original
return test_patched
return stub_view_permissions_decorator
@ddt
class TestPrivateResults(PollBaseTest):
"""
......@@ -55,7 +73,7 @@ class TestPrivateResults(PollBaseTest):
self.do_submit(names)
# No results should be showing.
self.assertNotIn(self.browser.find_element_by_css_selector('div.poll-block').get_attribute('innerHTML'), 'poll-top-choice')
self.assertRaises(NoSuchElementException, self.browser.find_element_by_css_selector, '.poll-results')
self.assertRaises(NoSuchElementException, self.browser.find_element_by_css_selector, '.poll-footnote')
@unpack
......@@ -83,3 +101,27 @@ class TestPrivateResults(PollBaseTest):
self.assertFalse(self.browser.find_element_by_css_selector('.poll-feedback').is_displayed())
self.do_submit(names)
self.assertTrue(self.browser.find_element_by_css_selector('.poll-feedback').is_displayed())
@unpack
@data(*scenarios)
@stub_view_permission(False)
def test_results_button_visibility_without_permission(self, page_name, names):
self.go_to_page(page_name)
self.assertRaises(NoSuchElementException, self.browser.find_element_by_css_selector, '.view-results-button')
@unpack
@data(*scenarios)
@stub_view_permission(True)
def test_results_button_visibility_with_permission(self, page_name, names):
self.go_to_page(page_name)
self.browser.find_element_by_css_selector('.view-results-button')
@unpack
@data(*scenarios)
@stub_view_permission(True)
def test_results_button(self, page_name, names):
self.go_to_page(page_name)
button = self.browser.find_element_by_css_selector('a.view-results-button')
button.click()
self.wait_until_exists('.poll-results')
self.wait_until_exists('.poll-footnote')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment