ECOM-1290: added the redirection back to courseware after re-verification
This commit is contained in:
46
lms/djangoapps/courseware/url_helpers.py
Normal file
46
lms/djangoapps/courseware/url_helpers.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Module to define url helpers functions
|
||||
"""
|
||||
from xmodule.modulestore.search import path_to_location, navigation_index
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
|
||||
def get_redirect_url(course_key, usage_key):
|
||||
""" Returns the redirect url back to courseware
|
||||
|
||||
Args:
|
||||
course_id(str): Course Id string
|
||||
location(str): The location id of course component
|
||||
|
||||
Raises:
|
||||
ItemNotFoundError if no data at the location or NoPathToItem if location not in any class
|
||||
|
||||
Returns:
|
||||
Redirect url string
|
||||
"""
|
||||
|
||||
(course_key, chapter, section, position) = path_to_location(modulestore(), usage_key)
|
||||
|
||||
# choose the appropriate view (and provide the necessary args) based on the
|
||||
# args provided by the redirect.
|
||||
# Rely on index to do all error handling and access control.
|
||||
if chapter is None:
|
||||
redirect_url = reverse('courseware', args=(unicode(course_key), ))
|
||||
elif section is None:
|
||||
redirect_url = reverse('courseware_chapter', args=(unicode(course_key), chapter))
|
||||
elif position is None:
|
||||
redirect_url = reverse(
|
||||
'courseware_section',
|
||||
args=(unicode(course_key), chapter, section)
|
||||
)
|
||||
else:
|
||||
# Here we use the navigation_index from the position returned from
|
||||
# path_to_location - we can only navigate to the topmost vertical at the
|
||||
# moment
|
||||
|
||||
redirect_url = reverse(
|
||||
'courseware_position',
|
||||
args=(unicode(course_key), chapter, section, navigation_index(position))
|
||||
)
|
||||
return redirect_url
|
||||
@@ -60,7 +60,6 @@ from util.cache import cache, cache_if_anonymous
|
||||
from xblock.fragment import Fragment
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
|
||||
from xmodule.modulestore.search import path_to_location, navigation_index
|
||||
from xmodule.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
import shoppingcart
|
||||
@@ -82,6 +81,7 @@ import survey.views
|
||||
from util.views import ensure_valid_course_key
|
||||
from eventtracking import tracker
|
||||
import analytics
|
||||
from courseware.url_helpers import get_redirect_url
|
||||
|
||||
log = logging.getLogger("edx.courseware")
|
||||
|
||||
@@ -642,37 +642,13 @@ def jump_to(_request, course_id, location):
|
||||
except InvalidKeyError:
|
||||
raise Http404(u"Invalid course_key or usage_key")
|
||||
try:
|
||||
(course_key, chapter, section, position) = path_to_location(modulestore(), usage_key)
|
||||
redirect_url = get_redirect_url(course_key, usage_key)
|
||||
except ItemNotFoundError:
|
||||
raise Http404(u"No data at this location: {0}".format(usage_key))
|
||||
except NoPathToItem:
|
||||
raise Http404(u"This location is not in any class: {0}".format(usage_key))
|
||||
|
||||
# choose the appropriate view (and provide the necessary args) based on the
|
||||
# args provided by the redirect.
|
||||
# Rely on index to do all error handling and access control.
|
||||
if chapter is None:
|
||||
return redirect('courseware', course_id=unicode(course_key))
|
||||
elif section is None:
|
||||
return redirect('courseware_chapter', course_id=unicode(course_key), chapter=chapter)
|
||||
elif position is None:
|
||||
return redirect(
|
||||
'courseware_section',
|
||||
course_id=unicode(course_key),
|
||||
chapter=chapter,
|
||||
section=section
|
||||
)
|
||||
else:
|
||||
# Here we use the navigation_index from the position returned from
|
||||
# path_to_location - we can only navigate to the topmost vertical at the
|
||||
# moment
|
||||
return redirect(
|
||||
'courseware_position',
|
||||
course_id=unicode(course_key),
|
||||
chapter=chapter,
|
||||
section=section,
|
||||
position=navigation_index(position)
|
||||
)
|
||||
return redirect(redirect_url)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
|
||||
@@ -34,7 +34,7 @@ class ReverificationService(object):
|
||||
except ObjectDoesNotExist:
|
||||
return None
|
||||
|
||||
def start_verification(self, course_id, checkpoint_name, item_id): # pylint: disable=W0613
|
||||
def start_verification(self, course_id, checkpoint_name, item_id):
|
||||
""" Get or create the verification checkpoint and return the re-verification link
|
||||
|
||||
Args:
|
||||
@@ -46,5 +46,5 @@ class ReverificationService(object):
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
VerificationCheckpoint.objects.get_or_create(course_id=course_key, checkpoint_name=checkpoint_name)
|
||||
re_verification_link = reverse("verify_student_incourse_reverify", args=(course_id, checkpoint_name))
|
||||
re_verification_link = reverse("verify_student_incourse_reverify", args=(course_id, checkpoint_name, item_id))
|
||||
return re_verification_link
|
||||
|
||||
@@ -20,7 +20,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core import mail
|
||||
from bs4 import BeautifulSoup
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
@@ -1701,20 +1701,42 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
|
||||
IMAGE_DATA = "abcd,1234"
|
||||
MIDTERM = "midterm"
|
||||
|
||||
def setUp(self):
|
||||
super(TestInCourseReverifyView, self).setUp()
|
||||
|
||||
self.user = UserFactory.create(username="rusty", password="test")
|
||||
self.client.login(username="rusty", password="test")
|
||||
def build_course(self):
|
||||
"""
|
||||
Build up a course tree with a Reverificaiton xBlock.
|
||||
"""
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
|
||||
self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course")
|
||||
CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
|
||||
|
||||
# Create the course modes
|
||||
for mode in ('audit', 'honor', 'verified'):
|
||||
min_price = 0 if mode in ["honor", "audit"] else 1
|
||||
CourseModeFactory(mode_slug=mode, course_id=self.course_key, min_price=min_price)
|
||||
|
||||
# Create the 'edx-reverification-block' in course tree
|
||||
section = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
|
||||
subsection = ItemFactory.create(parent=section, category='sequential', display_name='Test Subsection')
|
||||
vertical = ItemFactory.create(parent=subsection, category='vertical', display_name='Test Unit')
|
||||
reverification = ItemFactory.create(
|
||||
parent=vertical,
|
||||
category='edx-reverification-block',
|
||||
display_name='Test Verification Block'
|
||||
)
|
||||
self.section_location = section.location
|
||||
self.subsection_location = subsection.location
|
||||
self.vertical_location = vertical.location
|
||||
self.reverification_location = reverification.location
|
||||
|
||||
def setUp(self):
|
||||
super(TestInCourseReverifyView, self).setUp()
|
||||
|
||||
self.build_course()
|
||||
|
||||
self.user = UserFactory.create(username="rusty", password="test")
|
||||
self.client.login(username="rusty", password="test")
|
||||
|
||||
# Enroll the user in the default mode (honor) to emulate
|
||||
CourseEnrollment.enroll(self.user, self.course_key, mode="verified")
|
||||
self.config = InCourseReverificationConfiguration(enabled=True)
|
||||
@@ -1863,4 +1885,8 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
|
||||
|
||||
"""
|
||||
return reverse('verify_student_incourse_reverify',
|
||||
kwargs={"course_id": unicode(course_key), "checkpoint_name": checkpoint})
|
||||
kwargs={
|
||||
"course_id": unicode(course_key),
|
||||
"checkpoint_name": checkpoint,
|
||||
"location": unicode(self.reverification_location)
|
||||
})
|
||||
|
||||
@@ -143,7 +143,7 @@ urlpatterns = patterns(
|
||||
# Users are sent to this end-point from within courseware
|
||||
# to re-verify their identities by re-submitting face photos.
|
||||
url(
|
||||
r'^reverify/{course_id}/{checkpoint}/$'.format(
|
||||
r'^reverify/{course_id}/{checkpoint}/(?P<location>.*)/$'.format(
|
||||
course_id=settings.COURSE_ID_PATTERN, checkpoint=settings.CHECKPOINT_PATTERN
|
||||
),
|
||||
views.InCourseReverifyView.as_view(),
|
||||
|
||||
@@ -24,9 +24,10 @@ from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.mail import send_mail
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from opaque_keys import InvalidKeyError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
|
||||
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings, update_account_settings
|
||||
@@ -55,7 +56,7 @@ from util.json_request import JsonResponse
|
||||
from util.date_utils import get_default_time_display
|
||||
from eventtracking import tracker
|
||||
import analytics
|
||||
|
||||
from courseware.url_helpers import get_redirect_url
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -1089,7 +1090,7 @@ class InCourseReverifyView(View):
|
||||
Does not need to worry about pricing
|
||||
"""
|
||||
@method_decorator(login_required)
|
||||
def get(self, request, course_id, checkpoint_name):
|
||||
def get(self, request, course_id, checkpoint_name, location):
|
||||
""" Display the view for face photo submission"""
|
||||
# Check the in-course re-verification is enabled or not
|
||||
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
|
||||
@@ -1120,11 +1121,12 @@ class InCourseReverifyView(View):
|
||||
'course_name': course.display_name_with_default,
|
||||
'checkpoint_name': checkpoint_name,
|
||||
'platform_name': settings.PLATFORM_NAME,
|
||||
'location': location
|
||||
}
|
||||
return render_to_response("verify_student/incourse_reverify.html", context)
|
||||
|
||||
@method_decorator(login_required)
|
||||
def post(self, request, course_id, checkpoint_name):
|
||||
def post(self, request, course_id, checkpoint_name, location):
|
||||
"""Submits the re-verification attempt to SoftwareSecure
|
||||
|
||||
Args:
|
||||
@@ -1143,7 +1145,11 @@ class InCourseReverifyView(View):
|
||||
raise Http404
|
||||
|
||||
user = request.user
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
usage_key = UsageKey.from_string(location).replace(course_key=course_key)
|
||||
except InvalidKeyError:
|
||||
raise Http404(u"Invalid course_key or usage_key")
|
||||
course = modulestore().get_course(course_key)
|
||||
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
|
||||
if checkpoint is None:
|
||||
@@ -1156,6 +1162,7 @@ class InCourseReverifyView(View):
|
||||
'error': True,
|
||||
'errorMsg': _("No checkpoint found"),
|
||||
'platform_name': settings.PLATFORM_NAME,
|
||||
'location': location
|
||||
}
|
||||
return render_to_response("verify_student/incourse_reverify.html", context)
|
||||
init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
|
||||
@@ -1175,12 +1182,20 @@ class InCourseReverifyView(View):
|
||||
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, user.id, course_id, checkpoint_name
|
||||
)
|
||||
|
||||
return HttpResponse()
|
||||
try:
|
||||
redirect_url = get_redirect_url(course_key, usage_key)
|
||||
except (ItemNotFoundError, NoPathToItem):
|
||||
redirect_url = reverse("courseware", args=(unicode(course_key),))
|
||||
|
||||
return JsonResponse({'url': redirect_url})
|
||||
except Http404 as expt:
|
||||
log.exception("Invalid location during photo verification.")
|
||||
return HttpResponseBadRequest(expt.message)
|
||||
except IndexError:
|
||||
log.exception("Invalid image data during photo verification.")
|
||||
return HttpResponseBadRequest(_("Invalid image data during photo verification."))
|
||||
except Exception: # pylint: disable=broad-except
|
||||
log.exception("Could not submit verification attempt for user {}.").format(request.user.id)
|
||||
log.exception("Could not submit verification attempt for user %s.", request.user.id)
|
||||
msg = _("Could not submit photos")
|
||||
return HttpResponseBadRequest(msg)
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
courseKey: el.data('course-key'),
|
||||
checkpointName: el.data('checkpoint-name'),
|
||||
platformName: el.data('platform-name'),
|
||||
location: el.data('location'),
|
||||
errorModel: errorView.model
|
||||
}).render();
|
||||
|
||||
|
||||
@@ -18,18 +18,20 @@ var edx = edx || {};
|
||||
courseKey: '',
|
||||
checkpointName: '',
|
||||
faceImage: '',
|
||||
location: ''
|
||||
},
|
||||
|
||||
sync: function( method ) {
|
||||
var model = this;
|
||||
var headers = { 'X-CSRFToken': $.cookie( 'csrftoken' ) },
|
||||
data = {
|
||||
face_image: model.get( 'faceImage' ),
|
||||
face_image: model.get( 'faceImage' )
|
||||
},
|
||||
url = _.str.sprintf(
|
||||
'/verify_student/reverify/%(courseKey)s/%(checkpointName)s/', {
|
||||
'/verify_student/reverify/%(courseKey)s/%(checkpointName)s/%(location)s/', {
|
||||
courseKey: model.get('courseKey'),
|
||||
checkpointName: model.get('checkpointName')
|
||||
checkpointName: model.get('checkpointName'),
|
||||
location: model.get('location')
|
||||
}
|
||||
);
|
||||
|
||||
@@ -38,8 +40,8 @@ var edx = edx || {};
|
||||
type: 'POST',
|
||||
data: data,
|
||||
headers: headers,
|
||||
success: function() {
|
||||
model.trigger( 'sync' );
|
||||
success: function(response) {
|
||||
model.trigger( 'sync', response.url);
|
||||
},
|
||||
error: function( error ) {
|
||||
model.trigger( 'error', error );
|
||||
|
||||
@@ -28,11 +28,13 @@
|
||||
this.courseKey = obj.courseKey || null;
|
||||
this.checkpointName = obj.checkpointName || null;
|
||||
this.platformName = obj.platformName || null;
|
||||
this.location = obj.location || null;
|
||||
|
||||
|
||||
this.model = new edx.verify_student.ReverificationModel({
|
||||
courseKey: this.courseKey,
|
||||
checkpointName: this.checkpointName
|
||||
checkpointName: this.checkpointName,
|
||||
location: this.location
|
||||
});
|
||||
|
||||
this.listenTo( this.model, 'sync', _.bind( this.handleSubmitPhotoSuccess, this ));
|
||||
@@ -75,10 +77,10 @@
|
||||
this.model.save();
|
||||
},
|
||||
|
||||
handleSubmitPhotoSuccess: function() {
|
||||
handleSubmitPhotoSuccess: function(redirect_url) {
|
||||
// Eventually this will be a redirect back into the courseware,
|
||||
// but for now we can return to the student dashboard.
|
||||
window.location.href = '/dashboard';
|
||||
window.location.href = redirect_url;
|
||||
},
|
||||
|
||||
handleSubmissionError: function(xhr) {
|
||||
|
||||
@@ -46,6 +46,7 @@ checkpoint_name=checkpoint_name)}
|
||||
data-course-key='${course_key}'
|
||||
data-checkpoint-name='${checkpoint_name}'
|
||||
data-platform-name='${platform_name}'
|
||||
data-location='${location}'
|
||||
></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user