Commit f967cfce by Clinton Blackburn

Merged release to master

Resolved conflicts from hotfix-2015-10-05
parents b7cf3920 882c2fa3
......@@ -86,7 +86,8 @@ class CreditCourseDashboardTest(ModuleStoreTestCase):
def test_not_eligible_for_credit(self):
# The user is not yet eligible for credit, so no additional information should be displayed on the dashboard.
response = self._load_dashboard()
self.assertNotContains(response, "credit")
self.assertNotContains(response, "credit-eligibility-msg")
self.assertNotContains(response, "purchase-credit-btn")
def test_eligible_for_credit(self):
# Simulate that the user has completed the only requirement in the course
......
......@@ -400,7 +400,7 @@ FEATURES = {
'ENABLE_OPENBADGES': False,
# Credit course API
'ENABLE_CREDIT_API': False,
'ENABLE_CREDIT_API': True,
# The block types to disable need to be specified in "x block disable config" in django admin.
'ENABLE_DISABLING_XBLOCK_TYPES': True,
......@@ -2124,7 +2124,7 @@ if FEATURES.get('CLASS_DASHBOARD'):
INSTALLED_APPS += ('class_dashboard',)
################ Enable credit eligibility feature ####################
ENABLE_CREDIT_ELIGIBILITY = False
ENABLE_CREDIT_ELIGIBILITY = True
FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY
######################## CAS authentication ###########################
......
/**
* Credit-related utilities
*/
var edx = edx || {};
(function ($, _) {
'use strict';
edx.commerce = edx.commerce || {};
edx.commerce.credit = edx.commerce.credit || {};
edx.commerce.credit.createCreditRequest = function (providerId, courseKey, username) {
return $.ajax({
url: '/api/credit/v1/providers/' + providerId + '/request/',
type: 'POST',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
data: JSON.stringify({
'course_key': courseKey,
'username': username
}),
context: this,
success: function (requestData) {
var $form = $('<form>', {
'action': requestData.url,
'method': 'POST',
'accept-method': 'UTF-8'
});
_.each(requestData.parameters, function (value, key) {
$('<textarea>').attr({
name: key,
value: value
}).appendTo($form);
});
$form.submit();
}
});
};
})(jQuery, _);
......@@ -50,12 +50,12 @@ var edx = edx || {};
this.getProviderData(providerId).then(this.renderProvider, this.renderError)
}
},
renderCourseNamePlaceholder: function(courseId) {
renderCourseNamePlaceholder: function (courseId) {
// Display the course Id or name (if available) in the placeholder
var $courseNamePlaceholder = $(".course_name_placeholder");
$courseNamePlaceholder.text(courseId);
this.getCourseData(courseId).then(function(responseData) {
this.getCourseData(courseId).then(function (responseData) {
$courseNamePlaceholder.text(responseData.name);
});
},
......@@ -77,7 +77,7 @@ var edx = edx || {};
var self = this,
orderId = $.url('?basket_id') || $.url('?payment-order-num');
if (orderId && this.$el.data('is-payment-complete')==='True') {
if (orderId && this.$el.data('is-payment-complete') === 'True') {
// Get the order details
self.$el.removeClass('hidden');
self.getReceiptData(orderId).then(self.renderReceipt, self.renderError);
......@@ -168,7 +168,7 @@ var edx = edx || {};
billedTo: null
};
if (order.billing_address){
if (order.billing_address) {
receiptContext.billedTo = {
firstName: order.billing_address.first_name,
lastName: order.billing_address.last_name,
......@@ -263,8 +263,8 @@ var edx = edx || {};
line = order.lines[0];
if (this.useEcommerceApi) {
attributeValues = _.find(line.product.attribute_values, function (attribute) {
return attribute.name === 'credit_provider'
});
return attribute.name === 'credit_provider';
});
// This method assumes that all items in the order are related to a single course.
if (attributeValues != undefined) {
......@@ -273,7 +273,7 @@ var edx = edx || {};
}
return null;
},
}
});
new edx.commerce.ReceiptView({
......@@ -282,16 +282,11 @@ var edx = edx || {};
})(jQuery, _, _.str, Backbone);
function completeOrder (event) {
function completeOrder(event) { // jshint ignore:line
var courseKey = $(event).data("course-key"),
username = $(event).data("username"),
providerId = $(event).data("provider"),
postData = {
'course_key': courseKey,
'username': username
},
errorContainer = $("#error-container");
$errorContainer = $("#error-container");
analytics.track(
"edx.bi.credit.clicked_complete_credit",
......@@ -301,34 +296,7 @@ function completeOrder (event) {
}
);
$.ajax({
url: '/api/credit/v1/providers/' + providerId + '/request/',
type: 'POST',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
data: JSON.stringify(postData) ,
context: this,
success: function(requestData){
var form = $('#complete-order-form');
$('input', form).remove();
form.attr( 'action', requestData.url );
form.attr( 'method', 'POST' );
_.each( requestData.parameters, function( value, key ) {
$('<input>').attr({
type: 'hidden',
name: key,
value: value
}).appendTo(form);
});
form.submit();
},
error: function(xhr){
errorContainer.removeClass("is-hidden");
errorContainer.removeClass("hidden");
}
edx.commerce.credit.createCreditRequest(providerId, courseKey, username).fail(function () {
$errorContainer.removeClass("hidden");
});
}
(function($, analytics) {
/**
* Student dashboard credit messaging.
*/
var edx = edx || {};
(function ($, analytics) {
'use strict';
$(document).ready(function() {
var errorContainer = $(".credit-error-msg"),
creditStatusError = errorContainer.data("credit-error");
$(document).ready(function () {
var $errorContainer = $(".credit-error-msg"),
creditStatusError = $errorContainer.data("credit-error");
if (creditStatusError == "True"){
errorContainer.toggleClass("is-hidden");
if (creditStatusError === "True") {
$errorContainer.toggleClass("is-hidden");
}
// Fire analytics events when the "purchase credit" button is clicked
$(".purchase-credit-btn").on("click", function(event) {
$(".purchase-credit-btn").on("click", function (event) {
var courseKey = $(event.target).data("course-key");
analytics.track(
"edx.bi.credit.clicked_purchase_credit",
......@@ -20,46 +26,19 @@
}
);
});
// This event invokes credit request endpoint. It will initiate
// a credit request for the credit course for the provided user.
$(".pending-credit-btn").on("click", function(event){
var courseKey = $(event.target).data("course-key"),
username = $(event.target).data("user"),
provider_id = $(event.target).data("provider"),
postData = {
'course_key': courseKey,
'username': username
};
$.ajax({
url: 'api/credit/v1/providers/' + provider_id + '/request/',
type: 'POST',
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
data: JSON.stringify(postData) ,
context: this,
success: function(requestData){
var form = $('#credit-pending-form');
$('input', form).remove();
form.attr( 'action', requestData.url );
form.attr( 'method', 'POST' );
// This event invokes credit request endpoint. It will initiate
// a credit request for the credit course for the provided user.
$(".pending-credit-btn").on("click", function (event) {
var $target = $(event.target),
courseKey = $target.data("course-key"),
username = $target.data("user"),
providerId = $target.data("provider");
_.each( requestData.parameters, function( value, key ) {
$('<input>').attr({
type: 'hidden',
name: key,
value: value
}).appendTo(form);
});
form.submit();
},
error: function(xhr){
$(".credit-request-pending-msg").hide("is-hidden");
$(".pending-credit-btn").hide();
errorContainer.toggleClass("is-hidden");
}
edx.commerce.credit.createCreditRequest(providerId, courseKey, username).fail(function () {
$(".credit-action").hide();
$errorContainer.toggleClass("is-hidden");
});
});
});
......
......@@ -548,24 +548,6 @@
padding: 0;
}
.credit-btn{
@include float(right);
}
.denied-credit-btn{
@include float(right);
}
.credit-request-pending-msg{
display: inline-block;
}
.credit-btn {
@extend %btn-pl-yellow-base;
background-image: none ;
text-shadow: none;
box-shadow: none;
}
.message {
@extend %ui-depth1;
border-radius: 3px;
......@@ -750,19 +732,21 @@
font-weight: bold;
}
}
.credit-eligibility-msg {
@include float(left);
width: flex-grid(10, 12);
}
.credit-request-pending-msg {
@include float(left);
margin-top: 10px;
}
.credit-action {
.credit-msg {
@include float(left);
width: flex-grid(9, 12);
}
.credit-request-approved-msg{
width: flex-grid(10, 12);
@include float(left);
.credit-btn {
@extend %btn-pl-yellow-base;
@include float(right);
background-image: none ;
text-shadow: none;
box-shadow: none;
text-transform: none;
}
}
.actions {
......
......@@ -21,6 +21,7 @@ from django.utils.translation import ugettext as _
<script src="${static.url('js/vendor/jquery.ajax-retry.js')}"></script>
<script src="${static.url('js/vendor/underscore.string.min.js')}"></script>
<script src="${static.url('js/src/tooltip_manager.js')}"></script>
<script src="${static.url('js/commerce/credit.js')}"></script>
<script src="${static.url('js/commerce/views/receipt_view.js')}"></script>
</%block>
......@@ -56,7 +57,6 @@ from django.utils.translation import ugettext as _
<h1>${_("Loading Order Data...")}</h1>
<span>${ _("Please wait while we retrieve your order details.") }</span>
</div>
<form id="complete-order-form"></form>
</section>
</div>
</%block>
......@@ -21,6 +21,7 @@
<%= interpolate("<img src='%s' alt='%s'></image>", [thumbnail_url, display_name]) %>
</div>
<div class="complete-order">
<%= interpolate('<button data-provider="%s" data-course-key="%s" data-username="%s" class="complete-course" onClick=completeOrder(this)>%s</button>', [provider_id, course_key, username, gettext( "Complete Order")]) %>
<%= interpolate('<button data-provider="%s" data-course-key="%s" data-username="%s" class="complete-course" onClick=completeOrder(this)>%s</button>', [provider_id, course_key, username,
gettext( "Get Credit")]) %>
</div>
</div>
......@@ -35,6 +35,7 @@ import json
</%block>
<%block name="js_extra">
<script src="${static.url('js/commerce/credit.js')}"></script>
<%static:js group='dashboard'/>
<script type="text/javascript">
$(document).ready(function() {
......
......@@ -19,57 +19,47 @@
)
)}
</p>
% if not credit_status["purchased"] and not credit_status["error"] :
<p class="message-copy credit-eligibility-msg">
${_("You have completed this course and are eligible to purchase course credit. Select <b>Learn More</b> to get started.")}
<div class="credit-action">
% if not credit_status["purchased"] and not credit_status["error"] :
<p class="message-copy credit-msg credit-eligibility-msg">
## Translators: provider_name is the name of a credit provider or university (e.g. State University)
${_("You are now eligible to purchase course credit from {provider_name} for this course. Click <strong>Get Credit</strong> to get started.").format(
provider_name=credit_status["provider_name"],
)}
</p>
<div class="purchase_credit">
<a class="btn credit-btn purchase-credit-btn" href="${settings.ECOMMERCE_PUBLIC_URL_ROOT}/credit/checkout/${credit_status['course_key']}" target="_blank" data-course-key="${credit_status['course_key']}">${_("Get Credit")}</a>
</div>
% elif credit_status["request_status"] in [None, "pending"] and not credit_status["error"]:
<p class="message-copy credit-msg credit-request-pending-msg">
## Translators: provider_name is the name of a credit provider or university (e.g. State University)
${_("Thank you for your payment. To receive course credit, you must now request credit at the {provider_name} website.").format(
provider_name=credit_status["provider_name"],
)
}
</p>
<button class="btn credit-btn pending-credit-btn" data-course-key="${credit_status['course_key']}" data-user="${user.username}" data-provider="${credit_status['provider_id']}">${_("Finalize Credit")}</button>
</p>
<div class="purchase_credit">
<a class="btn credit-btn purchase-credit-btn" href="${settings.ECOMMERCE_PUBLIC_URL_ROOT}/credit/checkout/${credit_status['course_key']}" target="_blank" data-course-key="${credit_status['course_key']}">${_("Learn More")}</a>
</div>
% elif credit_status["request_status"] in [None, "pending"] and not credit_status["error"] :
% if credit_status["request_status"] == "pending":
<p class="message-copy credit-request-pending-msg">
## Translators: provider_name is the name of a credit provider, such as 'State University' or 'Happy Fun Company'.
${_("{provider_name} has received your course credit request. We will update you when credit processing is complete.").format(
% elif credit_status["request_status"] == "approved" and not credit_status["error"] :
<p class="message-copy credit-msg credit-request-approved-msg">
## Translators: provider_name is the name of a credit provider or university (e.g. State University)
${_("<strong>Congratulations!</strong> {provider_name} has converted your course credit. To see your course credit, click <strong>Access Credit</strong>.").format(
provider_name=credit_status["provider_name"],
)
}
</p>
% elif credit_status["request_status"] is None:
<p class="message-copy credit-request-pending-msg">
<a class="btn credit-btn access-credit-btn" href="${credit_status['provider_status_url']}" target="_blank">${_("Access Credit")}</a>
% elif credit_status["request_status"] == "rejected" and not credit_status["error"] :
<p class="message-copy credit-msg credit-request-rejected-msg">
## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a
## credit provider, such as 'State University' or 'Happy Fun Company'.
${_("Thank you for your payment. To receive course credit, you must now request credit at the {link_to_provider_site} website. Select <b>Request Credit</b> to get started.").format(
## credit provider, such as 'State University' or 'Happy Fun Company'. provider_name is the name of credit provider.
${_("{provider_name} did not approve your request for course credit. For more information, contact {link_to_provider_site} directly.").format(
provider_name=credit_status["provider_name"],
link_to_provider_site=provider_link,
)
}
</p>
<button class="btn credit-btn pending-credit-btn" data-course-key="${credit_status['course_key']}" data-user="${user.username}" data-provider="${credit_status['provider_id']}">${_("Request Credit")}</button>
% endif
<form id="credit-pending-form"> </form>
% elif credit_status["request_status"] == "approved" and not credit_status["error"] :
<p class="message-copy credit-request-approved-msg">
## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a
## credit provider, such as 'State University' or 'Happy Fun Company'. provider_name is the name of credit provider.
${_("<b>Congratulations!</b> {provider_name} has approved your request for course credit. To see your course credit, visit the {link_to_provider_site} website").format(
provider_name=credit_status["provider_name"],
link_to_provider_site=provider_link,
)
}
</p>
<a class="btn credit-btn access-credit-btn" href="${credit_status['provider_status_url']}" target="_blank">${_("View Credit")}</a>
% elif credit_status["request_status"] == "rejected" and not credit_status["error"] :
<p class="message-copy credit-request-rejected-msg">
## Translators: link_to_provider_site is a link to an external webpage. The text of the link will be the name of a
## credit provider, such as 'State University' or 'Happy Fun Company'. provider_name is the name of credit provider.
${_("{provider_name} did not approve your request for course credit. For more information, contact {link_to_provider_site} directly.").format(
provider_name=credit_status["provider_name"],
link_to_provider_site=provider_link,
)
}
</p>
% endif
</div>
</div>
% endif
......@@ -54,10 +54,10 @@ def signature(params, shared_secret):
str: The 32-character signature.
"""
encoded_params = "".join([
"{key}:{value}".format(key=key, value=params[key])
encoded_params = u"".join([
u"{key}:{value}".format(key=key, value=params[key])
for key in sorted(params.keys())
if key != "signature"
if key != u"signature"
])
hasher = hmac.new(shared_secret, encoded_params, hashlib.sha256)
hasher = hmac.new(shared_secret, encoded_params.encode('utf-8'), hashlib.sha256)
return hasher.hexdigest()
# coding=utf-8
"""
Tests for digital signatures used to validate messages to/from credit providers.
"""
......@@ -9,14 +10,14 @@ from django.test.utils import override_settings
from openedx.core.djangoapps.credit import signature
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
"asu": u'abcd1234'
})
class SignatureTest(TestCase):
"""
Tests for digital signatures.
"""
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
"asu": u'abcd1234'
})
def test_unicode_secret_key(self):
# Test a key that has type `unicode` but consists of ASCII characters
# (This can happen, for example, when loading the key from a JSON configuration file)
......@@ -35,3 +36,9 @@ class SignatureTest(TestCase):
# so we can fix the misconfiguration.
key = signature.get_shared_secret_key("asu")
self.assertIs(key, None)
def test_unicode_data(self):
""" Verify the signature generation method supports Unicode data. """
key = signature.get_shared_secret_key("asu")
sig = signature.signature({'name': u'Ed Xavíer'}, key)
self.assertEqual(sig, "76b6c9a657000829253d7c23977b35b34ad750c5681b524d7fdfb25cd5273cec")
......@@ -57,7 +57,7 @@ git+https://github.com/edx/edx-lint.git@c5745631d2eee4e2efe8c31fa7b42fe2c12a0755
git+https://github.com/edx/ecommerce-api-client.git@1.1.0#egg=ecommerce-api-client==1.1.0
-e git+https://github.com/edx/edx-user-state-client.git@30c0ad4b9f57f8d48d6943eb585ec8a9205f4469#egg=edx-user-state-client
git+https://github.com/edx/edx-organizations.git@release-2015-09-22#egg=edx-organizations==0.1.6
git+https://github.com/edx/edx-proctoring.git@0.9.14#egg=edx-proctoring==0.9.14
git+https://github.com/edx/edx-proctoring.git@0.9.16#egg=edx-proctoring==0.9.16
# Third Party XBlocks
-e git+https://github.com/mitodl/edx-sga@172a90fd2738f8142c10478356b2d9ed3e55334a#egg=edx-sga
......
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