Commit bfe01605 by Will Daly

Merge pull request #8700 from edx/will/remove-provider-course-m2m-relation

Credit eligibility/provider refactor
parents 4526b929 e2acf3ab
......@@ -1830,20 +1830,27 @@ class LanguageProficiency(models.Model):
class CourseEnrollmentAttribute(models.Model):
"""Represents Student's enrollment record for Credit Course.
This is populated when the user's order for a credit seat is fulfilled.
"""
enrollment = models.ForeignKey(CourseEnrollment)
Provide additional information about the user's enrollment.
"""
enrollment = models.ForeignKey(CourseEnrollment, related_name="attributes")
namespace = models.CharField(
max_length=255,
help_text=_("Namespace of enrollment attribute e.g. credit")
help_text=_("Namespace of enrollment attribute")
)
name = models.CharField(
max_length=255,
help_text=_("Name of the enrollment attribute e.g. provider_id")
help_text=_("Name of the enrollment attribute")
)
value = models.CharField(
max_length=255,
help_text=_("Value of the enrollment attribute e.g. ASU")
help_text=_("Value of the enrollment attribute")
)
def __unicode__(self):
"""Unicode representation of the attribute. """
return u"{namespace}:{name}, {value}".format(
namespace=self.namespace,
name=self.name,
value=self.value,
)
"""
Tests for credit courses on the student dashboard.
"""
import unittest
import datetime
import pytz
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test.utils import override_settings
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from student.models import CourseEnrollmentAttribute
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider, CreditEligibility
from openedx.core.djangoapps.credit import api as credit_api
TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6"
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={
"hogwarts": TEST_CREDIT_PROVIDER_SECRET_KEY,
})
class CreditCourseDashboardTest(ModuleStoreTestCase):
"""
Tests for credit courses on the student dashboard.
"""
USERNAME = "ron"
PASSWORD = "mobiliarbus"
PROVIDER_ID = "hogwarts"
PROVIDER_NAME = "Hogwarts School of Witchcraft and Wizardry"
PROVIDER_STATUS_URL = "http://credit.example.com/status"
def setUp(self):
"""Create a course and an enrollment. """
super(CreditCourseDashboardTest, self).setUp()
# Create a user and log in
self.user = UserFactory.create(username=self.USERNAME, password=self.PASSWORD)
result = self.client.login(username=self.USERNAME, password=self.PASSWORD)
self.assertTrue(result, msg="Could not log in")
# Create a course and configure it as a credit course
self.course = CourseFactory()
CreditCourse.objects.create(course_key=self.course.id, enabled=True) # pylint: disable=no-member
# Configure a credit provider
CreditProvider.objects.create(
provider_id=self.PROVIDER_ID,
display_name=self.PROVIDER_NAME,
provider_status_url=self.PROVIDER_STATUS_URL,
enable_integration=True,
)
# Configure a single credit requirement (minimum passing grade)
credit_api.set_credit_requirements(
self.course.id, # pylint: disable=no-member
[
{
"namespace": "grade",
"name": "grade",
"display_name": "Final Grade",
"criteria": {
"min_grade": 0.8
}
}
]
)
# Enroll the user in the course as "verified"
self.enrollment = CourseEnrollmentFactory(
user=self.user,
course_id=self.course.id, # pylint: disable=no-member
mode="verified"
)
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")
def test_eligible_for_credit(self):
# Simulate that the user has completed the only requirement in the course
# so the user is eligible for credit.
self._make_eligible()
# The user should have the option to purchase credit
response = self._load_dashboard()
self.assertContains(response, "credit-eligibility-msg")
self.assertContains(response, "purchase-credit-btn")
# Move the eligibility deadline so it's within 30 days
eligibility = CreditEligibility.objects.get(username=self.USERNAME)
eligibility.deadline = datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=29)
eligibility.save()
# The user should still have the option to purchase credit,
# but there should also be a message urging the user to purchase soon.
response = self._load_dashboard()
self.assertContains(response, "credit-eligibility-msg")
self.assertContains(response, "purchase-credit-btn")
self.assertContains(response, "purchase credit for this course expires")
def test_purchased_credit(self):
# Simulate that the user has purchased credit, but has not
# yet initiated a request to the credit provider
self._make_eligible()
self._purchase_credit()
# Expect that the user's status is "pending"
response = self._load_dashboard()
self.assertContains(response, "credit-request-pending-msg")
def test_purchased_credit_and_request_pending(self):
# Simulate that the user has purchased credit and initiated a request,
# but we haven't yet heard back from the credit provider.
self._make_eligible()
self._purchase_credit()
self._initiate_request()
# Expect that the user's status is "pending"
response = self._load_dashboard()
self.assertContains(response, "credit-request-pending-msg")
def test_purchased_credit_and_request_approved(self):
# Simulate that the user has purchased credit and initiated a request,
# and had that request approved by the credit provider
self._make_eligible()
self._purchase_credit()
request_uuid = self._initiate_request()
self._set_request_status(request_uuid, "approved")
# Expect that the user's status is "approved"
response = self._load_dashboard()
self.assertContains(response, "credit-request-approved-msg")
def test_purchased_credit_and_request_rejected(self):
# Simulate that the user has purchased credit and initiated a request,
# and had that request rejected by the credit provider
self._make_eligible()
self._purchase_credit()
request_uuid = self._initiate_request()
self._set_request_status(request_uuid, "rejected")
# Expect that the user's status is "approved"
response = self._load_dashboard()
self.assertContains(response, "credit-request-rejected-msg")
def test_credit_status_error(self):
# Simulate an error condition: the user has a credit enrollment
# but no enrollment attribute indicating which provider the user
# purchased credit from.
self._make_eligible()
self._purchase_credit()
CourseEnrollmentAttribute.objects.all().delete()
# Expect an error message
response = self._load_dashboard()
self.assertContains(response, "credit-error-msg")
def _load_dashboard(self):
"""Load the student dashboard and return the HttpResponse. """
return self.client.get(reverse("dashboard"))
def _make_eligible(self):
"""Make the user eligible for credit in the course. """
credit_api.set_credit_requirement_status(
self.USERNAME,
self.course.id, # pylint: disable=no-member
"grade", "grade",
status="satisfied",
reason={
"final_grade": 0.95
}
)
def _purchase_credit(self):
"""Purchase credit from a provider in the course. """
self.enrollment.mode = "credit"
self.enrollment.save() # pylint: disable=no-member
CourseEnrollmentAttribute.objects.create(
enrollment=self.enrollment,
namespace="credit",
name="provider_id",
value=self.PROVIDER_ID,
)
def _initiate_request(self):
"""Initiate a request for credit from a provider. """
request = credit_api.create_credit_request(
self.course.id, # pylint: disable=no-member
self.PROVIDER_ID,
self.USERNAME
)
return request["parameters"]["request_uuid"]
def _set_request_status(self, uuid, status):
"""Set the status of a request for credit, simulating the notification from the provider. """
credit_api.update_credit_request_status(uuid, self.PROVIDER_ID, status)
......@@ -51,7 +51,7 @@ from course_modes.models import CourseMode
from shoppingcart.api import order_history
from student.models import (
Registration, UserProfile, PendingNameChange,
PendingEmailChange, CourseEnrollment, unique_id_for_user,
PendingEmailChange, CourseEnrollment, CourseEnrollmentAttribute, unique_id_for_user,
CourseEnrollmentAllowed, UserStanding, LoginFailures,
create_comments_service_user, PasswordHistory, UserSignupSource,
DashboardConfiguration, LinkedInAddToProfileConfiguration, ManualEnrollmentAudit, ALLOWEDTOENROLL_TO_ENROLLED)
......@@ -124,7 +124,6 @@ from notification_prefs.views import enable_notifications
# Note that this lives in openedx, so this dependency should be refactored.
from openedx.core.djangoapps.user_api.preferences import api as preferences_api
from openedx.core.djangoapps.credit.api import get_credit_eligibility, get_purchased_credit_courses
log = logging.getLogger("edx.student")
......@@ -531,8 +530,6 @@ def dashboard(request):
for course, __ in course_enrollment_pairs:
enrolled_courses_dict[unicode(course.id)] = course
credit_messages = _create_credit_availability_message(enrolled_courses_dict, user)
course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True)
message = ""
......@@ -638,7 +635,6 @@ def dashboard(request):
context = {
'enrollment_message': enrollment_message,
'credit_messages': credit_messages,
'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts,
'message': message,
......@@ -647,6 +643,7 @@ def dashboard(request):
'show_courseware_links_for': show_courseware_links_for,
'all_course_modes': course_mode_info,
'cert_statuses': cert_statuses,
'credit_statuses': _credit_statuses(user, course_enrollment_pairs),
'show_email_settings_for': show_email_settings_for,
'reverifications': reverifications,
'verification_status': verification_status,
......@@ -703,47 +700,6 @@ def _create_recent_enrollment_message(course_enrollment_pairs, course_modes):
)
def _create_credit_availability_message(enrolled_courses_dict, user): # pylint: disable=invalid-name
"""Builds a dict of credit availability for courses.
Construct a for courses user has completed and has not purchased credit
from the credit provider yet.
Args:
course_enrollment_pairs (list): A list of tuples containing courses, and the associated enrollment information.
user (User): User object.
Returns:
A dict of courses user is eligible for credit.
"""
user_eligibilities = get_credit_eligibility(user.username)
user_purchased_credit = get_purchased_credit_courses(user.username)
eligibility_messages = {}
for course_id, eligibility in user_eligibilities.iteritems():
if course_id not in user_purchased_credit:
duration = eligibility["seconds_good_for_display"]
curr_time = timezone.now()
validity_till = eligibility["created_at"] + timedelta(seconds=duration)
if validity_till > curr_time:
diff = validity_till - curr_time
urgent = diff.days <= 30
eligibility_messages[course_id] = {
"user_id": user.id,
"course_id": course_id,
"course_name": enrolled_courses_dict[course_id].display_name,
"providers": eligibility["providers"],
"status": eligibility["status"],
"provider": eligibility.get("provider"),
"urgent": urgent,
"user_full_name": user.get_full_name(),
"expiry": validity_till
}
return eligibility_messages
def _get_recently_enrolled_courses(course_enrollment_pairs):
"""Checks to see if the student has recently enrolled in courses.
......@@ -793,6 +749,124 @@ def _update_email_opt_in(request, org):
preferences_api.update_email_opt_in(request.user, org, email_opt_in_boolean)
def _credit_statuses(user, course_enrollment_pairs):
"""
Retrieve the status for credit courses.
A credit course is a course for which a user can purchased
college credit. The current flow is:
1. User becomes eligible for credit (submits verifications, passes the course, etc.)
2. User purchases credit from a particular credit provider.
3. User requests credit from the provider, usually creating an account on the provider's site.
4. The credit provider notifies us whether the user's request for credit has been accepted or rejected.
The dashboard is responsible for communicating the user's state in this flow.
Arguments:
user (User): The currently logged-in user.
course_enrollment_pairs (list): List of (Course, CourseEnrollment) tuples.
Returns: dict
The returned dictionary has keys that are `CourseKey`s and values that
are dictionaries with:
* eligible (bool): True if the user is eligible for credit in this course.
* deadline (datetime): The deadline for purchasing and requesting credit for this course.
* purchased (bool): Whether the user has purchased credit for this course.
* provider_name (string): The display name of the credit provider.
* provider_status_url (string): A URL the user can visit to check on their credit request status.
* request_status (string): Either "pending", "approved", or "rejected"
* error (bool): If true, an unexpected error occurred when retrieving the credit status,
so the user should contact the support team.
Example:
>>> _credit_statuses(user, course_enrollment_pairs)
{
CourseKey.from_string("edX/DemoX/Demo_Course"): {
"course_key": "edX/DemoX/Demo_Course",
"eligible": True,
"deadline": 2015-11-23 00:00:00 UTC,
"purchased": True,
"provider_name": "Hogwarts",
"provider_status_url": "http://example.com/status",
"request_status": "pending",
"error": False
}
}
"""
from openedx.core.djangoapps.credit import api as credit_api
request_status_by_course = {
request["course_key"]: request["status"]
for request in credit_api.get_credit_requests_for_user(user.username)
}
credit_enrollments = {
course.id: enrollment
for course, enrollment in course_enrollment_pairs
if enrollment.mode == "credit"
}
# When a user purchases credit in a course, the user's enrollment
# mode is set to "credit" and an enrollment attribute is set
# with the ID of the credit provider. We retrieve *all* such attributes
# here to minimize the number of database queries.
purchased_credit_providers = {
attribute.enrollment.course_id: attribute.value
for attribute in CourseEnrollmentAttribute.objects.filter(
namespace="credit",
name="provider_id",
enrollment__in=credit_enrollments.values()
).select_related("enrollment")
}
provider_info_by_id = {
provider["id"]: provider
for provider in credit_api.get_credit_providers()
}
statuses = {}
for eligibility in credit_api.get_eligibilities_for_user(user.username):
course_key = eligibility["course_key"]
status = {
"course_key": unicode(course_key),
"eligible": True,
"deadline": eligibility["deadline"],
"purchased": course_key in credit_enrollments,
"provider_name": None,
"provider_status_url": None,
"request_status": request_status_by_course.get(course_key),
"error": False,
}
# If the user has purchased credit, then include information about the credit
# provider from which the user purchased credit.
# We retrieve the provider's ID from the an "enrollment attribute" set on the user's
# enrollment when the user's order for credit is fulfilled by the E-Commerce service.
if status["purchased"]:
provider_id = purchased_credit_providers.get(course_key)
if provider_id is None:
status["error"] = True
log.error(
u"Could not find credit provider associated with credit enrollment "
u"for user %s in course %s. The user will not be able to see his or her "
u"credit request status on the student dashboard. This attribute should "
u"have been set when the user purchased credit in the course.",
user.id, course_key
)
else:
provider_info = provider_info_by_id.get(provider_id, {})
status["provider_name"] = provider_info.get("display_name")
status["provider_status_url"] = provider_info.get("status_url")
statuses[course_key] = status
return statuses
@require_POST
@commit_on_success_with_read_committed
def change_enrollment(request, check_access=True):
......
......@@ -641,13 +641,11 @@ class TestCourseGrader(TestSubmittingProblems):
)
# Configure a credit provider for the course
credit_provider = CreditProvider.objects.create(
CreditProvider.objects.create(
provider_id="ASU",
enable_integration=True,
provider_url="https://credit.example.com/request",
)
credit_course.providers.add(credit_provider)
credit_course.save()
requirements = [{
"namespace": "grade",
......
(function($, analytics) {
'use strict';
$(document).ready(function() {
// Fire analytics events when the "purchase credit" button is clicked
$(".purchase-credit-btn").on("click", function(event) {
var courseKey = $(event.target).data("course-key");
analytics.track(
"edx.bi.credit.clicked_purchase_credit",
{
category: "credit",
label: courseKey
}
);
});
});
})(jQuery, window.analytics);
......@@ -531,8 +531,17 @@
padding: 0;
}
.credit-eligibility-msg {
@include float(left);
margin-top: 10px;
}
.purchase_credit {
float: right;
@include float(right);
}
.purchase-credit-btn {
@extend %btn-pl-yellow-base;
}
.message {
......
......@@ -76,6 +76,7 @@ from django.core.urlresolvers import reverse
% for dashboard_index, (course, enrollment) in enumerate(course_enrollment_pairs):
<% show_courseware_link = (course.id in show_courseware_links_for) %>
<% cert_status = cert_statuses.get(course.id) %>
<% credit_status = credit_statuses.get(course.id) %>
<% show_email_settings = (course.id in show_email_settings_for) %>
<% course_mode_info = all_course_modes.get(course.id) %>
<% show_refund_option = (course.id in show_refund_option_for) %>
......@@ -83,8 +84,7 @@ from django.core.urlresolvers import reverse
<% is_course_blocked = (course.id in block_courses) %>
<% course_verification_status = verification_status_by_course.get(course.id, {}) %>
<% course_requirements = courses_requirements_not_met.get(course.id) %>
<% credit_message = credit_messages.get(unicode(course.id)) %>
<%include file='dashboard/_dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option = show_refund_option, is_paid_course = is_paid_course, is_course_blocked = is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, credit_message=credit_message, user=user" />
<%include file='dashboard/_dashboard_course_listing.html' args="course=course, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option = show_refund_option, is_paid_course = is_paid_course, is_course_blocked = is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user" />
% endfor
% if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
......
<%page args="course, enrollment, show_courseware_link, cert_status, show_email_settings, course_mode_info, show_refund_option, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, credit_message" />
<%page args="course, enrollment, show_courseware_link, cert_status, credit_status, show_email_settings, course_mode_info, show_refund_option, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings" />
<%!
import urllib
......@@ -275,9 +275,9 @@ from student.helpers import (
<%include file='_dashboard_certificate_information.html' args='cert_status=cert_status,course=course, enrollment=enrollment'/>
% endif
% if credit_message:
<%include file='_dashboard_credit_information.html' args='credit_message=credit_message'/>
% endif
% if credit_status is not None:
<%include file="_dashboard_credit_info.html" args="credit_status=credit_status"/>
% endif
% if verification_status.get('status') in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED, VERIFY_STATUS_NEED_TO_REVERIFY] and not is_course_blocked:
<div class="message message-status wrapper-message-primary is-shown">
......
<%page args="credit_status" />
<%!
import datetime
import pytz
from django.utils.translation import ugettext as _
from util.date_utils import get_default_time_display
%>
<%namespace name='static' file='../static_content.html'/>
% if credit_status["eligible"]:
<div class="message message-status is-shown credit-message">
% if credit_status["error"]:
<p class="message-copy credit-error-msg">
${_("An error occurred with this transaction. For help, contact {support_email}.").format(
support_email=u'<a href="mailto:{address}">{address}</a>'.format(
address=settings.DEFAULT_FEEDBACK_EMAIL
)
)}
</p>
% elif not credit_status["purchased"]:
<p class="credit-eligibility-msg">
% if credit_status["deadline"] < datetime.datetime.now(pytz.UTC) + datetime.timedelta(days=30):
${_("The opportunity to purchase credit for this course expires on {deadline}. You've worked hard - don't miss out!").format(deadline=get_default_time_display(credit_status["deadline"]))}
% else:
${_("Congratulations - you have met the requirements for credit in this course!")}
% endif
</p>
<div class="purchase_credit">
## TODO: set the URL for the link to the provider selection page on the E-Commerce service
<a class="btn purchase-credit-btn" href="#" target="_blank" data-course-key="${credit_status["course_key"]}">${_("Purchase Course Credit")}</a>
</div>
% elif credit_status["request_status"] in [None, "pending"]:
<p class="message-copy credit-request-pending-msg">
${_("Thanks for your payment! We are currently processing your course credit. You'll see a message here when the transaction is complete. For more information, see {provider_link}.").format(
provider_link=u'<a href="{provider_url}">{provider_name}</a>'.format(
provider_url=credit_status["provider_status_url"],
provider_name=credit_status["provider_name"],
)
)
}
</p>
% elif credit_status["request_status"] == "approved":
<p class="message-copy credit-request-approved-msg">
${_("Congratulations - you have received credit for this course! For more information, see {provider_link}.").format(
provider_link=u'<a href="{provider_url}">{provider_name}</a>'.format(
provider_url=credit_status["provider_status_url"],
provider_name=credit_status["provider_name"],
)
)
}
</p>
% elif credit_status["request_status"] == "rejected":
<p class="message-copy credit-request-rejected-msg">
${_("{provider_name} has declined your request for course credit. For more information, contact {provider_link}.").format(
provider_name=credit_status["provider_name"],
provider_link=u'<a href="{provider_url}">{provider_name}</a>'.format(
provider_url=credit_status["provider_status_url"],
provider_name=credit_status["provider_name"],
)
)
}
</p>
% endif
</div>
% endif
<%page args="credit_message" />
<%!
from django.utils.translation import ugettext as _
from course_modes.models import CourseMode
from util.date_utils import get_default_time_display
%>
<%namespace name='static' file='../static_content.html'/>
<%block name="js_extra" args="credit_message">
<%static:js group='credit_wv'/>
<script type="text/javascript">
$(document).ready(function() {
$.ajaxSetup({
headers: {
'X-CSRFToken': $.cookie('csrftoken')
},
dataType: 'json'
});
$(".purchase-credit-btn").click(function() {
var data = {
user_id: "${credit_message['user_id']}",
course_id: "${credit_message['course_id']}"
};
Logger.log('edx.credit.shared', data);
});
});
</script>
</%block>
<div class="message message-status is-shown">
<p>
% if credit_message["status"] == "requirements_meet":
<span>
% if credit_message["urgent"]:
${_("{username}, your eligibility for credit expires on {expiry}. Don't miss out!").format(
username=credit_message["user_full_name"],
expiry=get_default_time_display(credit_message["expiry"])
)
}
% else:
${_("{congrats} {username}, You have meet requirements for credit.").format(
congrats="<b>Congratulations</b>",
username=credit_message["user_full_name"]
)
}
% endif
</span>
<span class="purchase_credit"> <a class="btn purchase-credit-btn" href="" target="_blank">${_("Purchase Credit")}</a> </span>
% elif credit_message["status"] == "pending":
${_("Thank you, your payment is complete, your credit is processing. Please see {provider_link} for more information.").format(
provider_link='<a href="#" target="_blank">{}</a>'.format(credit_message["provider"]["display_name"])
)
}
% elif credit_message["status"] == "approved":
${_("Thank you, your credit is approved. Please see {provider_link} for more information.").format(
provider_link='<a href="#" target="_blank">{}</a>'.format(credit_message["provider"]["display_name"])
)
}
% elif credit_message["status"] == "rejected":
${_("Your credit has been denied. Please contact {provider_link} for more information.").format(
provider_link='<a href="#" target="_blank">{}</a>'.format(credit_message["provider"]["display_name"])
)
}
% endif
</p>
</div>
......@@ -2,7 +2,33 @@
Django admin page for credit eligibility
"""
from ratelimitbackend import admin
from .models import CreditCourse, CreditProvider
from openedx.core.djangoapps.credit.models import (
CreditCourse, CreditProvider, CreditEligibility, CreditRequest
)
admin.site.register(CreditCourse)
admin.site.register(CreditProvider)
class CreditCourseAdmin(admin.ModelAdmin):
"""Admin for credit courses. """
search_fields = ("course_key",)
class CreditProviderAdmin(admin.ModelAdmin):
"""Admin for credit providers. """
search_fields = ("provider_id", "display_name")
class CreditEligibilityAdmin(admin.ModelAdmin):
"""Admin for credit eligibility. """
search_fields = ("username", "course__course_key")
class CreditRequestAdmin(admin.ModelAdmin):
"""Admin for credit requests. """
search_fields = ("uuid", "username", "course__course_key", "provider__provider_id")
readonly_fields = ("uuid",)
admin.site.register(CreditCourse, CreditCourseAdmin)
admin.site.register(CreditProvider, CreditProviderAdmin)
admin.site.register(CreditEligibility, CreditEligibilityAdmin)
admin.site.register(CreditRequest, CreditRequestAdmin)
"""
Credit Python API.
This module aggregates the API functions from the eligibility and provider APIs.
"""
from .eligibility import * # pylint: disable=wildcard-import
from .provider import * # pylint: disable=wildcard-import
......@@ -39,24 +39,24 @@ def listen_for_grade_calculation(sender, username, grade_summary, course_key, de
kwargs : None
"""
from openedx.core.djangoapps.credit.api import (
is_credit_course, get_credit_requirement, set_credit_requirement_status
)
# This needs to be imported here to avoid a circular dependency
# that can cause syncdb to fail.
from openedx.core.djangoapps.credit import api
course_id = CourseKey.from_string(unicode(course_key))
is_credit = is_credit_course(course_id)
is_credit = api.is_credit_course(course_id)
if is_credit:
requirement = get_credit_requirement(course_id, 'grade', 'grade')
if requirement:
criteria = requirement.get('criteria')
requirements = api.get_credit_requirements(course_id, namespace='grade')
if requirements:
criteria = requirements[0].get('criteria')
if criteria:
min_grade = criteria.get('min_grade')
if grade_summary['percent'] >= min_grade:
reason_dict = {'final_grade': grade_summary['percent']}
set_credit_requirement_status(
api.set_credit_requirement_status(
username, course_id, 'grade', 'grade', status="satisfied", reason=reason_dict
)
elif deadline and deadline < timezone.now():
set_credit_requirement_status(
api.set_credit_requirement_status(
username, course_id, 'grade', 'grade', status="failed", reason={}
)
......@@ -41,19 +41,17 @@ class TestMinGradedRequirementStatus(ModuleStoreTestCase):
self.client.login(username=self.user.username, password=self.user.password)
# Enable the course for credit
credit_course = CreditCourse.objects.create(
CreditCourse.objects.create(
course_key=self.course.id,
enabled=True,
)
# Configure a credit provider for the course
credit_provider = CreditProvider.objects.create(
CreditProvider.objects.create(
provider_id="ASU",
enable_integration=True,
provider_url="https://credit.example.com/request",
)
credit_course.providers.add(credit_provider)
credit_course.save()
requirements = [{
"namespace": "grade",
......
......@@ -73,13 +73,11 @@ class CreditProviderViewTests(UrlResetMixin, TestCase):
)
# Configure a credit provider for the course
credit_provider = CreditProvider.objects.create(
CreditProvider.objects.create(
provider_id=self.PROVIDER_ID,
enable_integration=True,
provider_url=self.PROVIDER_URL,
)
credit_course.providers.add(credit_provider)
credit_course.save()
# Add a single credit requirement (final grade)
requirement = CreditRequirement.objects.create(
......@@ -256,11 +254,8 @@ class CreditProviderViewTests(UrlResetMixin, TestCase):
other_provider_id = "other_provider"
other_provider_secret_key = "1d01f067a5a54b0b8059f7095a7c636d"
# Create an additional credit provider and associate it with the course.
credit_course = CreditCourse.objects.get(course_key=self.COURSE_KEY)
credit_provider = CreditProvider.objects.create(provider_id=other_provider_id, enable_integration=True)
credit_course.providers.add(credit_provider)
credit_course.save()
# Create an additional credit provider
CreditProvider.objects.create(provider_id=other_provider_id, enable_integration=True)
# Initiate a credit request with the first provider
request_uuid = self._create_credit_request_and_get_uuid(self.USERNAME, self.COURSE_KEY)
......
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