Commit 119d761c by Matt Drayer

Merge pull request #7082 from edx/ziafazal/skip-entrance-exam-am

Skip entrance exam feature on instructor dashboard
parents 7bad6039 e21ff203
......@@ -1500,3 +1500,43 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
)
if self.trk_partner_name else None
)
class EntranceExamConfiguration(models.Model):
"""
Represents a Student's entrance exam specific data for a single Course
"""
user = models.ForeignKey(User, db_index=True)
course_id = CourseKeyField(max_length=255, db_index=True)
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
updated = models.DateTimeField(auto_now=True, db_index=True)
# if skip_entrance_exam is True, then student can skip entrance exam
# for the course
skip_entrance_exam = models.BooleanField(default=True)
class Meta(object):
"""
Meta class to make user and course_id unique in the table
"""
unique_together = (('user', 'course_id'), )
def __unicode__(self):
return "[EntranceExamConfiguration] %s: %s (%s) = %s" % (
self.user, self.course_id, self.created, self.skip_entrance_exam
)
@classmethod
def user_can_skip_entrance_exam(cls, user, course_key):
"""
Return True if given user can skip entrance exam for given course otherwise False.
"""
can_skip = False
if settings.FEATURES.get('ENTRANCE_EXAMS', False):
try:
record = EntranceExamConfiguration.objects.get(user=user, course_id=course_key)
can_skip = record.skip_entrance_exam
except EntranceExamConfiguration.DoesNotExist:
can_skip = False
return can_skip
......@@ -524,6 +524,13 @@ class StudentAdminPage(PageObject):
return self.q(css='{} input[name=rescore-entrance-exam]'.format(self.EE_CONTAINER))
@property
def skip_entrance_exam_button(self):
"""
Return Let Student Skip Entrance Exam button.
"""
return self.q(css='{} input[name=skip-entrance-exam]'.format(self.EE_CONTAINER))
@property
def delete_student_state_button(self):
"""
Returns delete student state button.
......@@ -592,6 +599,12 @@ class StudentAdminPage(PageObject):
"""
return self.rescore_submission_button.click()
def click_skip_entrance_exam_button(self):
"""
clicks let student skip entrance exam button.
"""
return self.skip_entrance_exam_button.click()
def click_delete_student_state_button(self):
"""
clicks delete student state button.
......
......@@ -76,10 +76,10 @@ class SettingsPage(CoursePage):
"""
press_the_notification_button(self, "save")
if wait_for_confirmation:
EmptyPromise(
lambda: self.q(css='#alert-confirmation-title').present,
'Save is confirmed'
).fulfill()
self.wait_for_element_visibility(
'#alert-confirmation-title',
'Save confirmation message is visible'
)
def refresh_page(self, wait_for_confirmation=True):
"""
......@@ -91,3 +91,4 @@ class SettingsPage(CoursePage):
lambda: self.q(css='body.view-settings').present,
'Page is refreshed'
).fulfill()
self.wait_for_ajax()
......@@ -241,9 +241,9 @@ def element_has_text(page, css_selector, text):
def get_modal_alert(browser):
"""
Returns instance of modal alert box shown in browser after waiting
for 4 seconds
for 6 seconds
"""
WebDriverWait(browser, 4).until(EC.alert_is_present())
WebDriverWait(browser, 6).until(EC.alert_is_present())
return browser.switch_to.alert
......
......@@ -204,6 +204,45 @@ class EntranceExamGradeTest(UniqueCourseTest):
self.student_admin_section.wait_for_ajax()
self.assertGreater(len(self.student_admin_section.top_notification.text[0]), 0)
def test_clicking_skip_entrance_exam_button_with_success(self):
"""
Scenario: Clicking on the Let Student Skip Entrance Exam button with
valid student email address or username should result in success prompt.
Given that I am on the Student Admin tab on the Instructor Dashboard
When I click the Let Student Skip Entrance Exam Button under
Entrance Exam Grade Adjustment after entering a valid student
email address or username
Then I should be shown an alert with success message
"""
self.student_admin_section.set_student_email(self.student_identifier)
self.student_admin_section.click_skip_entrance_exam_button()
#first we have window.confirm
alert = get_modal_alert(self.student_admin_section.browser)
alert.accept()
# then we have alert confirming action
alert = get_modal_alert(self.student_admin_section.browser)
alert.dismiss()
def test_clicking_skip_entrance_exam_button_with_error(self):
"""
Scenario: Clicking on the Let Student Skip Entrance Exam button with
email address or username of a non existing student should result in error message.
Given that I am on the Student Admin tab on the Instructor Dashboard
When I click the Let Student Skip Entrance Exam Button under
Entrance Exam Grade Adjustment after entering non existing
student email address or username
Then I should be shown an error message
"""
self.student_admin_section.set_student_email('non_existing@example.com')
self.student_admin_section.click_skip_entrance_exam_button()
#first we have window.confirm
alert = get_modal_alert(self.student_admin_section.browser)
alert.accept()
self.student_admin_section.wait_for_ajax()
self.assertGreater(len(self.student_admin_section.top_notification.text[0]), 0)
def test_clicking_delete_student_attempts_button_with_success(self):
"""
Scenario: Clicking on the Delete Student State for entrance exam button
......
......@@ -36,7 +36,7 @@ from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
from edxmako.shortcuts import render_to_string
from eventtracking import tracker
from psychometrics.psychoanalyze import make_psychometrics_data_update_handler
from student.models import anonymous_id_for_user, user_by_anonymous_id
from student.models import anonymous_id_for_user, user_by_anonymous_id, EntranceExamConfiguration
from xblock.core import XBlock
from xblock.fields import Scope
from xblock.runtime import KvsFieldData, KeyValueStore
......@@ -129,6 +129,15 @@ def _get_required_content(course, user):
if milestone_path.get('content') and len(milestone_path['content']):
for content in milestone_path['content']:
required_content.append(content)
can_skip_entrance_exam = EntranceExamConfiguration.user_can_skip_entrance_exam(user, course.id)
# check if required_content has any entrance exam and user is allowed to skip it
# then remove it from required content
if required_content and getattr(course, 'entrance_exam_enabled', False) and can_skip_entrance_exam:
descriptors = [modulestore().get_item(UsageKey.from_string(content)) for content in required_content]
entrance_exam_contents = [unicode(descriptor.location)
for descriptor in descriptors if descriptor.is_entrance_exam]
required_content = list(set(required_content) - set(entrance_exam_contents))
return required_content
......
......@@ -6,7 +6,7 @@ from django.conf import settings
from django.utils.translation import ugettext as _
from courseware.access import has_access
from student.models import CourseEnrollment
from student.models import CourseEnrollment, EntranceExamConfiguration
from xmodule.tabs import CourseTabList
if settings.FEATURES.get('MILESTONES_APP', False):
......@@ -40,7 +40,8 @@ def get_course_tab_list(course, user):
for __, value in course_milestones_paths.iteritems():
if len(value.get('content', [])):
for content in value['content']:
if content == course.entrance_exam_id:
if content == course.entrance_exam_id \
and not EntranceExamConfiguration.user_can_skip_entrance_exam(user, course.id):
entrance_exam_mode = True
break
......
......@@ -3,10 +3,11 @@ Tests use cases related to LMS Entrance Exam behavior, such as gated content acc
"""
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.core.urlresolvers import reverse
from courseware.model_data import FieldDataCache
from courseware.module_render import get_module, toc_for_course
from courseware.tests.factories import UserFactory
from courseware.tests.factories import UserFactory, InstructorFactory
from milestones import api as milestones_api
from milestones.models import MilestoneRelationshipType
from xmodule.modulestore.django import modulestore
......@@ -57,14 +58,15 @@ class EntranceExamTestCases(ModuleStoreTestCase):
self.entrance_exam = ItemFactory.create(
parent=self.course,
category="chapter",
display_name="Entrance Exam Section - Chapter 1"
display_name="Entrance Exam Section - Chapter 1",
is_entrance_exam=True
)
self.exam_1 = ItemFactory.create(
parent=self.entrance_exam,
category='sequential',
display_name="Exam Sequential - Subsection 1",
graded=True,
metadata={'in_entrance_exam': True}
in_entrance_exam=True
)
subsection = ItemFactory.create(
parent=self.exam_1,
......@@ -130,13 +132,7 @@ class EntranceExamTestCases(ModuleStoreTestCase):
self.course.entrance_exam_id = unicode(self.entrance_exam.scope_ids.usage_id)
modulestore().update_item(self.course, user.id) # pylint: disable=no-member
def test_entrance_exam_gating(self):
"""
Unit Test: test_entrance_exam_gating
"""
# This user helps to cover a discovered bug in the milestone fulfillment logic
chaos_user = UserFactory()
expected_locked_toc = (
self.expected_locked_toc = (
[
{
'active': True,
......@@ -155,60 +151,7 @@ class EntranceExamTestCases(ModuleStoreTestCase):
}
]
)
locked_toc = toc_for_course(
self.request,
self.course,
self.entrance_exam.url_name,
self.exam_1.url_name,
self.field_data_cache
)
for toc_section in expected_locked_toc:
self.assertIn(toc_section, locked_toc)
# Set up the chaos user
# pylint: disable=maybe-no-member,no-member
grade_dict = {'value': 1, 'max_value': 1, 'user_id': chaos_user.id}
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
self.course.id,
chaos_user,
self.course,
depth=2
)
# pylint: disable=protected-access
module = get_module(
chaos_user,
self.request,
self.problem_1.scope_ids.usage_id,
field_data_cache,
)._xmodule
module.system.publish(self.problem_1, 'grade', grade_dict)
# pylint: disable=maybe-no-member,no-member
grade_dict = {'value': 1, 'max_value': 1, 'user_id': self.request.user.id}
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
self.course.id,
self.request.user,
self.course,
depth=2
)
# pylint: disable=protected-access
module = get_module(
self.request.user,
self.request,
self.problem_1.scope_ids.usage_id,
field_data_cache,
)._xmodule
module.system.publish(self.problem_1, 'grade', grade_dict)
module = get_module(
self.request.user,
self.request,
self.problem_2.scope_ids.usage_id,
field_data_cache,
)._xmodule # pylint: disable=protected-access
module.system.publish(self.problem_2, 'grade', grade_dict)
expected_unlocked_toc = (
self.expected_unlocked_toc = (
[
{
'active': False,
......@@ -263,6 +206,64 @@ class EntranceExamTestCases(ModuleStoreTestCase):
]
)
def test_entrance_exam_gating(self):
"""
Unit Test: test_entrance_exam_gating
"""
# This user helps to cover a discovered bug in the milestone fulfillment logic
chaos_user = UserFactory()
locked_toc = toc_for_course(
self.request,
self.course,
self.entrance_exam.url_name,
self.exam_1.url_name,
self.field_data_cache
)
for toc_section in self.expected_locked_toc:
self.assertIn(toc_section, locked_toc)
# Set up the chaos user
# pylint: disable=maybe-no-member,no-member
grade_dict = {'value': 1, 'max_value': 1, 'user_id': chaos_user.id}
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
self.course.id,
chaos_user,
self.course,
depth=2
)
# pylint: disable=protected-access
module = get_module(
chaos_user,
self.request,
self.problem_1.scope_ids.usage_id,
field_data_cache,
)._xmodule
module.system.publish(self.problem_1, 'grade', grade_dict)
# pylint: disable=maybe-no-member,no-member
grade_dict = {'value': 1, 'max_value': 1, 'user_id': self.request.user.id}
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
self.course.id,
self.request.user,
self.course,
depth=2
)
# pylint: disable=protected-access
module = get_module(
self.request.user,
self.request,
self.problem_1.scope_ids.usage_id,
field_data_cache,
)._xmodule
module.system.publish(self.problem_1, 'grade', grade_dict)
module = get_module(
self.request.user,
self.request,
self.problem_2.scope_ids.usage_id,
field_data_cache,
)._xmodule # pylint: disable=protected-access
module.system.publish(self.problem_2, 'grade', grade_dict)
unlocked_toc = toc_for_course(
self.request,
self.course,
......@@ -271,5 +272,39 @@ class EntranceExamTestCases(ModuleStoreTestCase):
self.field_data_cache
)
for toc_section in expected_unlocked_toc:
for toc_section in self.expected_unlocked_toc:
self.assertIn(toc_section, unlocked_toc)
def test_skip_entrance_exame_gating(self):
"""
Tests gating is disabled if skip entrance exam is set for a user.
"""
# make sure toc is locked before allowing user to skip entrance exam
locked_toc = toc_for_course(
self.request,
self.course,
self.entrance_exam.url_name,
self.exam_1.url_name,
self.field_data_cache
)
for toc_section in self.expected_locked_toc:
self.assertIn(toc_section, locked_toc)
# hit skip entrance exam api in instructor app
instructor = InstructorFactory(course_key=self.course.id)
self.client.login(username=instructor.username, password='test')
url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(self.course.id)})
response = self.client.post(url, {
'unique_student_identifier': self.request.user.email,
})
self.assertEqual(response.status_code, 200)
unlocked_toc = toc_for_course(
self.request,
self.course,
self.entrance_exam.url_name,
self.exam_1.url_name,
self.field_data_cache
)
for toc_section in self.expected_unlocked_toc:
self.assertIn(toc_section, unlocked_toc)
......@@ -10,6 +10,7 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.courses import get_course_by_id
from courseware.tests.helpers import get_request_for_user, LoginEnrollmentTestCase
from courseware.tests.factories import InstructorFactory
from xmodule import tabs
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_MIXED_TOY_MODULESTORE, TEST_DATA_MIXED_CLOSED_MODULESTORE
......@@ -177,6 +178,29 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
self.assertEqual(course_tab_list[0]['name'], 'Entrance Exam')
self.assertEqual(course_tab_list[1]['tab_id'], 'instructor')
def test_get_course_tabs_list_skipped_entrance_exam(self):
"""
Tests tab list is not limited if user is allowed to skip entrance exam.
"""
#create a user
student = UserFactory()
# login as instructor hit skip entrance exam api in instructor app
instructor = InstructorFactory(course_key=self.course.id)
self.client.logout()
self.client.login(username=instructor.username, password='test')
url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(self.course.id)})
response = self.client.post(url, {
'unique_student_identifier': student.email,
})
self.assertEqual(response.status_code, 200)
# log in again as student
self.client.logout()
self.login(self.email, self.password)
course_tab_list = get_course_tab_list(self.course, self.user)
self.assertEqual(len(course_tab_list), 5)
class TextBookTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
"""
......
......@@ -23,6 +23,7 @@ from django.http import HttpRequest, HttpResponse
from django.test import RequestFactory, TestCase
from django.test.utils import override_settings
from django.utils.timezone import utc
from django.utils.translation import ugettext as _
from mock import Mock, patch
from nose.tools import raises
......@@ -2375,13 +2376,13 @@ class TestEntranceExamInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollm
student=self.student,
course_id=self.course.id,
module_state_key=self.ee_problem_1.location,
state=json.dumps({'attempts': 10}),
state=json.dumps({'attempts': 10, 'done': True}),
)
ee_module_to_reset2 = StudentModule.objects.create(
student=self.student,
course_id=self.course.id,
module_state_key=self.ee_problem_2.location,
state=json.dumps({'attempts': 10}),
state=json.dumps({'attempts': 10, 'done': True}),
)
self.ee_modules = [ee_module_to_reset1.module_state_key, ee_module_to_reset2.module_state_key]
......@@ -2521,6 +2522,7 @@ class TestEntranceExamInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollm
# check response
tasks = json.loads(response.content)['tasks']
self.assertEqual(len(tasks), 1)
self.assertEqual(tasks[0]['status'], _('Complete'))
def test_list_entrance_exam_instructor_tasks_all_student(self):
""" Test list task history for entrance exam AND all student. """
......@@ -2541,6 +2543,27 @@ class TestEntranceExamInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollm
})
self.assertEqual(response.status_code, 400)
def test_skip_entrance_exam_student(self):
""" Test skip entrance exam api for student. """
# create a re-score entrance exam task
url = reverse('mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(self.course.id)})
response = self.client.post(url, {
'unique_student_identifier': self.student.email,
})
self.assertEqual(response.status_code, 200)
# check response
message = _('This student (%s) will skip the entrance exam.') % self.student.email
self.assertContains(response, message)
# post again with same student
response = self.client.post(url, {
'unique_student_identifier': self.student.email,
})
# This time response message should be different
message = _('This student (%s) is already allowed to skip the entrance exam.') % self.student.email
self.assertContains(response, message)
@override_settings(MODULESTORE=TEST_DATA_MOCK_MODULESTORE)
@patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message'))
......
......@@ -58,7 +58,7 @@ from shoppingcart.models import (
CourseMode,
CourseRegistrationCodeInvoiceItem,
)
from student.models import CourseEnrollment, unique_id_for_user, anonymous_id_for_user
from student.models import CourseEnrollment, unique_id_for_user, anonymous_id_for_user, EntranceExamConfiguration
import instructor_task.api
from instructor_task.api_helper import AlreadyRunningError
from instructor_task.models import ReportStore
......@@ -2322,3 +2322,27 @@ def spoc_gradebook(request, course_id):
'staff_access': True,
'ordered_grades': sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True),
})
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@require_level('staff')
@require_POST
def mark_student_can_skip_entrance_exam(request, course_id): # pylint: disable=invalid-name
"""
Mark a student to skip entrance exam.
Takes `unique_student_identifier` as required POST parameter.
"""
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
student_identifier = request.POST.get('unique_student_identifier')
student = get_student_from_identifier(student_identifier)
__, created = EntranceExamConfiguration.objects.get_or_create(user=student, course_id=course_id)
if created:
message = _('This student (%s) will skip the entrance exam.') % student_identifier
else:
message = _('This student (%s) is already allowed to skip the entrance exam.') % student_identifier
response_payload = {
'message': message,
}
return JsonResponse(response_payload)
# pylint: disable=bad-continuation
"""
Instructor API endpoint urls.
"""
......@@ -38,15 +37,27 @@ urlpatterns = patterns(
'instructor.views.api.get_student_progress_url', name="get_student_progress_url"),
url(r'^reset_student_attempts$',
'instructor.views.api.reset_student_attempts', name="reset_student_attempts"),
url(r'^rescore_problem$', 'instructor.views.api.rescore_problem', name="rescore_problem"),
# entrance exam tasks
url(r'^reset_student_attempts_for_entrance_exam$',
url( # pylint: disable=bad-continuation
r'^rescore_problem$',
'instructor.views.api.rescore_problem',
name="rescore_problem"
), url(
r'^reset_student_attempts_for_entrance_exam$',
'instructor.views.api.reset_student_attempts_for_entrance_exam',
name="reset_student_attempts_for_entrance_exam"),
url(r'^rescore_entrance_exam$',
'instructor.views.api.rescore_entrance_exam', name="rescore_entrance_exam"),
url(r'^list_entrance_exam_instructor_tasks',
'instructor.views.api.list_entrance_exam_instructor_tasks', name="list_entrance_exam_instructor_tasks"),
name="reset_student_attempts_for_entrance_exam"
), url(
r'^rescore_entrance_exam$',
'instructor.views.api.rescore_entrance_exam',
name="rescore_entrance_exam"
), url(
r'^list_entrance_exam_instructor_tasks',
'instructor.views.api.list_entrance_exam_instructor_tasks',
name="list_entrance_exam_instructor_tasks"
), url(
r'^mark_student_can_skip_entrance_exam',
'instructor.views.api.mark_student_can_skip_entrance_exam',
name="mark_student_can_skip_entrance_exam"
),
url(r'^list_instructor_tasks$',
'instructor.views.api.list_instructor_tasks', name="list_instructor_tasks"),
......
......@@ -310,6 +310,10 @@ def _section_student_admin(course, access):
),
'rescore_problem_url': reverse('rescore_problem', kwargs={'course_id': unicode(course_key)}),
'rescore_entrance_exam_url': reverse('rescore_entrance_exam', kwargs={'course_id': unicode(course_key)}),
'student_can_skip_entrance_exam_url': reverse(
'mark_student_can_skip_entrance_exam',
kwargs={'course_id': unicode(course_key)},
),
'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}),
'list_entrace_exam_instructor_tasks_url': reverse('list_entrance_exam_instructor_tasks',
kwargs={'course_id': unicode(course_key)}),
......
......@@ -149,6 +149,7 @@ def get_task_completion_info(instructor_task):
else:
student = task_input.get('student')
problem_url = task_input.get('problem_url')
entrance_exam_url = task_input.get('entrance_exam_url')
email_id = task_input.get('email_id')
if instructor_task.task_state == PROGRESS:
......@@ -167,6 +168,17 @@ def get_task_completion_info(instructor_task):
succeeded = True
# Translators: {action} is a past-tense verb that is localized separately. {student} is a student identifier.
msg_format = _("Problem successfully {action} for student '{student}'")
elif student is not None and entrance_exam_url is not None:
# this reports on actions on entrance exam for a particular student:
if num_attempted == 0:
# Translators: {action} is a past-tense verb that is localized separately.
# {student} is a student identifier.
msg_format = _("Unable to find entrance exam submission to be {action} for student '{student}'")
else:
succeeded = True
# Translators: {action} is a past-tense verb that is localized separately.
# {student} is a student identifier.
msg_format = _("Entrance exam successfully {action} for student '{student}'")
elif student is None and problem_url is not None:
# this reports on actions on problems for all students:
if num_attempted == 0:
......
......@@ -46,6 +46,7 @@ class @StudentAdmin
@$btn_reset_entrance_exam_attempts = @$section.find "input[name='reset-entrance-exam-attempts']"
@$btn_delete_entrance_exam_state = @$section.find "input[name='delete-entrance-exam-state']"
@$btn_rescore_entrance_exam = @$section.find "input[name='rescore-entrance-exam']"
@$btn_skip_entrance_exam = @$section.find "input[name='skip-entrance-exam']"
@$btn_entrance_exam_task_history = @$section.find "input[name='entrance-exam-task-history']"
@$table_entrance_exam_task_history = @$section.find ".entrance-exam-task-history-table"
......@@ -223,6 +224,28 @@ class @StudentAdmin
full_error_message = interpolate_text(error_message, {student_id: unique_student_identifier})
@$request_response_error_ee.text full_error_message
# Mark a student to skip entrance exam
@$btn_skip_entrance_exam.click =>
unique_student_identifier = @$field_entrance_exam_student_select_grade.val()
if not unique_student_identifier
return @$request_response_error_ee.text gettext("Enter a student's username or email address.")
confirm_message = gettext("Do you want to allow this student ('{student_id}') to skip the entrance exam?")
full_confirm_message = interpolate_text(confirm_message, {student_id: unique_student_identifier})
if window.confirm full_confirm_message
send_data =
unique_student_identifier: unique_student_identifier
$.ajax
dataType: 'json'
url: @$btn_skip_entrance_exam.data 'endpoint'
data: send_data
type: 'POST'
success: @clear_errors_then (data) ->
alert data.message
error: std_ajax_err =>
error_message = gettext("An error occurred. Make sure that the student's username or email address is correct and try again.")
@$request_response_error_ee.text error_message
# delete student state for entrance exam
@$btn_delete_entrance_exam_state.click =>
unique_student_identifier = @$field_entrance_exam_student_select_grade.val()
......@@ -249,7 +272,7 @@ class @StudentAdmin
@$btn_entrance_exam_task_history.click =>
unique_student_identifier = @$field_entrance_exam_student_select_grade.val()
if not unique_student_identifier
return @$request_response_error_ee.text gettext("Please enter a student email address or username.")
return @$request_response_error_ee.text gettext("Enter a student's username or email address.")
send_data =
unique_student_identifier: unique_student_identifier
......
......@@ -94,6 +94,7 @@
<input type="button" name="reset-entrance-exam-attempts" value="Reset Number of Student Attempts" data-endpoint="/courses/PU/FSc/2014_T4/instructor/api/reset_student_attempts_for_entrance_exam">
<input type="button" name="rescore-entrance-exam" value="Rescore All Problems" data-endpoint="/courses/PU/FSc/2014_T4/instructor/api/rescore_entrance_exam">
<input type="button" name="skip-entrance-exam" value="Let Student Skip Entrance Exam" data-endpoint="/courses/PU/FSc/2014_T4/instructor/api/mark_student_can_skip_entrance_exam">
<p>
<label> You can also delete all of the student's answers and scores for the entire entrance exam.
......
......@@ -23,7 +23,7 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'js/common_he
});
it('binds reset entrance exam ajax call and the result will be success', function() {
it('initiates resetting of entrance exam when button is clicked', function() {
studentadmin.$btn_reset_entrance_exam_attempts.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext("Please enter a student email address or username."));
......@@ -53,7 +53,7 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'js/common_he
expect(alert_msg).toEqual(full_success_message);
});
it('binds reset entrance exam ajax call and the result will be error', function() {
it('shows an error when resetting of entrance exam fails', function() {
// Spy on AJAX requests
var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier)
......@@ -129,7 +129,53 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'js/common_he
expect(studentadmin.$request_response_error_ee.text()).toEqual(full_error_message);
});
it('binds delete student state for entrance exam ajax call and the result will be success', function() {
it('initiates skip entrance exam when button is clicked', function() {
studentadmin.$btn_skip_entrance_exam.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext("Enter a student's username or email address."));
var success_message = "This student ('{student_id}') will skip the entrance exam.";
var full_success_message = interpolate_text(success_message, {
student_id: unique_student_identifier
});
// Spy on AJAX requests
var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier)
studentadmin.$btn_skip_entrance_exam.click();
// Verify that the client contacts the server to start instructor task
var url = dashboard_api_url + '/mark_student_can_skip_entrance_exam';
AjaxHelpers.expectRequest(requests, 'POST', url, $.param({
'unique_student_identifier': unique_student_identifier
}));
// Simulate a success response from the server
AjaxHelpers.respondWithJson(requests, {
message: full_success_message
});
expect(alert_msg).toEqual(full_success_message);
});
it('shows an error when skip entrance exam fails', function() {
// Spy on AJAX requests
var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier)
studentadmin.$btn_skip_entrance_exam.click();
// Verify that the client contacts the server to start instructor task
var url = dashboard_api_url + '/mark_student_can_skip_entrance_exam';
AjaxHelpers.expectRequest(requests, 'POST', url, $.param({
'unique_student_identifier': unique_student_identifier
}));
// Simulate an error response from the server
AjaxHelpers.respondWithError(requests, 400,{});
var error_message = "An error occurred. Make sure that the student's username or email address is correct and try again.";
expect(studentadmin.$request_response_error_ee.text()).toEqual(error_message);
});
it('initiates delete student state for entrance exam when button is clicked', function() {
studentadmin.$btn_delete_entrance_exam_state.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext("Please enter a student email address or username."));
......@@ -159,7 +205,7 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'js/common_he
expect(alert_msg).toEqual(full_success_message);
});
it('binds delete student state for entrance exam ajax call and the result will be error', function() {
it('shows an error when delete student state for entrance exam fails', function() {
// Spy on AJAX requests
var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier)
......@@ -183,10 +229,10 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'js/common_he
expect(studentadmin.$request_response_error_ee.text()).toEqual(full_error_message);
});
it('binds list entrance exam task history ajax call and the result will be success', function() {
it('initiates listing of entrance exam task history when button is clicked', function() {
studentadmin.$btn_entrance_exam_task_history.click();
// expect error to be shown since student identifier is not set
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext("Please enter a student email address or username."));
expect(studentadmin.$request_response_error_ee.text()).toEqual(gettext("Enter a student's username or email address."));
var success_message = gettext("Entrance exam state is being deleted for student '{student_id}'.");
var full_success_message = interpolate_text(success_message, {
......@@ -224,7 +270,7 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'js/common_he
expect($('.entrance-exam-task-history-table')).toBeVisible();
});
it('binds list entrance exam task history ajax call and the result will be error', function() {
it('shows an error when listing entrance exam task history fails', function() {
// Spy on AJAX requests
var requests = AjaxHelpers.requests(this);
studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier)
......
......@@ -107,6 +107,7 @@
%if settings.FEATURES.get('ENABLE_INSTRUCTOR_BACKGROUND_TASKS') and section_data['access']['instructor']:
<input type="button" name="rescore-entrance-exam" value="${_('Rescore All Problems')}" data-endpoint="${ section_data['rescore_entrance_exam_url'] }">
%endif
<input type="button" name="skip-entrance-exam" value="${_('Let Student Skip Entrance Exam')}" data-endpoint="${ section_data['student_can_skip_entrance_exam_url'] }">
<p>
%if section_data['access']['instructor']:
......
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