Commit f07e4b9b by Aamir

Merge pull request #8387 from edx/aamir-khan/ECOM-1524-dashboard-credit-display-new

Aamir khan/ecom 1524 dashboard credit display new
parents 2dfca626 987889fc
...@@ -7,8 +7,10 @@ import uuid ...@@ -7,8 +7,10 @@ import uuid
import time import time
import json import json
import warnings import warnings
from datetime import timedelta
from collections import defaultdict from collections import defaultdict
from pytz import UTC from pytz import UTC
from requests import HTTPError
from ipware.ip import get_ip from ipware.ip import get_ip
from django.conf import settings from django.conf import settings
...@@ -25,21 +27,19 @@ from django.db import IntegrityError, transaction ...@@ -25,21 +27,19 @@ from django.db import IntegrityError, transaction
from django.http import (HttpResponse, HttpResponseBadRequest, HttpResponseForbidden, from django.http import (HttpResponse, HttpResponseBadRequest, HttpResponseForbidden,
HttpResponseServerError, Http404) HttpResponseServerError, Http404)
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils import timezone
from django.utils.translation import ungettext from django.utils.translation import ungettext
from django.utils.http import cookie_date, base36_to_int from django.utils.http import cookie_date, base36_to_int
from django.utils.translation import ugettext as _, get_language from django.utils.translation import ugettext as _, get_language
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.decorators.http import require_POST, require_GET from django.views.decorators.http import require_POST, require_GET
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from ratelimitbackend.exceptions import RateLimitException from ratelimitbackend.exceptions import RateLimitException
from requests import HTTPError
from social.apps.django_app import utils as social_utils from social.apps.django_app import utils as social_utils
from social.backends import oauth as social_oauth from social.backends import oauth as social_oauth
...@@ -123,13 +123,12 @@ from notification_prefs.views import enable_notifications ...@@ -123,13 +123,12 @@ from notification_prefs.views import enable_notifications
# Note that this lives in openedx, so this dependency should be refactored. # 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.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") log = logging.getLogger("edx.student")
AUDIT_LOG = logging.getLogger("audit") AUDIT_LOG = logging.getLogger("audit")
ReverifyInfo = namedtuple('ReverifyInfo', 'course_id course_name course_number date status display') # pylint: disable=invalid-name ReverifyInfo = namedtuple('ReverifyInfo', 'course_id course_name course_number date status display') # pylint: disable=invalid-name
SETTING_CHANGE_INITIATED = 'edx.user.settings.change_initiated' SETTING_CHANGE_INITIATED = 'edx.user.settings.change_initiated'
...@@ -523,6 +522,13 @@ def dashboard(request): ...@@ -523,6 +522,13 @@ def dashboard(request):
course_enrollment_pairs, course_modes_by_course course_enrollment_pairs, course_modes_by_course
) )
# Retrieve the course modes for each course
enrolled_courses_dict = {}
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) course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True)
message = "" message = ""
...@@ -628,6 +634,7 @@ def dashboard(request): ...@@ -628,6 +634,7 @@ def dashboard(request):
context = { context = {
'enrollment_message': enrollment_message, 'enrollment_message': enrollment_message,
'credit_messages': credit_messages,
'course_enrollment_pairs': course_enrollment_pairs, 'course_enrollment_pairs': course_enrollment_pairs,
'course_optouts': course_optouts, 'course_optouts': course_optouts,
'message': message, 'message': message,
...@@ -692,6 +699,47 @@ def _create_recent_enrollment_message(course_enrollment_pairs, course_modes): ...@@ -692,6 +699,47 @@ 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): def _get_recently_enrolled_courses(course_enrollment_pairs):
"""Checks to see if the student has recently enrolled in courses. """Checks to see if the student has recently enrolled in courses.
......
...@@ -1339,6 +1339,12 @@ certificates_web_view_js = [ ...@@ -1339,6 +1339,12 @@ certificates_web_view_js = [
'js/src/logger.js', 'js/src/logger.js',
] ]
credit_web_view_js = [
'js/vendor/jquery.min.js',
'js/vendor/jquery.cookie.js',
'js/src/logger.js',
]
PIPELINE_CSS = { PIPELINE_CSS = {
'style-vendor': { 'style-vendor': {
'source_filenames': [ 'source_filenames': [
...@@ -1578,6 +1584,10 @@ PIPELINE_JS = { ...@@ -1578,6 +1584,10 @@ PIPELINE_JS = {
'utility': { 'utility': {
'source_filenames': ['js/src/utility.js'], 'source_filenames': ['js/src/utility.js'],
'output_filename': 'js/utility.js' 'output_filename': 'js/utility.js'
},
'credit_wv': {
'source_filenames': credit_web_view_js,
'output_filename': 'js/credit/web_view.js'
} }
} }
......
...@@ -531,6 +531,10 @@ ...@@ -531,6 +531,10 @@
padding: 0; padding: 0;
} }
.purchase_credit {
float: right;
}
.message { .message {
@extend %ui-depth1; @extend %ui-depth1;
border-radius: 3px; border-radius: 3px;
......
...@@ -83,7 +83,8 @@ from django.core.urlresolvers import reverse ...@@ -83,7 +83,8 @@ from django.core.urlresolvers import reverse
<% is_course_blocked = (course.id in block_courses) %> <% is_course_blocked = (course.id in block_courses) %>
<% course_verification_status = verification_status_by_course.get(course.id, {}) %> <% course_verification_status = verification_status_by_course.get(course.id, {}) %>
<% course_requirements = courses_requirements_not_met.get(course.id) %> <% course_requirements = courses_requirements_not_met.get(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_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" />
% endfor % endfor
% if settings.FEATURES.get('CUSTOM_COURSES_EDX', False): % 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" /> <%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" />
<%! <%!
import urllib import urllib
...@@ -273,7 +273,11 @@ from student.helpers import ( ...@@ -273,7 +273,11 @@ from student.helpers import (
<ul class="messages-list"> <ul class="messages-list">
% if course.may_certify() and cert_status: % if course.may_certify() and cert_status:
<%include file='_dashboard_certificate_information.html' args='cert_status=cert_status,course=course, enrollment=enrollment'/> <%include file='_dashboard_certificate_information.html' args='cert_status=cert_status,course=course, enrollment=enrollment'/>
% endif % endif
% if credit_message:
<%include file='_dashboard_credit_information.html' args='credit_message=credit_message'/>
% 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: % 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"> <div class="message message-status wrapper-message-primary is-shown">
......
<%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>
...@@ -26,6 +26,7 @@ from .exceptions import ( ...@@ -26,6 +26,7 @@ from .exceptions import (
) )
from .models import ( from .models import (
CreditCourse, CreditCourse,
CreditProvider,
CreditRequirement, CreditRequirement,
CreditRequirementStatus, CreditRequirementStatus,
CreditRequest, CreditRequest,
...@@ -33,6 +34,7 @@ from .models import ( ...@@ -33,6 +34,7 @@ from .models import (
) )
from .signature import signature, get_shared_secret_key from .signature import signature, get_shared_secret_key
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -211,14 +213,13 @@ def create_credit_request(course_key, provider_id, username): ...@@ -211,14 +213,13 @@ def create_credit_request(course_key, provider_id, username):
""" """
try: try:
user_eligibility = CreditEligibility.objects.select_related('course', 'provider').get( user_eligibility = CreditEligibility.objects.select_related('course').get(
username=username, username=username,
course__course_key=course_key, course__course_key=course_key
provider__provider_id=provider_id
) )
credit_course = user_eligibility.course credit_course = user_eligibility.course
credit_provider = user_eligibility.provider credit_provider = credit_course.providers.get(provider_id=provider_id)
except CreditEligibility.DoesNotExist: except (CreditEligibility.DoesNotExist, CreditProvider.DoesNotExist):
log.warning(u'User tried to initiate a request for credit, but the user is not eligible for credit') log.warning(u'User tried to initiate a request for credit, but the user is not eligible for credit')
raise UserIsNotEligible raise UserIsNotEligible
...@@ -614,3 +615,132 @@ def is_credit_course(course_key): ...@@ -614,3 +615,132 @@ def is_credit_course(course_key):
return False return False
return CreditCourse.is_credit_course(course_key=course_key) return CreditCourse.is_credit_course(course_key=course_key)
def get_credit_request_status(username, course_key):
"""Get the credit request status.
This function returns the status of credit request of user for given course.
It returns the latest request status for the any credit provider.
The valid status are 'pending', 'approved' or 'rejected'.
Args:
username(str): The username of user
course_key(CourseKey): The course locator key
Returns:
A dictionary of credit request user has made if any
"""
credit_request = CreditRequest.get_user_request_status(username, course_key)
if credit_request:
credit_status = {
"uuid": credit_request.uuid,
"timestamp": credit_request.modified,
"course_key": credit_request.course.course_key,
"provider": {
"id": credit_request.provider.provider_id,
"display_name": credit_request.provider.display_name
},
"status": credit_request.status
}
else:
credit_status = {}
return credit_status
def _get_duration_and_providers(credit_course):
"""Returns the credit providers and eligibility durations.
The eligibility_duration is the max of the credit duration of
all the credit providers of given course.
Args:
credit_course(CreditCourse): The CreditCourse object
Returns:
Tuple of eligibility_duration and credit providers of given course
"""
providers = credit_course.providers.all()
seconds_good_for_display = 0
providers_list = []
for provider in providers:
providers_list.append(
{
"id": provider.provider_id,
"display_name": provider.display_name,
"eligibility_duration": provider.eligibility_duration,
"provider_url": provider.provider_url
}
)
eligibility_duration = int(provider.eligibility_duration) if provider.eligibility_duration else 0
seconds_good_for_display = max(eligibility_duration, seconds_good_for_display)
return seconds_good_for_display, providers_list
def get_credit_eligibility(username):
"""
Returns the all the eligibility the user has meet.
Args:
username(str): The username of user
Example:
>> get_credit_eligibility('Aamir'):
{
"edX/DemoX/Demo_Course": {
"created_at": "2015-12-21",
"providers": [
"id": 12,
"display_name": "Arizona State University",
"eligibility_duration": 60,
"provider_url": "http://arizona/provideere/link"
],
"seconds_good_for_display": 90
}
}
Returns:
A dict of eligibilities
"""
eligibilities = CreditEligibility.get_user_eligibility(username)
user_credit_requests = get_credit_requests_for_user(username)
request_dict = {}
# Change the list to dict for iteration
for request in user_credit_requests:
request_dict[unicode(request["course_key"])] = request
user_eligibilities = {}
for eligibility in eligibilities:
course_key = eligibility.course.course_key
duration, providers_list = _get_duration_and_providers(eligibility.course)
user_eligibilities[unicode(course_key)] = {
"created_at": eligibility.created,
"seconds_good_for_display": duration,
"providers": providers_list,
}
# Default status is requirements_meet
user_eligibilities[unicode(course_key)]["status"] = "requirements_meet"
# If there is some request user has made for this eligibility then update the status
if unicode(course_key) in request_dict:
user_eligibilities[unicode(course_key)]["status"] = request_dict[unicode(course_key)]["status"]
user_eligibilities[unicode(course_key)]["provider"] = request_dict[unicode(course_key)]["provider"]
return user_eligibilities
def get_purchased_credit_courses(username): # pylint: disable=unused-argument
"""
Returns the purchased credit courses.
Args:
username(str): Username of the student
Returns:
A dict of courses user has purchased from the credit provider after completion
"""
# TODO: How to track the purchased courses. It requires Will's work for credit provider integration
return {}
...@@ -307,12 +307,24 @@ class CreditEligibility(TimeStampedModel): ...@@ -307,12 +307,24 @@ class CreditEligibility(TimeStampedModel):
""" """
username = models.CharField(max_length=255, db_index=True) username = models.CharField(max_length=255, db_index=True)
course = models.ForeignKey(CreditCourse, related_name="eligibilities") course = models.ForeignKey(CreditCourse, related_name="eligibilities")
provider = models.ForeignKey(CreditProvider, related_name="eligibilities")
class Meta(object): # pylint: disable=missing-docstring class Meta(object): # pylint: disable=missing-docstring
unique_together = ('username', 'course') unique_together = ('username', 'course')
@classmethod @classmethod
def get_user_eligibility(cls, username):
"""Returns the eligibilities of given user.
Args:
username(str): Username of the user
Returns:
CreditEligibility queryset for the user
"""
return cls.objects.filter(username=username).select_related('course').prefetch_related('course__providers')
@classmethod
def is_user_eligible_for_credit(cls, course_key, username): def is_user_eligible_for_credit(cls, course_key, username):
"""Check if the given user is eligible for the provided credit course """Check if the given user is eligible for the provided credit course
...@@ -361,6 +373,12 @@ class CreditRequest(TimeStampedModel): ...@@ -361,6 +373,12 @@ class CreditRequest(TimeStampedModel):
history = HistoricalRecords() history = HistoricalRecords()
class Meta(object): # pylint: disable=missing-docstring
# Enforce the constraint that each user can have exactly one outstanding
# request to a given provider. Multiple requests use the same UUID.
unique_together = ('username', 'course', 'provider')
get_latest_by = 'created'
@classmethod @classmethod
def credit_requests_for_user(cls, username): def credit_requests_for_user(cls, username):
""" """
...@@ -402,7 +420,21 @@ class CreditRequest(TimeStampedModel): ...@@ -402,7 +420,21 @@ class CreditRequest(TimeStampedModel):
for request in cls.objects.select_related('course', 'provider').filter(username=username) for request in cls.objects.select_related('course', 'provider').filter(username=username)
] ]
class Meta(object): # pylint: disable=missing-docstring @classmethod
# Enforce the constraint that each user can have exactly one outstanding def get_user_request_status(cls, username, course_key):
# request to a given provider. Multiple requests use the same UUID. """Returns the latest credit request of user against the given course.
unique_together = ('username', 'course', 'provider')
Args:
username(str): The username of requesting user
course_key(CourseKey): The course identifier
Returns:
CreditRequest if any otherwise None
"""
try:
return cls.objects.filter(
username=username, course__course_key=course_key
).select_related('course', 'provider').latest()
except cls.DoesNotExist:
return None
""" """
Tests for the API functions in the credit app. Tests for the API functions in the credit app.
""" """
import unittest
import datetime import datetime
import ddt import ddt
import pytz import pytz
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.db import connection, transaction from django.db import connection, transaction
from django.core.urlresolvers import reverse
from django.conf import settings
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
from student.tests.factories import UserFactory
from util.date_utils import from_timestamp from util.date_utils import from_timestamp
from openedx.core.djangoapps.credit import api from openedx.core.djangoapps.credit import api
from openedx.core.djangoapps.credit.exceptions import ( from openedx.core.djangoapps.credit.exceptions import (
...@@ -34,13 +35,20 @@ from openedx.core.djangoapps.credit.api import ( ...@@ -34,13 +35,20 @@ from openedx.core.djangoapps.credit.api import (
set_credit_requirement_status, set_credit_requirement_status,
get_credit_requirement get_credit_requirement
) )
from student.models import CourseEnrollment
from student.views import _create_credit_availability_message
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6" TEST_CREDIT_PROVIDER_SECRET_KEY = "931433d583c84ca7ba41784bad3232e6"
@override_settings(CREDIT_PROVIDER_SECRET_KEYS={ @override_settings(CREDIT_PROVIDER_SECRET_KEYS={
"hogwarts": TEST_CREDIT_PROVIDER_SECRET_KEY "hogwarts": TEST_CREDIT_PROVIDER_SECRET_KEY,
"ASU": TEST_CREDIT_PROVIDER_SECRET_KEY,
"MIT": TEST_CREDIT_PROVIDER_SECRET_KEY
}) })
class CreditApiTestBase(TestCase): class CreditApiTestBase(TestCase):
""" """
...@@ -212,7 +220,7 @@ class CreditRequirementApiTests(CreditApiTestBase): ...@@ -212,7 +220,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
def test_is_user_eligible_for_credit(self): def test_is_user_eligible_for_credit(self):
credit_course = self.add_credit_course() credit_course = self.add_credit_course()
CreditEligibility.objects.create( CreditEligibility.objects.create(
course=credit_course, username="staff", provider=CreditProvider.objects.get(provider_id=self.PROVIDER_ID) course=credit_course, username="staff"
) )
is_eligible = api.is_user_eligible_for_credit('staff', credit_course.course_key) is_eligible = api.is_user_eligible_for_credit('staff', credit_course.course_key)
self.assertTrue(is_eligible) self.assertTrue(is_eligible)
...@@ -380,19 +388,26 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase): ...@@ -380,19 +388,26 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
# Initial status should be "pending" # Initial status should be "pending"
self._assert_credit_status("pending") self._assert_credit_status("pending")
credit_request_status = api.get_credit_request_status(self.USER_INFO['username'], self.course_key)
self.assertEqual(credit_request_status["status"], "pending")
# Update the status # Update the status
api.update_credit_request_status(request["parameters"]["request_uuid"], self.PROVIDER_ID, status) api.update_credit_request_status(request["parameters"]["request_uuid"], self.PROVIDER_ID, status)
self._assert_credit_status(status) self._assert_credit_status(status)
credit_request_status = api.get_credit_request_status(self.USER_INFO['username'], self.course_key)
self.assertEqual(credit_request_status["status"], status)
def test_query_counts(self): def test_query_counts(self):
# Yes, this is a lot of queries, but this API call is also doing a lot of work :) # Yes, this is a lot of queries, but this API call is also doing a lot of work :)
# - 1 query: Check the user's eligibility and retrieve the credit course and provider. # - 1 query: Check the user's eligibility and retrieve the credit course
# - 1 Get the provider of the credit course.
# - 2 queries: Get-or-create the credit request. # - 2 queries: Get-or-create the credit request.
# - 1 query: Retrieve user account and profile information from the user API. # - 1 query: Retrieve user account and profile information from the user API.
# - 1 query: Look up the user's final grade from the credit requirements table. # - 1 query: Look up the user's final grade from the credit requirements table.
# - 2 queries: Update the request. # - 2 queries: Update the request.
# - 2 queries: Update the history table for the request. # - 2 queries: Update the history table for the request.
with self.assertNumQueries(9): with self.assertNumQueries(10):
request = api.create_credit_request(self.course_key, self.PROVIDER_ID, self.USER_INFO['username']) request = api.create_credit_request(self.course_key, self.PROVIDER_ID, self.USER_INFO['username'])
# - 3 queries: Retrieve and update the request # - 3 queries: Retrieve and update the request
...@@ -522,12 +537,131 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase): ...@@ -522,12 +537,131 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
status.save() status.save()
CreditEligibility.objects.create( CreditEligibility.objects.create(
username=self.USER_INFO["username"], username=self.USER_INFO['username'],
course=CreditCourse.objects.get(course_key=self.course_key), course=CreditCourse.objects.get(course_key=self.course_key)
provider=CreditProvider.objects.get(provider_id=self.PROVIDER_ID)
) )
def _assert_credit_status(self, expected_status): def _assert_credit_status(self, expected_status):
"""Check the user's credit status. """ """Check the user's credit status. """
statuses = api.get_credit_requests_for_user(self.USER_INFO["username"]) statuses = api.get_credit_requests_for_user(self.USER_INFO["username"])
self.assertEqual(statuses[0]["status"], expected_status) self.assertEqual(statuses[0]["status"], expected_status)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class CreditMessagesTests(ModuleStoreTestCase, CreditApiTestBase):
"""
Test dashboard messages of credit course.
"""
FINAL_GRADE = 0.8
def setUp(self):
super(CreditMessagesTests, self).setUp()
self.student = UserFactory()
self.student.set_password('test') # pylint: disable=no-member
self.student.save() # pylint: disable=no-member
self.client.login(username=self.student.username, password='test')
# New Course
self.course = CourseFactory.create()
self.enrollment = CourseEnrollment.enroll(self.student, self.course.id)
def _set_creditcourse(self):
"""
Mark the course to credit
"""
# pylint: disable=attribute-defined-outside-init
self.first_provider = CreditProvider.objects.create(
provider_id="ASU",
display_name="Arizona State University",
provider_url="google.com",
enable_integration=True
) # pylint: disable=attribute-defined-outside-init
self.second_provider = CreditProvider.objects.create(
provider_id="MIT",
display_name="Massachusetts Institute of Technology",
provider_url="MIT.com",
enable_integration=True
) # pylint: disable=attribute-defined-outside-init
self.credit_course = CreditCourse.objects.create(course_key=self.course.id, enabled=True) # pylint: disable=attribute-defined-outside-init
self.credit_course.providers.add(self.first_provider)
self.credit_course.providers.add(self.second_provider)
def _set_user_eligible(self, credit_course, username):
"""
Mark the user eligible for credit for the given credit course.
"""
self.eligibility = CreditEligibility.objects.create(username=username, course=credit_course) # pylint: disable=attribute-defined-outside-init
def test_user_request_status(self):
request_status = api.get_credit_request_status(self.student.username, self.course.id)
self.assertEqual(len(request_status), 0)
def test_credit_messages(self):
self._set_creditcourse()
requirement = CreditRequirement.objects.create(
course=self.credit_course,
namespace="grade",
name="grade",
active=True
)
status = CreditRequirementStatus.objects.create(
username=self.student.username,
requirement=requirement,
)
status.status = "satisfied"
status.reason = {"final_grade": self.FINAL_GRADE}
status.save()
self._set_user_eligible(self.credit_course, self.student.username)
response = self.client.get(reverse("dashboard"))
self.assertContains(
response,
"<b>Congratulations</b> {}, You have meet requirements for credit.".format(
self.student.get_full_name() # pylint: disable=no-member
)
)
api.create_credit_request(self.course.id, self.first_provider.provider_id, self.student.username)
response = self.client.get(reverse("dashboard"))
self.assertContains(
response,
'Thank you, your payment is complete, your credit is processing. '
'Please see {provider_link} for more information.'.format(
provider_link='<a href="#" target="_blank">{provider_name}</a>'.format(
provider_name=self.first_provider.display_name
)
)
)
def test_query_counts(self):
# This check the number of queries executed while rendering the
# credit message to display on the dashboard.
# - 1 query: Check the user's eligibility.
# - 1 query: Get the user credit requests.
self._set_creditcourse()
requirement = CreditRequirement.objects.create(
course=self.credit_course,
namespace="grade",
name="grade",
active=True
)
status = CreditRequirementStatus.objects.create(
username=self.student.username,
requirement=requirement,
)
status.status = "satisfied"
status.reason = {"final_grade": self.FINAL_GRADE}
status.save()
with self.assertNumQueries(2):
enrollment_dict = {unicode(self.course.id): self.course}
_create_credit_availability_message(
enrollment_dict, self.student
)
...@@ -99,7 +99,6 @@ class CreditProviderViewTests(UrlResetMixin, TestCase): ...@@ -99,7 +99,6 @@ class CreditProviderViewTests(UrlResetMixin, TestCase):
CreditEligibility.objects.create( CreditEligibility.objects.create(
username=self.USERNAME, username=self.USERNAME,
course=credit_course, course=credit_course,
provider=credit_provider,
) )
def test_credit_request_and_response(self): def test_credit_request_and_response(self):
......
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