Merge pull request #7082 from edx/ziafazal/skip-entrance-exam-am
Skip entrance exam feature on instructor dashboard
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding model 'EntranceExamConfiguration'
|
||||
db.create_table('student_entranceexamconfiguration', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('course_id', self.gf('xmodule_django.models.CourseKeyField')(max_length=255, db_index=True)),
|
||||
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, db_index=True, blank=True)),
|
||||
('updated', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, db_index=True, blank=True)),
|
||||
('skip_entrance_exam', self.gf('django.db.models.fields.BooleanField')(default=True)),
|
||||
))
|
||||
db.send_create_signal('student', ['EntranceExamConfiguration'])
|
||||
|
||||
# Adding unique constraint on 'EntranceExamConfiguration', fields ['user', 'course_id']
|
||||
db.create_unique('student_entranceexamconfiguration', ['user_id', 'course_id'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Removing unique constraint on 'EntranceExamConfiguration', fields ['user', 'course_id']
|
||||
db.delete_unique('student_entranceexamconfiguration', ['user_id', 'course_id'])
|
||||
|
||||
# Deleting model 'EntranceExamConfiguration'
|
||||
db.delete_table('student_entranceexamconfiguration')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'student.anonymoususerid': {
|
||||
'Meta': {'object_name': 'AnonymousUserId'},
|
||||
'anonymous_user_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.courseaccessrole': {
|
||||
'Meta': {'unique_together': "(('user', 'org', 'course_id', 'role'),)", 'object_name': 'CourseAccessRole'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'org': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '64', 'blank': 'True'}),
|
||||
'role': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_index': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.courseenrollment': {
|
||||
'Meta': {'ordering': "('user', 'course_id')", 'unique_together': "(('user', 'course_id'),)", 'object_name': 'CourseEnrollment'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'mode': ('django.db.models.fields.CharField', [], {'default': "'honor'", 'max_length': '100'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.courseenrollmentallowed': {
|
||||
'Meta': {'unique_together': "(('email', 'course_id'),)", 'object_name': 'CourseEnrollmentAllowed'},
|
||||
'auto_enroll': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'student.dashboardconfiguration': {
|
||||
'Meta': {'object_name': 'DashboardConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'recent_enrollment_time_delta': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
|
||||
},
|
||||
'student.entranceexamconfiguration': {
|
||||
'Meta': {'unique_together': "(('user', 'course_id'),)", 'object_name': 'EntranceExamConfiguration'},
|
||||
'course_id': ('xmodule_django.models.CourseKeyField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'skip_entrance_exam': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'db_index': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.linkedinaddtoprofileconfiguration': {
|
||||
'Meta': {'object_name': 'LinkedInAddToProfileConfiguration'},
|
||||
'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
|
||||
'dashboard_tracking_code': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
|
||||
},
|
||||
'student.loginfailures': {
|
||||
'Meta': {'object_name': 'LoginFailures'},
|
||||
'failure_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'lockout_until': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.passwordhistory': {
|
||||
'Meta': {'object_name': 'PasswordHistory'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'time_set': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.pendingemailchange': {
|
||||
'Meta': {'object_name': 'PendingEmailChange'},
|
||||
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'new_email': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
|
||||
},
|
||||
'student.pendingnamechange': {
|
||||
'Meta': {'object_name': 'PendingNameChange'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'new_name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
|
||||
'rationale': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'})
|
||||
},
|
||||
'student.registration': {
|
||||
'Meta': {'object_name': 'Registration', 'db_table': "'auth_registration'"},
|
||||
'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
|
||||
},
|
||||
'student.userprofile': {
|
||||
'Meta': {'object_name': 'UserProfile', 'db_table': "'auth_userprofile'"},
|
||||
'allow_certificate': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'city': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'null': 'True', 'blank': 'True'}),
|
||||
'courseware': ('django.db.models.fields.CharField', [], {'default': "'course.xml'", 'max_length': '255', 'blank': 'True'}),
|
||||
'gender': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
|
||||
'goals': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'language': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'level_of_education': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '6', 'null': 'True', 'blank': 'True'}),
|
||||
'location': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'mailing_address': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'meta': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '255', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"}),
|
||||
'year_of_birth': ('django.db.models.fields.IntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
|
||||
},
|
||||
'student.usersignupsource': {
|
||||
'Meta': {'object_name': 'UserSignupSource'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'site': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.userstanding': {
|
||||
'Meta': {'object_name': 'UserStanding'},
|
||||
'account_status': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
|
||||
'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'standing_last_changed_at': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'standing'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'student.usertestgroup': {
|
||||
'Meta': {'object_name': 'UserTestGroup'},
|
||||
'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
|
||||
'users': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.User']", 'db_index': 'True', 'symmetrical': 'False'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['student']
|
||||
@@ -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
|
||||
|
||||
@@ -523,6 +523,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):
|
||||
"""
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# 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']:
|
||||
|
||||
Reference in New Issue
Block a user