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 + %> +
+ -
- % 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()