Commit ca08b979 by aamir-khan

ECOM-1290: added the redirection back to courseware after re-verification

parent 37e2ffec
"""
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 ...@@ -60,7 +60,6 @@ from util.cache import cache, cache_if_anonymous
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem 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.tabs import CourseTabList, StaffGradingTab, PeerGradingTab, OpenEndedGradingTab
from xmodule.x_module import STUDENT_VIEW from xmodule.x_module import STUDENT_VIEW
import shoppingcart import shoppingcart
...@@ -82,6 +81,7 @@ import survey.views ...@@ -82,6 +81,7 @@ import survey.views
from util.views import ensure_valid_course_key from util.views import ensure_valid_course_key
from eventtracking import tracker from eventtracking import tracker
import analytics import analytics
from courseware.url_helpers import get_redirect_url
log = logging.getLogger("edx.courseware") log = logging.getLogger("edx.courseware")
...@@ -642,37 +642,13 @@ def jump_to(_request, course_id, location): ...@@ -642,37 +642,13 @@ def jump_to(_request, course_id, location):
except InvalidKeyError: except InvalidKeyError:
raise Http404(u"Invalid course_key or usage_key") raise Http404(u"Invalid course_key or usage_key")
try: try:
(course_key, chapter, section, position) = path_to_location(modulestore(), usage_key) redirect_url = get_redirect_url(course_key, usage_key)
except ItemNotFoundError: except ItemNotFoundError:
raise Http404(u"No data at this location: {0}".format(usage_key)) raise Http404(u"No data at this location: {0}".format(usage_key))
except NoPathToItem: except NoPathToItem:
raise Http404(u"This location is not in any class: {0}".format(usage_key)) 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 return redirect(redirect_url)
# 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)
)
@ensure_csrf_cookie @ensure_csrf_cookie
......
...@@ -34,7 +34,7 @@ class ReverificationService(object): ...@@ -34,7 +34,7 @@ class ReverificationService(object):
except ObjectDoesNotExist: except ObjectDoesNotExist:
return None 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 """ Get or create the verification checkpoint and return the re-verification link
Args: Args:
...@@ -46,5 +46,5 @@ class ReverificationService(object): ...@@ -46,5 +46,5 @@ class ReverificationService(object):
""" """
course_key = CourseKey.from_string(course_id) course_key = CourseKey.from_string(course_id)
VerificationCheckpoint.objects.get_or_create(course_id=course_key, checkpoint_name=checkpoint_name) 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 return re_verification_link
...@@ -20,7 +20,7 @@ from django.core.exceptions import ObjectDoesNotExist ...@@ -20,7 +20,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.core import mail from django.core import mail
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase 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.django import modulestore
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
...@@ -1701,20 +1701,42 @@ class TestInCourseReverifyView(ModuleStoreTestCase): ...@@ -1701,20 +1701,42 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
IMAGE_DATA = "abcd,1234" IMAGE_DATA = "abcd,1234"
MIDTERM = "midterm" MIDTERM = "midterm"
def setUp(self): def build_course(self):
super(TestInCourseReverifyView, self).setUp() """
Build up a course tree with a Reverificaiton xBlock.
self.user = UserFactory.create(username="rusty", password="test") """
self.client.login(username="rusty", password="test") # pylint: disable=attribute-defined-outside-init
self.course_key = SlashSeparatedCourseKey("Robot", "999", "Test_Course") 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 # Create the course modes
for mode in ('audit', 'honor', 'verified'): for mode in ('audit', 'honor', 'verified'):
min_price = 0 if mode in ["honor", "audit"] else 1 min_price = 0 if mode in ["honor", "audit"] else 1
CourseModeFactory(mode_slug=mode, course_id=self.course_key, min_price=min_price) 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 # Enroll the user in the default mode (honor) to emulate
CourseEnrollment.enroll(self.user, self.course_key, mode="verified") CourseEnrollment.enroll(self.user, self.course_key, mode="verified")
self.config = InCourseReverificationConfiguration(enabled=True) self.config = InCourseReverificationConfiguration(enabled=True)
...@@ -1863,4 +1885,8 @@ class TestInCourseReverifyView(ModuleStoreTestCase): ...@@ -1863,4 +1885,8 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
""" """
return reverse('verify_student_incourse_reverify', 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( ...@@ -143,7 +143,7 @@ urlpatterns = patterns(
# Users are sent to this end-point from within courseware # Users are sent to this end-point from within courseware
# to re-verify their identities by re-submitting face photos. # to re-verify their identities by re-submitting face photos.
url( 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 course_id=settings.COURSE_ID_PATTERN, checkpoint=settings.CHECKPOINT_PATTERN
), ),
views.InCourseReverifyView.as_view(), views.InCourseReverifyView.as_view(),
......
...@@ -24,9 +24,10 @@ from django.utils.decorators import method_decorator ...@@ -24,9 +24,10 @@ from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.mail import send_mail 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.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 edxmako.shortcuts import render_to_response, render_to_string
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings, update_account_settings 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 ...@@ -55,7 +56,7 @@ from util.json_request import JsonResponse
from util.date_utils import get_default_time_display from util.date_utils import get_default_time_display
from eventtracking import tracker from eventtracking import tracker
import analytics import analytics
from courseware.url_helpers import get_redirect_url
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -1089,7 +1090,7 @@ class InCourseReverifyView(View): ...@@ -1089,7 +1090,7 @@ class InCourseReverifyView(View):
Does not need to worry about pricing Does not need to worry about pricing
""" """
@method_decorator(login_required) @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""" """ Display the view for face photo submission"""
# Check the in-course re-verification is enabled or not # Check the in-course re-verification is enabled or not
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
...@@ -1120,11 +1121,12 @@ class InCourseReverifyView(View): ...@@ -1120,11 +1121,12 @@ class InCourseReverifyView(View):
'course_name': course.display_name_with_default, 'course_name': course.display_name_with_default,
'checkpoint_name': checkpoint_name, 'checkpoint_name': checkpoint_name,
'platform_name': settings.PLATFORM_NAME, 'platform_name': settings.PLATFORM_NAME,
'location': location
} }
return render_to_response("verify_student/incourse_reverify.html", context) return render_to_response("verify_student/incourse_reverify.html", context)
@method_decorator(login_required) @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 """Submits the re-verification attempt to SoftwareSecure
Args: Args:
...@@ -1143,7 +1145,11 @@ class InCourseReverifyView(View): ...@@ -1143,7 +1145,11 @@ class InCourseReverifyView(View):
raise Http404 raise Http404
user = request.user 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) course = modulestore().get_course(course_key)
checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name) checkpoint = VerificationCheckpoint.get_verification_checkpoint(course_key, checkpoint_name)
if checkpoint is None: if checkpoint is None:
...@@ -1156,6 +1162,7 @@ class InCourseReverifyView(View): ...@@ -1156,6 +1162,7 @@ class InCourseReverifyView(View):
'error': True, 'error': True,
'errorMsg': _("No checkpoint found"), 'errorMsg': _("No checkpoint found"),
'platform_name': settings.PLATFORM_NAME, 'platform_name': settings.PLATFORM_NAME,
'location': location
} }
return render_to_response("verify_student/incourse_reverify.html", context) return render_to_response("verify_student/incourse_reverify.html", context)
init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user) init_verification = SoftwareSecurePhotoVerification.get_initial_verification(user)
...@@ -1175,12 +1182,20 @@ class InCourseReverifyView(View): ...@@ -1175,12 +1182,20 @@ class InCourseReverifyView(View):
EVENT_NAME_USER_SUBMITTED_INCOURSE_REVERIFY, user.id, course_id, checkpoint_name 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: except IndexError:
log.exception("Invalid image data during photo verification.") log.exception("Invalid image data during photo verification.")
return HttpResponseBadRequest(_("Invalid image data during photo verification.")) return HttpResponseBadRequest(_("Invalid image data during photo verification."))
except Exception: # pylint: disable=broad-except 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") msg = _("Could not submit photos")
return HttpResponseBadRequest(msg) return HttpResponseBadRequest(msg)
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
courseKey: el.data('course-key'), courseKey: el.data('course-key'),
checkpointName: el.data('checkpoint-name'), checkpointName: el.data('checkpoint-name'),
platformName: el.data('platform-name'), platformName: el.data('platform-name'),
location: el.data('location'),
errorModel: errorView.model errorModel: errorView.model
}).render(); }).render();
......
...@@ -18,18 +18,20 @@ var edx = edx || {}; ...@@ -18,18 +18,20 @@ var edx = edx || {};
courseKey: '', courseKey: '',
checkpointName: '', checkpointName: '',
faceImage: '', faceImage: '',
location: ''
}, },
sync: function( method ) { sync: function( method ) {
var model = this; var model = this;
var headers = { 'X-CSRFToken': $.cookie( 'csrftoken' ) }, var headers = { 'X-CSRFToken': $.cookie( 'csrftoken' ) },
data = { data = {
face_image: model.get( 'faceImage' ), face_image: model.get( 'faceImage' )
}, },
url = _.str.sprintf( url = _.str.sprintf(
'/verify_student/reverify/%(courseKey)s/%(checkpointName)s/', { '/verify_student/reverify/%(courseKey)s/%(checkpointName)s/%(location)s/', {
courseKey: model.get('courseKey'), courseKey: model.get('courseKey'),
checkpointName: model.get('checkpointName') checkpointName: model.get('checkpointName'),
location: model.get('location')
} }
); );
...@@ -38,8 +40,8 @@ var edx = edx || {}; ...@@ -38,8 +40,8 @@ var edx = edx || {};
type: 'POST', type: 'POST',
data: data, data: data,
headers: headers, headers: headers,
success: function() { success: function(response) {
model.trigger( 'sync' ); model.trigger( 'sync', response.url);
}, },
error: function( error ) { error: function( error ) {
model.trigger( 'error', error ); model.trigger( 'error', error );
......
...@@ -28,11 +28,13 @@ ...@@ -28,11 +28,13 @@
this.courseKey = obj.courseKey || null; this.courseKey = obj.courseKey || null;
this.checkpointName = obj.checkpointName || null; this.checkpointName = obj.checkpointName || null;
this.platformName = obj.platformName || null; this.platformName = obj.platformName || null;
this.location = obj.location || null;
this.model = new edx.verify_student.ReverificationModel({ this.model = new edx.verify_student.ReverificationModel({
courseKey: this.courseKey, courseKey: this.courseKey,
checkpointName: this.checkpointName checkpointName: this.checkpointName,
location: this.location
}); });
this.listenTo( this.model, 'sync', _.bind( this.handleSubmitPhotoSuccess, this )); this.listenTo( this.model, 'sync', _.bind( this.handleSubmitPhotoSuccess, this ));
...@@ -75,10 +77,10 @@ ...@@ -75,10 +77,10 @@
this.model.save(); this.model.save();
}, },
handleSubmitPhotoSuccess: function() { handleSubmitPhotoSuccess: function(redirect_url) {
// Eventually this will be a redirect back into the courseware, // Eventually this will be a redirect back into the courseware,
// but for now we can return to the student dashboard. // but for now we can return to the student dashboard.
window.location.href = '/dashboard'; window.location.href = redirect_url;
}, },
handleSubmissionError: function(xhr) { handleSubmissionError: function(xhr) {
......
...@@ -46,6 +46,7 @@ checkpoint_name=checkpoint_name)} ...@@ -46,6 +46,7 @@ checkpoint_name=checkpoint_name)}
data-course-key='${course_key}' data-course-key='${course_key}'
data-checkpoint-name='${checkpoint_name}' data-checkpoint-name='${checkpoint_name}'
data-platform-name='${platform_name}' data-platform-name='${platform_name}'
data-location='${location}'
></div> ></div>
</section> </section>
</div> </div>
......
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