diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss
index 71a5bfe022..1901dcae37 100644
--- a/lms/static/sass/multicourse/_dashboard.scss
+++ b/lms/static/sass/multicourse/_dashboard.scss
@@ -782,14 +782,10 @@
}
.credit-action {
- .credit-msg {
- @include float(left);
- width: flex-grid(10, 12);
- }
-
.credit-btn {
@extend %btn-pl-yellow-base;
@include float(right);
+ margin-right: 5px;
background-image: none ;
text-shadow: none;
box-shadow: none;
diff --git a/lms/templates/dashboard/_dashboard_credit_info.html b/lms/templates/dashboard/_dashboard_credit_info.html
index 884b455a43..07d31c5b23 100644
--- a/lms/templates/dashboard/_dashboard_credit_info.html
+++ b/lms/templates/dashboard/_dashboard_credit_info.html
@@ -1,75 +1,87 @@
<%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["provider_name"]:
- <% provider_link='{}'.format(credit_status["provider_status_url"], credit_status["provider_name"]) %>
-% endif
+
% if credit_status["eligible"]:
+ <%
+ provider_link = '{name}'.format(
+ href=credit_status["provider_status_url"],
+ name=credit_status["provider_name"])
+
+ error = credit_status['error']
+
+
+ status = 'eligible'
+
+ # Translators: provider_name is the name of a credit provider or university (e.g. State University)
+ credit_msg = _("You have completed this course and are eligible to purchase course credit. Select Get Credit to get started.")
+ credit_msg_class = "credit-eligibility-msg"
+ credit_btn_class = "purchase-credit-btn"
+ credit_btn_label = _("Get Credit")
+ credit_btn_href = '{root}/credit/checkout/{course_id}/'.format(
+ root=settings.ECOMMERCE_PUBLIC_URL_ROOT,
+ course_id=credit_status['course_key'])
+
+ if credit_status["purchased"]:
+ request_status = credit_status["request_status"]
+ if request_status is None:
+ # Learner must initiate the credit request
+
+ # 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'.
+ credit_msg = _("Thank you for your payment. To receive course credit, you must now request credit "
+ "at the {link_to_provider_site} website. Select Request Credit to get started.").format(
+ link_to_provider_site=provider_link,
+ )
+ credit_msg_class = "credit-request-pending-msg"
+ credit_btn_label = _("Request Credit")
+ credit_btn_class = 'pending-credit-btn'
+ elif request_status == 'pending':
+ # Request received but not reviewed
+ ## Translators: provider_name is the name of a credit provider or university (e.g. State University)
+ credit_msg = _("{provider_name} has received your course credit request. We will update you when credit processing is complete.").format(provider_name=credit_status["provider_name"])
+ credit_msg_class = "credit-request-pending-msg"
+ credit_btn_label = _("View Details")
+ credit_btn_class = 'pending-credit-btn'
+ elif request_status == 'approved':
+ # Credit granted!
+ # 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.
+ credit_msg = _("Congratulations! {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,
+ )
+ credit_msg_class = "credit-request-approved-msg"
+ credit_btn_href = credit_status['provider_status_url']
+ credit_btn_label = _("View Credit")
+ elif request_status == 'rejected':
+ # REJECTED (by the credit provider)!
+ ## 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.
+ credit_msg = _("{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,
+ )
+ credit_msg_class = "credit-request-rejected-msg"
+ credit_btn_label = None
+ %>
+
+
${_("An error occurred with this transaction. For help, contact {support_email}.").format(
- support_email=u'{address}'.format(
- address=settings.DEFAULT_FEEDBACK_EMAIL
- )
+ support_email=u'{address}'.format(
+ address=settings.DEFAULT_FEEDBACK_EMAIL
+ )
)}
-
- % if not credit_status["purchased"] and not credit_status["error"] :
-
- ## Translators: provider_name is the name of a credit provider or university (e.g. State University)
- ${_("You have completed this course and are eligible to purchase course credit. Select Get Credit to get started.")}
-
-
- % elif credit_status["request_status"] in [None, "pending"] and not credit_status["error"]:
- % if credit_status["request_status"] == "pending":
-
- ## Translators: provider_name is the name of a credit provider or university (e.g. State University)
- ${_("{provider_name} has received your course credit request. We will update you when credit processing is complete.").format(
- provider_name=credit_status["provider_name"],
- )
- }
-
- % elif credit_status["request_status"] is None:
-
- ## 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 Request Credit to get started.").format(
- link_to_provider_site=provider_link,
- )
- }
-
+
+ % if credit_btn_label:
+
+ ${credit_btn_label}
+
% endif
-
${_("View Details")}
- % elif credit_status["request_status"] == "approved" and not credit_status["error"] :
-
- ## 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.
- ${_("Congratulations! {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,
- )
- }
-
-
${_("View Credit")}
- % elif credit_status["request_status"] == "rejected" and not credit_status["error"] :
-
- ## 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,
- )
- }
-
- % endif
+
${credit_msg}
% endif
diff --git a/openedx/core/lib/api/permissions.py b/openedx/core/lib/api/permissions.py
index 2d27c58bf9..6b7de66e7e 100644
--- a/openedx/core/lib/api/permissions.py
+++ b/openedx/core/lib/api/permissions.py
@@ -3,8 +3,8 @@ API library for Django REST Framework permissions-oriented workflows
"""
from django.conf import settings
-from rest_framework import permissions
from django.http import Http404
+from rest_framework import permissions
from student.roles import CourseStaffRole
@@ -13,6 +13,7 @@ class ApiKeyHeaderPermission(permissions.BasePermission):
"""
Django REST Framework permissions class used to manage API Key integrations
"""
+
def has_permission(self, request, view):
"""
Check for permissions by matching the configured API key and header
@@ -35,8 +36,9 @@ class ApiKeyHeaderPermissionIsAuthenticated(ApiKeyHeaderPermission, permissions.
See ApiKeyHeaderPermission for more information how the API key portion is implemented.
"""
+
def has_permission(self, request, view):
- #TODO We can optimize this later on when we know which of these methods is used more often.
+ # TODO We can optimize this later on when we know which of these methods is used more often.
api_permissions = ApiKeyHeaderPermission.has_permission(self, request, view)
is_authenticated_permissions = permissions.IsAuthenticated.has_permission(self, request, view)
return api_permissions or is_authenticated_permissions
@@ -46,6 +48,7 @@ class IsUserInUrl(permissions.BasePermission):
"""
Permission that checks to see if the request user matches the user in the URL.
"""
+
def has_permission(self, request, view):
"""
Returns true if the current request is by the user themselves.
@@ -65,6 +68,7 @@ class IsUserInUrlOrStaff(IsUserInUrl):
"""
Permission that checks to see if the request user matches the user in the URL or has is_staff access.
"""
+
def has_permission(self, request, view):
if request.user.is_staff:
return True
@@ -76,6 +80,7 @@ class IsStaffOrReadOnly(permissions.BasePermission):
"""Permission that checks to see if the user is global or course
staff, permitting only read-only access if they are not.
"""
+
def has_object_permission(self, request, view, obj):
return (request.user.is_staff or
CourseStaffRole(obj.course_id).has_user(request.user) or
@@ -87,9 +92,12 @@ class IsStaffOrOwner(permissions.BasePermission):
Permission that allows access to admin users or the owner of an object.
The owner is considered the User object represented by obj.user.
"""
+
def has_object_permission(self, request, view, obj):
return request.user.is_staff or obj.user == request.user
def has_permission(self, request, view):
user = request.user
- return user.is_staff or (user.username == request.GET.get('username'))
+ return user.is_staff \
+ or (user.username == request.GET.get('username')) \
+ or (user.username == getattr(request, 'data', {}).get('username'))
diff --git a/openedx/core/lib/api/tests/test_permissions.py b/openedx/core/lib/api/tests/test_permissions.py
index cc2436085a..1ab26cd4d2 100644
--- a/openedx/core/lib/api/tests/test_permissions.py
+++ b/openedx/core/lib/api/tests/test_permissions.py
@@ -1,4 +1,6 @@
""" Tests for API permissions classes. """
+
+import ddt
from django.test import TestCase, RequestFactory
from openedx.core.lib.api.permissions import IsStaffOrOwner
@@ -10,8 +12,10 @@ class TestObject(object):
user = None
+@ddt.ddt
class IsStaffOrOwnerTests(TestCase):
""" Tests for IsStaffOrOwner permission class. """
+
def setUp(self):
super(IsStaffOrOwnerTests, self).setUp()
self.permission = IsStaffOrOwner()
@@ -50,13 +54,24 @@ class IsStaffOrOwnerTests(TestCase):
self.request.user = UserFactory.create(is_staff=True)
self.assertTrue(self.permission.has_permission(self.request, None))
- def test_has_permission_as_owner(self):
- """ Owners always have permission. """
+ def test_has_permission_as_owner_with_get(self):
+ """ Owners always have permission to make GET actions. """
user = UserFactory.create()
request = RequestFactory().get('/?username={}'.format(user.username))
request.user = user
self.assertTrue(self.permission.has_permission(request, None))
+ @ddt.data('patch', 'post', 'put')
+ def test_has_permission_as_owner_with_edit(self, action):
+ """ Owners always have permission to edit. """
+ user = UserFactory.create()
+
+ data = {'username': user.username}
+ request = getattr(RequestFactory(), action)('/', data, format='json')
+ request.user = user
+ request.data = data # Note (CCB): This is a hack that should be fixed. (ECOM-3171)
+ self.assertTrue(self.permission.has_permission(request, None))
+
def test_has_permission_as_non_owner(self):
""" Non-owners should not have permission. """
user = UserFactory.create()