Commit 37477204 by Clinton Blackburn

Merge pull request #623 from edx/revert-543-douglashall/WL-286/support_multitenant_oauth

Revert "WL-286 Add support for multitenant OAuth"
parents 809b7369 985a7b78
from django.conf import settings
from ecommerce.core.url_utils import get_lms_dashboard_url
def core(_request):
return {
'lms_dashboard_url': get_lms_dashboard_url(),
'platform_name': settings.PLATFORM_NAME,
'support_url': settings.SUPPORT_URL,
'platform_name': settings.PLATFORM_NAME
}
class MissingRequestError(Exception):
""" Raised when the current request is missing from threadlocal storage """
pass
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import jsonfield.fields
class Migration(migrations.Migration):
dependencies = [
('core', '0010_add_async_sample'),
]
operations = [
migrations.AddField(
model_name='siteconfiguration',
name='oauth_settings',
field=jsonfield.fields.JSONField(default=b'{}', help_text='JSON string containing OAuth backend settings.', verbose_name='OAuth settings'),
),
]
......@@ -34,13 +34,6 @@ class SiteConfiguration(models.Model):
null=False,
blank=False
)
oauth_settings = JSONField(
verbose_name=_('OAuth settings'),
help_text=_("JSON string containing OAuth backend settings."),
null=False,
blank=False,
default='{}'
)
class Meta(object):
unique_together = ('site', 'partner')
......
from django.test import override_settings, RequestFactory
from ecommerce.core.context_processors import core
from ecommerce.core.url_utils import get_lms_dashboard_url
from ecommerce.tests.testcases import TestCase
PLATFORM_NAME = 'Test Platform'
......@@ -12,11 +11,4 @@ class CoreContextProcessorTests(TestCase):
@override_settings(PLATFORM_NAME=PLATFORM_NAME, SUPPORT_URL=SUPPORT_URL)
def test_core(self):
request = RequestFactory().get('/')
self.assertDictEqual(
core(request),
{
'lms_dashboard_url': get_lms_dashboard_url(),
'platform_name': PLATFORM_NAME,
'support_url': SUPPORT_URL
}
)
self.assertDictEqual(core(request), {'platform_name': PLATFORM_NAME, 'support_url': SUPPORT_URL})
import mock
from ecommerce.core.exceptions import MissingRequestError
from ecommerce.core.url_utils import get_ecommerce_url, get_lms_url
from ecommerce.tests.testcases import TestCase
class UrlUtilityTests(TestCase):
@mock.patch('ecommerce.core.url_utils.get_current_request', mock.Mock(return_value=None))
def test_get_ecommerce_url_with_no_current_request(self):
with self.assertRaises(MissingRequestError):
get_ecommerce_url()
@mock.patch('ecommerce.core.url_utils.get_current_request', mock.Mock(return_value=None))
def test_get_lms_url_with_no_current_request(self):
with self.assertRaises(MissingRequestError):
get_lms_url()
......@@ -24,7 +24,6 @@ class HealthTests(TestCase):
"""Tests of the health endpoint."""
def setUp(self):
super(HealthTests, self).setUp()
self.fake_lms_response = Response()
def test_all_services_available(self, mock_lms_request):
......@@ -34,8 +33,6 @@ class HealthTests(TestCase):
self._assert_health(status.HTTP_200_OK, Status.OK, Status.OK, Status.OK)
@mock.patch('ecommerce.core.views.get_lms_heartbeat_url', mock.Mock(return_value=''))
@mock.patch('django.contrib.sites.middleware.get_current_site', mock.Mock(return_value=None))
@mock.patch('django.db.backends.base.base.BaseDatabaseWrapper.cursor', mock.Mock(side_effect=DatabaseError))
def test_database_outage(self, mock_lms_request):
"""Test that the endpoint reports when the database is unavailable."""
......
from urlparse import urljoin
from threadlocals.threadlocals import get_current_request
from ecommerce.core.exceptions import MissingRequestError
def get_ecommerce_url(path=''):
"""
Returns path joined with the appropriate ecommerce URL root for the current site
Raises:
MissingRequestError: If the current ecommerce site is not in threadlocal storage
"""
request = get_current_request()
if request:
ecommerce_url_root = "{}://{}".format(request.scheme, request.site.domain)
return urljoin(ecommerce_url_root, path)
raise MissingRequestError
def get_lms_commerce_api_url():
return get_lms_url('/api/commerce/v1/')
def get_lms_dashboard_url():
return get_lms_url('/dashboard')
def get_lms_enrollment_api_url():
return get_lms_url('/api/enrollment/v1/enrollment')
def get_lms_heartbeat_url():
return get_lms_url('/heartbeat')
def get_lms_url(path=''):
"""
Returns path joined with the appropriate LMS URL root for the current site
Raises:
MissingRequestError: If the current ecommerce site is not in threadlocal storage
"""
request = get_current_request()
if request:
return urljoin(request.site.siteconfiguration.lms_url_root, path)
raise MissingRequestError
def get_oauth2_provider_url():
return get_lms_url('/oauth2')
......@@ -16,8 +16,6 @@ from django.views.generic import View
from django.utils.decorators import method_decorator
from ecommerce.core.constants import Status, UnavailabilityMessage
from ecommerce.core.url_utils import get_lms_heartbeat_url
logger = logging.getLogger(__name__)
User = get_user_model()
......@@ -55,7 +53,7 @@ def health(_):
database_status = Status.UNAVAILABLE
try:
response = requests.get(get_lms_heartbeat_url(), timeout=1)
response = requests.get(settings.LMS_HEARTBEAT_URL, timeout=1)
if response.status_code == status.HTTP_200_OK:
lms_status = Status.OK
......
......@@ -11,11 +11,11 @@ from oscar.test.factories import OrderFactory, ConditionalOfferFactory, VoucherF
from oscar.test.utils import RequestFactory
import pytz
from ecommerce.core.url_utils import get_lms_url, get_lms_enrollment_api_url
from ecommerce.coupons.views import get_voucher_from_code, voucher_is_valid
from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.extensions.test.factories import prepare_voucher
from ecommerce.settings import get_lms_url
from ecommerce.tests.mixins import CouponMixin, LmsApiMockMixin
from ecommerce.tests.testcases import TestCase
......@@ -187,7 +187,7 @@ class CouponOfferViewTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
response = self.client.get(self.path_with_code)
response_text = (
'Could not get course information. '
'[Client Error 404: http://lms.testserver.fake/api/courses/v1/courses/{}/]'
'[Client Error 404: http://127.0.0.1:8000/api/courses/v1/courses/{}/]'
).format(course.id)
self.assertEqual(response.context['error'], _(response_text))
......@@ -264,8 +264,8 @@ class CouponRedeemViewTests(CouponMixin, CourseCatalogTestMixin, TestCase):
def test_basket_redirect_enrollment_code(self):
""" Verify the view redirects to LMS when an enrollment code is provided. """
self.create_and_test_coupon()
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200)
self.assert_redemption_page_redirects(get_lms_url())
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=200)
self.assert_redemption_page_redirects(settings.LMS_URL_ROOT)
@httpretty.activate
def test_multiple_vouchers(self):
......@@ -273,5 +273,5 @@ class CouponRedeemViewTests(CouponMixin, CourseCatalogTestMixin, TestCase):
self.create_and_test_coupon()
basket = Basket.get_basket(self.user, self.site)
basket.vouchers.add(Voucher.objects.get(code=COUPON_CODE))
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200)
self.assert_redemption_page_redirects(get_lms_url())
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=200)
self.assert_redemption_page_redirects(settings.LMS_URL_ROOT)
......@@ -15,7 +15,6 @@ from oscar.core.loading import get_class, get_model
from edx_rest_api_client.client import EdxRestApiClient
from edx_rest_api_client.exceptions import SlumberHttpBaseException
from ecommerce.core.url_utils import get_lms_url
from ecommerce.core.views import StaffOnlyMixin
from ecommerce.extensions.analytics.utils import prepare_analytics_data
from ecommerce.extensions.api.constants import APIConstants as AC
......@@ -23,6 +22,7 @@ from ecommerce.extensions.api.data import get_lms_footer
from ecommerce.extensions.basket.utils import prepare_basket
from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
from ecommerce.extensions.offer.utils import format_benefit_value
from ecommerce.settings import get_lms_url
Applicator = get_class('offer.utils', 'Applicator')
......
......@@ -8,8 +8,8 @@ from edx_rest_api_client.client import EdxRestApiClient
from edx_rest_api_client.exceptions import SlumberHttpBaseException
import requests
from ecommerce.core.url_utils import get_lms_url, get_lms_commerce_api_url
from ecommerce.courses.utils import mode_for_seat
from ecommerce.settings import get_lms_url
logger = logging.getLogger(__name__)
......@@ -74,9 +74,8 @@ class LMSPublisher(object):
course_id=course_id
)
commerce_api_url = get_lms_commerce_api_url()
if not commerce_api_url:
logger.error('Commerce API URL is not set. Commerce data will not be published!')
if not settings.COMMERCE_API_URL:
logger.error('COMMERCE_API_URL is not set. Commerce data will not be published!')
return error_message
name = course.name
......@@ -110,7 +109,7 @@ class LMSPublisher(object):
'modes': modes,
}
url = '{}/courses/{}/'.format(commerce_api_url.rstrip('/'), course_id)
url = '{}/courses/{}/'.format(settings.COMMERCE_API_URL.rstrip('/'), course_id)
headers = {
'Content-Type': 'application/json',
......
......@@ -2,16 +2,17 @@ import datetime
import json
import ddt
from django.conf import settings
from django.test import override_settings
import httpretty
import mock
from requests import Timeout
from testfixtures import LogCapture
from ecommerce.core.url_utils import get_lms_url, get_lms_commerce_api_url
from ecommerce.courses.publishers import LMSPublisher
from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.settings import get_lms_url
from ecommerce.tests.testcases import TestCase
EDX_API_KEY = 'edx'
......@@ -20,7 +21,7 @@ LOGGER_NAME = 'ecommerce.courses.publishers'
@ddt.ddt
@override_settings(EDX_API_KEY=EDX_API_KEY)
@override_settings(COMMERCE_API_URL='http://example.com/commerce/api/v1/', EDX_API_KEY=EDX_API_KEY)
class LMSPublisherTests(CourseCatalogTestMixin, TestCase):
def setUp(self):
super(LMSPublisherTests, self).setUp()
......@@ -36,7 +37,7 @@ class LMSPublisherTests(CourseCatalogTestMixin, TestCase):
self.assertTrue(httpretty.is_enabled(), 'httpretty must be enabled to mock Commerce API calls.')
body = body or {}
url = '{}/courses/{}/'.format(get_lms_commerce_api_url().rstrip('/'), self.course.id)
url = '{}/courses/{}/'.format(settings.COMMERCE_API_URL.rstrip('/'), self.course.id)
httpretty.register_uri(httpretty.PUT, url, status=status, body=json.dumps(body),
content_type=JSON)
......@@ -52,16 +53,15 @@ class LMSPublisherTests(CourseCatalogTestMixin, TestCase):
content_type=JSON
)
@mock.patch('ecommerce.courses.publishers.get_lms_commerce_api_url', mock.Mock(return_value=None))
def test_commerce_api_url_not_set(self):
""" If the commerce API url cannot be retrieved, the method should log an ERROR message and return """
with LogCapture(LOGGER_NAME) as l:
response = self.publisher.publish(self.course)
l.check(
(LOGGER_NAME, 'ERROR', 'Commerce API URL is not set. Commerce data will not be published!')
)
self.assertIsNotNone(response)
self.assertEqual(response, self.error_message)
@ddt.data('', None)
def test_commerce_api_url_not_set(self, setting_value):
""" If the Commerce API is not setup, the method should log an INFO message and return """
with override_settings(COMMERCE_API_URL=setting_value):
with LogCapture(LOGGER_NAME) as l:
response = self.publisher.publish(self.course)
l.check((LOGGER_NAME, 'ERROR', 'COMMERCE_API_URL is not set. Commerce data will not be published!'))
self.assertIsNotNone(response)
self.assertEqual(response, self.error_message)
def test_api_exception(self):
""" If an exception is raised when communicating with the Commerce API, an ERROR message should be logged. """
......
......@@ -5,7 +5,7 @@ from django.conf import settings
from django.core.urlresolvers import reverse
from testfixtures import LogCapture
from ecommerce.core.url_utils import get_lms_url
from ecommerce.settings import get_lms_url
from ecommerce.tests.testcases import TestCase
LOGGER_NAME = 'ecommerce.courses.views'
......
......@@ -12,9 +12,9 @@ from edx_rest_api_client.client import EdxRestApiClient
from requests import Timeout
from slumber.exceptions import SlumberBaseException
from ecommerce.core.url_utils import get_lms_url
from ecommerce.core.views import StaffOnlyMixin
from ecommerce.extensions.partner.shortcuts import get_partner_for_site
from ecommerce.settings import get_lms_url
logger = logging.getLogger(__name__)
......
......@@ -11,10 +11,10 @@ import httpretty
from waffle.models import Switch
from ecommerce.core.tests import toggle_switch
from ecommerce.core.url_utils import get_lms_url
from ecommerce.courses.models import Course
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.extensions.payment.helpers import get_processor_class
from ecommerce.settings import get_lms_url
from ecommerce.tests.mixins import JwtMixin
from ecommerce.tests.testcases import TestCase
......
......@@ -14,11 +14,11 @@ from edx_rest_api_client.client import EdxRestApiClient
from slumber.exceptions import SlumberHttpBaseException
import waffle
from ecommerce.core.url_utils import get_lms_url
from ecommerce.courses.models import Course
from ecommerce.extensions.analytics.utils import prepare_analytics_data
from ecommerce.extensions.partner.shortcuts import get_partner_for_site
from ecommerce.extensions.payment.helpers import get_processor_class
from ecommerce.settings import get_lms_url
logger = logging.getLogger(__name__)
......
"""JWT authentication scheme for use with DRF."""
import logging
from django.conf import settings
from django.contrib.auth import get_user_model
import requests
from rest_framework import exceptions
......@@ -8,8 +9,6 @@ from rest_framework.authentication import get_authorization_header, BaseAuthenti
from rest_framework.status import HTTP_200_OK
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from ecommerce.core.url_utils import get_oauth2_provider_url
logger = logging.getLogger(__name__)
User = get_user_model()
......@@ -90,7 +89,7 @@ class BearerAuthentication(BaseAuthentication):
"""
def authenticate(self, request):
provider_url = get_oauth2_provider_url()
provider_url = getattr(settings, 'OAUTH2_PROVIDER_URL', None)
if not provider_url:
return None
......
......@@ -2,11 +2,12 @@
import logging
import requests
from django.conf import settings
from oscar.core.loading import get_model, get_class
from ecommerce.core.url_utils import get_lms_url
from ecommerce.extensions.api import exceptions
from ecommerce.extensions.api.constants import APIConstants as AC
from ecommerce.settings import get_lms_url
NoShippingRequired = get_class('shipping.methods', 'NoShippingRequired')
OrderTotalCalculator = get_class('checkout.calculators', 'OrderTotalCalculator')
......@@ -58,9 +59,8 @@ def get_lms_footer():
str: HTML representation of the footer.
"""
try:
footer_api_url = get_lms_url('api/branding/v1/footer')
response = requests.get(
footer_api_url,
get_lms_url('api/branding/v1/footer'),
data={'language': 'en'}
)
if response.status_code == 200:
......@@ -68,11 +68,11 @@ def get_lms_footer():
else:
logger.error(
'Failed retrieve provider information for %s provider. Provider API returned status code %d. Error: %s',
footer_api_url, response.status_code, response.text)
settings.LMS_URL_ROOT, response.status_code, response.text)
return None
except requests.exceptions.ConnectionError:
logger.exception('Connection error occurred during getting data for %s provider', get_lms_url())
logger.exception('Connection error occurred during getting data for %s provider', settings.LMS_URL_ROOT)
return None
except requests.Timeout:
logger.exception('Failed to retrieve data for %s provider, connection timeout', get_lms_url())
logger.exception('Failed to retrieve data for %s provider, connection timeout', settings.LMS_URL_ROOT)
return None
......@@ -3,6 +3,7 @@ from decimal import Decimal
import logging
from dateutil.parser import parse
from django.conf import settings
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from oscar.core.loading import get_model, get_class
......@@ -11,7 +12,6 @@ from rest_framework.reverse import reverse
import waffle
from ecommerce.core.constants import ISO_8601_FORMAT, COURSE_ID_REGEX
from ecommerce.core.url_utils import get_ecommerce_url
from ecommerce.courses.models import Course
logger = logging.getLogger(__name__)
......@@ -372,8 +372,8 @@ class VoucherSerializer(serializers.ModelSerializer):
return (obj.offers.first().benefit.type, obj.offers.first().benefit.value)
def get_redeem_url(self, obj):
url = get_ecommerce_url('/coupons/offer/')
return '{url}?code={code}'.format(url=url, code=obj.code)
domain = settings.ECOMMERCE_URL_ROOT
return "{}/coupons/offer/?code={}".format(domain, obj.code)
class Meta(object):
model = Voucher
......
......@@ -16,11 +16,12 @@ from rest_framework.exceptions import AuthenticationFailed
from rest_framework.views import APIView
from rest_framework_jwt import utils
from ecommerce.core.url_utils import get_oauth2_provider_url
from ecommerce.extensions.api.authentication import BearerAuthentication, JwtAuthentication
from ecommerce.tests.mixins import JwtMixin
from ecommerce.tests.testcases import TestCase
OAUTH2_PROVIDER_URL = 'https://example.com/oauth2'
User = get_user_model()
......@@ -40,12 +41,13 @@ class AccessTokenMixin(object):
DEFAULT_TOKEN = 'abc123'
def _mock_access_token_response(self, status=200, token=DEFAULT_TOKEN, username='fake-user'):
httpretty.register_uri(httpretty.GET, '{}/access_token/{}/'.format(get_oauth2_provider_url(), token),
httpretty.register_uri(httpretty.GET, '{}/access_token/{}/'.format(OAUTH2_PROVIDER_URL, token),
body=json.dumps({'username': username, 'scope': 'read', 'expires_in': 60}),
content_type="application/json",
status=status)
@override_settings(OAUTH2_PROVIDER_URL=OAUTH2_PROVIDER_URL)
class BearerAuthenticationTests(AccessTokenMixin, TestCase):
def setUp(self):
super(BearerAuthenticationTests, self).setUp()
......@@ -61,9 +63,15 @@ class BearerAuthenticationTests(AccessTokenMixin, TestCase):
""" The method should return the string Bearer. """
self.assertEqual(self.auth.authenticate_header(self._create_request()), 'Bearer')
@mock.patch('ecommerce.core.url_utils.get_lms_url', mock.Mock(return_value=None))
@override_settings(OAUTH2_PROVIDER_URL=None)
def test_authenticate_no_provider(self):
""" If the we cannot get the LMS URL, the method returns None. """
""" If the setting OAUTH2_PROVIDER_URL is not set, the method returns None. """
# Empty value
self.assertIsNone(self.auth.authenticate(self._create_request()))
# Missing value
del settings.OAUTH2_PROVIDER_URL
self.assertIsNone(self.auth.authenticate(self._create_request()))
def test_authenticate_invalid_token(self):
......
import json
from django.conf import settings
import httpretty
import mock
import requests
from testfixtures import LogCapture
from ecommerce.core.url_utils import get_lms_url
from ecommerce.extensions.api.data import get_lms_footer
from ecommerce.tests.testcases import TestCase
from ecommerce.settings import get_lms_url
LOGGER_NAME = 'ecommerce.extensions.api.data'
......@@ -16,9 +17,10 @@ LOGGER_NAME = 'ecommerce.extensions.api.data'
class DataFunctionsTests(TestCase):
""" Tests for api data functions. """
footer_url = get_lms_url('api/branding/v1/footer')
def setUp(self):
super(DataFunctionsTests, self).setUp()
self.footer_url = get_lms_url('api/branding/v1/footer')
@httpretty.activate
def test_get_lms_footer_success(self):
......@@ -47,7 +49,7 @@ class DataFunctionsTests(TestCase):
(
LOGGER_NAME, 'ERROR',
u'Connection error occurred during getting data for {lms_url} provider'.format(
lms_url=get_lms_url()
lms_url=settings.LMS_URL_ROOT
)
)
)
......@@ -62,7 +64,7 @@ class DataFunctionsTests(TestCase):
(
LOGGER_NAME, 'ERROR',
u'Failed to retrieve data for {lms_url} provider, connection timeout'.format(
lms_url=get_lms_url()
lms_url=settings.LMS_URL_ROOT
)
)
)
......
......@@ -9,7 +9,7 @@ from django.test import override_settings
from oscar.core.loading import get_model
from oscar.test import factories
from ecommerce.extensions.api.tests.test_authentication import AccessTokenMixin
from ecommerce.extensions.api.tests.test_authentication import AccessTokenMixin, OAUTH2_PROVIDER_URL
from ecommerce.extensions.api.v2.tests.views import OrderDetailViewTestMixin
from ecommerce.extensions.fulfillment.signals import SHIPPING_EVENT_NAME
from ecommerce.extensions.fulfillment.status import ORDER
......@@ -42,6 +42,7 @@ class OrderListViewTests(AccessTokenMixin, ThrottlingMixin, TestCase):
self.assertEqual(content['results'], [])
@httpretty.activate
@override_settings(OAUTH2_PROVIDER_URL=OAUTH2_PROVIDER_URL)
def test_access_token(self):
""" Verifies that the view accepts OAuth2 access tokens for authentication."""
auth_header = 'Bearer {}'.format(self.DEFAULT_TOKEN)
......
......@@ -14,7 +14,6 @@ class PaymentProcessorListViewTests(TestCase):
""" Ensures correct behavior of the payment processors list view."""
def setUp(self):
super(PaymentProcessorListViewTests, self).setUp()
self.token = self.generate_jwt_token_header(self.create_user())
self.toggle_payment_processor('dummy', True)
self.toggle_payment_processor('another-dummy', True)
......
......@@ -2,6 +2,7 @@ import json
import ddt
from django.core.urlresolvers import reverse
from django.test import override_settings
import httpretty
import mock
from oscar.core.loading import get_model
......@@ -9,7 +10,7 @@ from oscar.test import factories
from rest_framework import status
from ecommerce.extensions.api.serializers import RefundSerializer
from ecommerce.extensions.api.tests.test_authentication import AccessTokenMixin
from ecommerce.extensions.api.tests.test_authentication import AccessTokenMixin, OAUTH2_PROVIDER_URL
from ecommerce.extensions.api.v2.tests.views import JSON_CONTENT_TYPE
from ecommerce.extensions.refund.tests.factories import RefundLineFactory, RefundFactory
from ecommerce.extensions.refund.tests.mixins import RefundTestMixin
......@@ -95,6 +96,7 @@ class RefundCreateViewTests(RefundTestMixin, AccessTokenMixin, JwtMixin, TestCas
self.assert_ok_response(response)
@httpretty.activate
@override_settings(OAUTH2_PROVIDER_URL=OAUTH2_PROVIDER_URL)
def test_oauth_authentication(self):
""" Client can authenticate with OAuth. """
self.client.logout()
......
......@@ -16,12 +16,12 @@ from slumber.exceptions import SlumberBaseException
from testfixtures import LogCapture
from ecommerce.core.tests import toggle_switch
from ecommerce.core.url_utils import get_lms_url
from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.extensions.offer.utils import format_benefit_value
from ecommerce.extensions.payment.tests.processors import DummyProcessor
from ecommerce.extensions.test.factories import prepare_voucher
from ecommerce.settings import get_lms_url
from ecommerce.tests.factories import StockRecordFactory
from ecommerce.tests.mixins import CouponMixin, LmsApiMockMixin
from ecommerce.tests.testcases import TestCase
......
......@@ -13,7 +13,6 @@ from oscar.apps.basket.views import * # pylint: disable=wildcard-import, unused
from edx_rest_api_client.client import EdxRestApiClient
from slumber.exceptions import SlumberBaseException
from ecommerce.core.url_utils import get_lms_url
from ecommerce.coupons.views import get_voucher_from_code
from ecommerce.extensions.analytics.utils import prepare_analytics_data
from ecommerce.extensions.api.data import get_lms_footer
......@@ -21,6 +20,7 @@ from ecommerce.extensions.basket.utils import get_certificate_type_display_value
from ecommerce.extensions.offer.utils import format_benefit_value
from ecommerce.extensions.payment.helpers import get_processor_class
from ecommerce.extensions.partner.shortcuts import get_partner_for_site
from ecommerce.settings import get_lms_url
Benefit = get_model('offer', 'Benefit')
logger = logging.getLogger(__name__)
......
......@@ -4,9 +4,9 @@ from optparse import make_option
from dateutil.parser import parse
from django.conf import settings
from django.contrib.sites.models import Site
from django.core.management import BaseCommand
from django.db import transaction
from oscar.core.loading import get_model
import requests
import waffle
......@@ -14,12 +14,13 @@ from ecommerce.courses.models import Course
logger = logging.getLogger(__name__)
Partner = get_model('partner', 'Partner')
class MigratedCourse(object):
def __init__(self, course_id, site_domain):
def __init__(self, course_id, partner_short_code):
self.course, _created = Course.objects.get_or_create(id=course_id)
self.site_configuration = Site.objects.get(domain=site_domain).siteconfiguration
self.partner = Partner.objects.get(short_code=partner_short_code)
def load_from_lms(self, access_token):
"""
......@@ -38,12 +39,17 @@ class MigratedCourse(object):
def _build_lms_url(self, path):
# We avoid using urljoin here because it URL-encodes the path, and some LMS APIs
# are not capable of decoding these values.
host = self.site_configuration.lms_url_root.strip('/')
host = settings.LMS_URL_ROOT.strip('/')
return '{host}/{path}'.format(host=host, path=path)
def _query_commerce_api(self, headers):
"""Get course name and verification deadline from the Commerce API."""
url = '{}/courses/{}/'.format(self._build_lms_url('api/commerce/v1'), self.course.id)
if not settings.COMMERCE_API_URL:
message = 'Aborting migration. COMMERCE_API_URL is not set.'
logger.error(message)
raise Exception(message)
url = '{}/courses/{}/'.format(settings.COMMERCE_API_URL.rstrip('/'), self.course.id)
timeout = settings.COMMERCE_API_TIMEOUT
response = requests.get(url, headers=headers, timeout=timeout)
......@@ -145,7 +151,7 @@ class MigratedCourse(object):
expires = mode.get('expiration_datetime')
expires = parse(expires) if expires else None
self.course.create_or_update_seat(
certificate_type, id_verification_required, price, self.site_configuration.partner,
certificate_type, id_verification_required, price, self.partner,
expires=expires, remove_stale_modes=False
)
......@@ -165,30 +171,30 @@ class Command(BaseCommand):
default=False,
help='Save the migrated data to the database. If this is not set, '
'migrated data will NOT be saved to the database.'),
make_option('--site',
make_option('--partner',
action='store',
dest='site_domain',
dest='partner_short_code',
default=None,
help='Domain for the ecommerce site providing the course.'),
help='Short code for the partner providing the course.'),
)
def handle(self, *args, **options):
course_ids = args
access_token = options.get('access_token')
site_domain = options.get('site_domain')
partner_short_code = options.get('partner_short_code')
if not access_token:
logger.error('Courses cannot be migrated if no access token is supplied.')
return
if not site_domain:
logger.error('Courses cannot be migrated without providing a site domain.')
if not partner_short_code:
logger.error('Courses cannot be migrated without providing a partner short code.')
return
for course_id in course_ids:
course_id = unicode(course_id)
try:
with transaction.atomic():
migrated_course = MigratedCourse(course_id, site_domain)
migrated_course = MigratedCourse(course_id, partner_short_code)
migrated_course.load_from_lms(access_token)
course = migrated_course.course
......
......@@ -6,6 +6,7 @@ import json
import logging
from urlparse import urljoin, urlparse
from django.conf import settings
from django.core.management import call_command
from django.test import override_settings
import httpretty
......@@ -40,6 +41,9 @@ StockRecord = get_model('partner', 'StockRecord')
class CourseMigrationTestMixin(CourseCatalogTestMixin):
course_id = 'aaa/bbb/ccc'
course_name = 'A Tést Côurse'
commerce_api_url = '{}/courses/{}/'.format(settings.COMMERCE_API_URL.rstrip('/'), course_id)
course_structure_url = urljoin(settings.LMS_URL_ROOT, 'api/course_structure/v0/courses/{}/'.format(course_id))
enrollment_api_url = urljoin(settings.LMS_URL_ROOT, 'api/enrollment/v1/course/{}'.format(course_id))
prices = {
'honor': 0,
......@@ -50,23 +54,6 @@ class CourseMigrationTestMixin(CourseCatalogTestMixin):
'credit': 0,
}
@property
def lms_url(self):
return self.site.siteconfiguration.lms_url_root
@property
def commerce_api_url(self):
lms_commerce_api_url = urljoin(self.lms_url, '/api/commerce/v1')
return '{}/courses/{}/'.format(lms_commerce_api_url, self.course_id)
@property
def course_structure_url(self):
return urljoin(self.lms_url, 'api/course_structure/v0/courses/{}/'.format(self.course_id))
@property
def enrollment_api_url(self):
return urljoin(self.lms_url, 'api/enrollment/v1/course/{}'.format(self.course_id))
def _mock_lms_apis(self):
self.assertTrue(httpretty.is_enabled(), 'httpretty must be enabled to mock LMS API calls.')
......@@ -151,7 +138,7 @@ class MigratedCourseTests(CourseMigrationTestMixin, TestCase):
def _migrate_course_from_lms(self):
""" Create a new MigratedCourse and simulate the loading of data from LMS. """
self._mock_lms_apis()
migrated_course = MigratedCourse(self.course_id, self.site.domain)
migrated_course = MigratedCourse(self.course_id, self.partner.short_code)
migrated_course.load_from_lms(ACCESS_TOKEN)
return migrated_course
......@@ -196,7 +183,7 @@ class MigratedCourseTests(CourseMigrationTestMixin, TestCase):
# Try migrating the course, which should fail.
try:
migrated_course = MigratedCourse(self.course_id, self.site.domain)
migrated_course = MigratedCourse(self.course_id, self.partner.short_code)
migrated_course.load_from_lms(ACCESS_TOKEN)
except Exception as ex: # pylint: disable=broad-except
self.assertEqual(ex.message, 'Aborting migration. No name is available for {}.'.format(self.course_id))
......@@ -222,7 +209,7 @@ class MigratedCourseTests(CourseMigrationTestMixin, TestCase):
content_type=JSON
)
migrated_course = MigratedCourse(self.course_id, self.site.domain)
migrated_course = MigratedCourse(self.course_id, self.partner.short_code)
migrated_course.load_from_lms(ACCESS_TOKEN)
course = migrated_course.course
......@@ -256,7 +243,7 @@ class MigratedCourseTests(CourseMigrationTestMixin, TestCase):
}
httpretty.register_uri(httpretty.GET, self.commerce_api_url, body=json.dumps(body), content_type=JSON)
migrated_course = MigratedCourse(self.course_id, self.site.domain)
migrated_course = MigratedCourse(self.course_id, self.partner.short_code)
migrated_course.load_from_lms(ACCESS_TOKEN)
course = migrated_course.course
......@@ -284,7 +271,7 @@ class CommandTests(CourseMigrationTestMixin, TestCase):
with mock.patch.object(LMSPublisher, 'publish') as mock_publish:
mock_publish.return_value = True
call_command(
'migrate_course', self.course_id, access_token=ACCESS_TOKEN, site_domain=self.site.domain
'migrate_course', self.course_id, access_token=ACCESS_TOKEN, partner_short_code=self.partner.short_code
)
# Verify that the migrated course was not published back to the LMS
......@@ -309,8 +296,8 @@ class CommandTests(CourseMigrationTestMixin, TestCase):
self.course_id,
access_token=ACCESS_TOKEN,
commit=True,
# `site_domain` is the option destination variable
site_domain=self.site.domain
# `partner_short_code` is the option destination variable
partner_short_code=self.partner.short_code
)
# Verify that the migrated course was published back to the LMS
......@@ -319,8 +306,8 @@ class CommandTests(CourseMigrationTestMixin, TestCase):
self.assert_course_migrated()
@httpretty.activate
def test_handle_with_no_site(self):
""" Verify the management command does not run if no site domain is provided. """
def test_handle_with_no_partner(self):
""" Verify the management command does not run if no partner short code is provided. """
self._mock_lms_apis()
with mock.patch.object(LMSPublisher, 'publish') as mock_publish:
......@@ -332,7 +319,7 @@ class CommandTests(CourseMigrationTestMixin, TestCase):
commit=True
)
l.check((LOGGER_NAME, 'ERROR', 'Courses cannot be migrated without providing a site domain.'))
l.check((LOGGER_NAME, 'ERROR', 'Courses cannot be migrated without providing a partner short code.'))
# Verify that the migrated course was published back to the LMS
self.assertFalse(mock_publish.called)
......@@ -347,7 +334,7 @@ class CommandTests(CourseMigrationTestMixin, TestCase):
self.course_id,
access_token=ACCESS_TOKEN,
commit=False,
site=self.site.domain
partner=self.partner.short_code
)
# Verify that the migrated course was published back to the LMS
......@@ -365,7 +352,7 @@ class CommandTests(CourseMigrationTestMixin, TestCase):
self.course_id,
access_token=ACCESS_TOKEN,
commit=True,
site=self.site.domain
partner=self.partner.short_code
)
# Verify that the migrated course was published back to the LMS
......
......@@ -6,11 +6,11 @@ from django.dispatch import receiver
from oscar.core.loading import get_class
import waffle
from ecommerce.core.url_utils import get_lms_url
from ecommerce.courses.utils import mode_for_seat
from ecommerce.extensions.analytics.utils import is_segment_configured, parse_tracking_context, silence_exceptions
from ecommerce.extensions.checkout.utils import get_provider_data
from ecommerce.notifications.notifications import send_notification
from ecommerce.settings import get_lms_url
logger = logging.getLogger(__name__)
......
......@@ -5,10 +5,10 @@ from oscar.test import factories
from oscar.test.newfactories import BasketFactory, UserFactory
from ecommerce.core.tests import toggle_switch
from ecommerce.core.url_utils import get_lms_url
from ecommerce.courses.models import Course
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.extensions.checkout.signals import send_course_purchase_email
from ecommerce.settings import get_lms_url
from ecommerce.tests.testcases import TestCase
......
......@@ -4,8 +4,8 @@ import mock
import requests
from requests import ConnectionError, Timeout
from ecommerce.core.url_utils import get_lms_url
from ecommerce.extensions.checkout.utils import get_provider_data
from ecommerce.settings import get_lms_url
from ecommerce.tests.testcases import TestCase
......
......@@ -6,9 +6,9 @@ import httpretty
from oscar.core.loading import get_model
from oscar.test import newfactories as factories
from ecommerce.core.url_utils import get_lms_url
from ecommerce.extensions.checkout.exceptions import BasketNotFreeError
from ecommerce.tests.testcases import TestCase
from ecommerce.settings import get_lms_url
Order = get_model('order', 'Order')
......
......@@ -3,7 +3,7 @@ import requests
from django.conf import settings
from ecommerce.core.url_utils import get_lms_url
from ecommerce.settings import get_lms_url
logger = logging.getLogger(__name__)
......
......@@ -12,9 +12,9 @@ from django.views.generic import RedirectView
from oscar.apps.checkout.views import * # pylint: disable=wildcard-import, unused-wildcard-import
from oscar.core.loading import get_class, get_model
from ecommerce.core.url_utils import get_lms_url
from ecommerce.extensions.checkout.exceptions import BasketNotFreeError
from ecommerce.extensions.checkout.mixins import EdxOrderPlacementMixin
from ecommerce.settings import get_lms_url
Applicator = get_class('offer.utils', 'Applicator')
Basket = get_model('basket', 'Basket')
......
import json
from django.conf import settings
from django.contrib.messages import constants as MSG
from django.core.urlresolvers import reverse
import httpretty
......@@ -8,7 +9,6 @@ from requests import Timeout
from testfixtures import LogCapture
from ecommerce.core.tests import toggle_switch
from ecommerce.core.url_utils import get_lms_enrollment_api_url
from ecommerce.extensions.dashboard.tests import DashboardViewTestMixin
from ecommerce.extensions.dashboard.users.views import UserDetailView
from ecommerce.tests.testcases import TestCase
......@@ -26,7 +26,7 @@ class UserDetailViewTests(DashboardViewTestMixin, TestCase):
def mock_enrollment_api(self, status=200):
self.assertTrue(httpretty.is_enabled())
httpretty.register_uri(httpretty.GET, get_lms_enrollment_api_url(), status=status,
httpretty.register_uri(httpretty.GET, settings.ENROLLMENT_API_URL, status=status,
body=json.dumps(self.data),
content_type='application/json')
......
......@@ -7,9 +7,6 @@ from oscar.apps.dashboard.users.views import UserDetailView as CoreUserDetailVie
import requests
import waffle
from ecommerce.core.url_utils import get_lms_enrollment_api_url
logger = logging.getLogger(__name__)
......@@ -26,7 +23,7 @@ class UserDetailView(CoreUserDetailView):
"""Retrieve the enrollments for the User being viewed."""
username = self.object.username
try:
url = '{}?user={}'.format(get_lms_enrollment_api_url(), username)
url = '{}?user={}'.format(settings.ENROLLMENT_API_URL, username)
timeout = settings.ENROLLMENT_FULFILLMENT_TIMEOUT
headers = {
......
......@@ -11,9 +11,8 @@ from django.conf import settings
from rest_framework import status
import requests
from requests.exceptions import ConnectionError, Timeout
from ecommerce.core.url_utils import get_lms_enrollment_api_url
from ecommerce.courses.utils import mode_for_seat
from ecommerce.extensions.analytics.utils import audit_log, parse_tracking_context
from ecommerce.extensions.fulfillment.status import LINE
......@@ -95,7 +94,7 @@ class EnrollmentFulfillmentModule(BaseFulfillmentModule):
"""
def _post_to_enrollment_api(self, data, user):
enrollment_api_url = get_lms_enrollment_api_url()
enrollment_api_url = settings.ENROLLMENT_API_URL
timeout = settings.ENROLLMENT_FULFILLMENT_TIMEOUT
headers = {
'Content-Type': 'application/json',
......@@ -149,10 +148,11 @@ class EnrollmentFulfillmentModule(BaseFulfillmentModule):
"""
logger.info("Attempting to fulfill 'Seat' product types for order [%s]", order.number)
enrollment_api_url = getattr(settings, 'ENROLLMENT_API_URL', None)
api_key = getattr(settings, 'EDX_API_KEY', None)
if not api_key:
if not (enrollment_api_url and api_key):
logger.error(
'EDX_API_KEY must be set to use the EnrollmentFulfillmentModule'
'ENROLLMENT_API_URL and EDX_API_KEY must be set to use the EnrollmentFulfillmentModule'
)
for line in lines:
line.set_status(LINE.FULFILLMENT_CONFIGURATION_ERROR)
......
......@@ -5,6 +5,7 @@ import ddt
import httpretty
import mock
from django.conf import settings
from django.test import override_settings
from oscar.core.loading import get_class, get_model
from oscar.test import factories
......@@ -12,7 +13,6 @@ from oscar.test.newfactories import UserFactory, BasketFactory
from requests.exceptions import ConnectionError, Timeout
from testfixtures import LogCapture
from ecommerce.core.url_utils import get_lms_enrollment_api_url
from ecommerce.courses.models import Course
from ecommerce.courses.utils import mode_for_seat
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
......@@ -83,7 +83,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
@mock.patch('ecommerce.extensions.fulfillment.modules.parse_tracking_context')
def test_enrollment_module_fulfill(self, parse_tracking_context):
"""Happy path test to ensure we can properly fulfill enrollments."""
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON)
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=200, body='{}', content_type=JSON)
parse_tracking_context.return_value = ('user_123', 'GA-123456789', '11.22.33.44')
# Attempt to enroll.
with LogCapture(LOGGER_NAME) as l:
......@@ -137,7 +137,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
self.assertDictContainsSubset(expected_headers, actual_headers)
self.assertEqual(expected_body, actual_body)
@override_settings(EDX_API_KEY=None)
@override_settings(ENROLLMENT_API_URL='')
def test_enrollment_module_not_configured(self):
"""Test that lines receive a configuration error status if fulfillment configuration is invalid."""
EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all()))
......@@ -167,7 +167,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
"""Test that lines receive a server-side error status if a server-side error occurs during fulfillment."""
# NOTE: We are testing for cases where the response does and does NOT have data. The module should be able
# to handle both cases.
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=500, body=body, content_type=JSON)
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=500, body=body, content_type=JSON)
EnrollmentFulfillmentModule().fulfill_product(self.order, list(self.order.lines.all()))
self.assertEqual(LINE.FULFILLMENT_SERVER_ERROR, self.order.lines.all()[0].status)
......@@ -175,7 +175,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
@mock.patch('ecommerce.extensions.fulfillment.modules.parse_tracking_context')
def test_revoke_product(self, parse_tracking_context):
""" The method should call the Enrollment API to un-enroll the student, and return True. """
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON)
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=200, body='{}', content_type=JSON)
parse_tracking_context.return_value = ('user_123', 'GA-123456789', '11.22.33.44')
line = self.order.lines.first()
......@@ -227,7 +227,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
"""
message = 'Enrollment mode mismatch: active mode=x, requested mode=y. Won\'t deactivate.'
body = '{{"message": "{}"}}'.format(message)
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=400, body=body, content_type=JSON)
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=400, body=body, content_type=JSON)
line = self.order.lines.first()
logger_name = 'ecommerce.extensions.fulfillment.modules'
......@@ -243,7 +243,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
""" If the Enrollment API responds with a non-200 status, the method should log an error and return False. """
message = 'Meh.'
body = '{{"message": "{}"}}'.format(message)
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=500, body=body, content_type=JSON)
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=500, body=body, content_type=JSON)
line = self.order.lines.first()
logger_name = 'ecommerce.extensions.fulfillment.modules'
......@@ -263,7 +263,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
def request_callback(_method, _uri, _headers):
raise Timeout
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), body=request_callback)
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, body=request_callback)
line = self.order.lines.first()
logger_name = 'ecommerce.extensions.fulfillment.modules'
......@@ -279,7 +279,7 @@ class EnrollmentFulfillmentModuleTests(CourseCatalogTestMixin, FulfillmentTestMi
"""Happy path test to ensure we can properly fulfill enrollments."""
# Create the credit certificate type and order for the credit certificate type.
self.create_seat_and_order(certificate_type='credit', provider='MIT')
httpretty.register_uri(httpretty.POST, get_lms_enrollment_api_url(), status=200, body='{}', content_type=JSON)
httpretty.register_uri(httpretty.POST, settings.ENROLLMENT_API_URL, status=200, body='{}', content_type=JSON)
# Attempt to enroll.
with LogCapture(LOGGER_NAME) as l:
......
"""Test Order Utility classes """
import mock
from django.test.client import RequestFactory
from django.test import override_settings
from oscar.core.loading import get_class
from oscar.test.factories import create_basket as oscar_create_basket
from oscar.test.newfactories import BasketFactory
......@@ -35,11 +33,7 @@ class OrderNumberGeneratorTests(TestCase):
partner = site_configuration.partner
basket = BasketFactory(site=None)
request = RequestFactory().get('')
request.session = None
request.site = site
with mock.patch('ecommerce.extensions.order.utils.get_current_request', mock.Mock(return_value=request)):
with override_settings(SITE_ID=site.id):
self.assert_order_number_matches_basket(basket, partner)
def test_order_number_from_basket_id(self):
......
......@@ -2,14 +2,11 @@
from __future__ import unicode_literals
import logging
from django.contrib.sites.models import Site
from oscar.apps.order.utils import OrderCreator as OscarOrderCreator
from oscar.core.loading import get_model
from threadlocals.threadlocals import get_current_request
logger = logging.getLogger(__name__)
Order = get_model('order', 'Order')
class OrderNumberGenerator(object):
OFFSET = 100000
......@@ -26,7 +23,7 @@ class OrderNumberGenerator(object):
"""
site = basket.site
if not site:
site = get_current_request().site
site = Site.objects.get_current()
logger.warning('Basket [%d] is not associated with a Site. Defaulting to Site [%d].', basket.id, site.id)
partner = site.siteconfiguration.partner
......@@ -70,33 +67,11 @@ class OrderCreator(OscarOrderCreator):
site is used. The site value can be overridden by setting the `site` kwarg.
"""
# If a site was not passed in with extra_order_fields,
# use the basket's site if it has one, else get the site
# from the current request.
site = basket.site
if not site:
site = get_current_request().site
order_data = {'basket': basket,
'number': order_number,
'site': site,
'currency': total.currency,
'total_incl_tax': total.incl_tax,
'total_excl_tax': total.excl_tax,
'shipping_incl_tax': shipping_charge.incl_tax,
'shipping_excl_tax': shipping_charge.excl_tax,
'shipping_method': shipping_method.name,
'shipping_code': shipping_method.code}
if shipping_address:
order_data['shipping_address'] = shipping_address
if billing_address:
order_data['billing_address'] = billing_address
if user and user.is_authenticated():
order_data['user_id'] = user.id
if status:
order_data['status'] = status
if extra_order_fields:
order_data.update(extra_order_fields)
order = Order(**order_data)
order.save()
return order
# Pull the order's site from the basket, if the basket has a site and
# a site is not already being explicitly set.
if basket.site and 'site' not in extra_order_fields:
extra_order_fields['site'] = basket.site
return super(OrderCreator, self).create_order_model(
user, basket, shipping_address, shipping_method, shipping_charge, billing_address, total, order_number,
status, **extra_order_fields)
......@@ -13,7 +13,6 @@ from suds.client import Client
from suds.sudsobject import asdict
from suds.wsse import Security, UsernameToken
from ecommerce.core.url_utils import get_lms_url
from ecommerce.core.constants import ISO_8601_FORMAT
from ecommerce.extensions.order.constants import PaymentEventTypeName
from ecommerce.extensions.payment.constants import CYBERSOURCE_CARD_TYPE_MAP
......@@ -59,16 +58,10 @@ class Cybersource(BasePaymentProcessor):
self.access_key = configuration['access_key']
self.secret_key = configuration['secret_key']
self.payment_page_url = configuration['payment_page_url']
self.receipt_page_url = configuration['receipt_page_url']
self.cancel_page_url = configuration['cancel_page_url']
self.language_code = settings.LANGUAGE_CODE
@property
def receipt_page_url(self):
return get_lms_url(self.configuration['receipt_path'])
@property
def cancel_page_url(self):
return get_lms_url(self.configuration['cancel_path'])
def get_transaction_parameters(self, basket, request=None):
"""
Generate a dictionary of signed parameters CyberSource requires to complete a transaction.
......
......@@ -3,13 +3,13 @@ from decimal import Decimal
import logging
from urlparse import urljoin
from django.conf import settings
from django.core.urlresolvers import reverse
from oscar.apps.payment.exceptions import GatewayError
from oscar.core.loading import get_model
import paypalrestsdk
import waffle
from ecommerce.core.url_utils import get_ecommerce_url, get_lms_url
from ecommerce.extensions.order.constants import PaymentEventTypeName
from ecommerce.extensions.payment.processors import BasePaymentProcessor
from ecommerce.extensions.payment.models import PaypalWebProfile
......@@ -42,21 +42,17 @@ class Paypal(BasePaymentProcessor):
Raises:
KeyError: If a required setting is not configured for this payment processor
AttributeError: If ECOMMERCE_URL_ROOT setting is not set.
"""
# Number of times payment execution is retried after failure.
self.retry_attempts = self.configuration.get('retry_attempts', 1)
@property
def receipt_url(self):
return get_lms_url(self.configuration['receipt_path'])
configuration = self.configuration
self.receipt_url = configuration['receipt_url']
self.cancel_url = configuration['cancel_url']
self.error_url = configuration['error_url']
@property
def cancel_url(self):
return get_lms_url(self.configuration['cancel_path'])
# Number of times payment execution is retried after failure.
self.retry_attempts = configuration.get('retry_attempts', 1)
@property
def error_url(self):
return get_lms_url(self.configuration['error_path'])
self.ecommerce_url_root = settings.ECOMMERCE_URL_ROOT
def get_transaction_parameters(self, basket, request=None):
"""
......@@ -76,7 +72,7 @@ class Paypal(BasePaymentProcessor):
GatewayError: Indicates a general error or unexpected behavior on the part of PayPal which prevented
a payment from being created.
"""
return_url = urljoin(get_ecommerce_url(), reverse('paypal_execute'))
return_url = urljoin(self.ecommerce_url_root, reverse('paypal_execute'))
data = {
'intent': 'sale',
'redirect_urls': {
......
......@@ -21,7 +21,6 @@ from paypalrestsdk import Payment, Sale
from paypalrestsdk.resource import Resource
from testfixtures import LogCapture
from ecommerce.core.url_utils import get_ecommerce_url
from ecommerce.core.constants import ISO_8601_FORMAT
from ecommerce.core.tests import toggle_switch
from ecommerce.core.tests.patched_httpretty import httpretty
......@@ -394,7 +393,7 @@ class PaypalTests(PaypalMixin, PaymentProcessorTestCaseMixin, TestCase):
self.assert_processor_response_recorded(self.processor.NAME, self.PAYMENT_ID, response, basket=self.basket)
last_request_body = json.loads(httpretty.last_request().body)
expected = urljoin(get_ecommerce_url(), reverse('paypal_execute'))
expected = urljoin(settings.ECOMMERCE_URL_ROOT, reverse('paypal_execute'))
self.assertEqual(last_request_body['redirect_urls']['return_url'], expected)
@httpretty.activate
......
......@@ -7,7 +7,6 @@ from oscar.core.loading import get_model, get_class
from oscar.test.newfactories import UserFactory
from testfixtures import LogCapture
from ecommerce.core.url_utils import get_lms_enrollment_api_url
from ecommerce.extensions.refund import models
from ecommerce.extensions.refund.exceptions import InvalidStatus
from ecommerce.extensions.refund.status import REFUND, REFUND_LINE
......@@ -130,7 +129,7 @@ class RefundTests(RefundTestMixin, StatusTestsMixin, TestCase):
"""
httpretty.register_uri(
httpretty.POST,
get_lms_enrollment_api_url(),
settings.ENROLLMENT_API_URL,
status=200,
body='{}',
content_type='application/json'
......
import datetime
from django.conf import settings
from django.db import IntegrityError
from django.test import override_settings
from oscar.core.loading import get_model
from oscar.templatetags.currency_filters import currency
from oscar.test.factories import * # pylint:disable=wildcard-import,unused-wildcard-import
from ecommerce.core.url_utils import get_ecommerce_url
from ecommerce.courses.tests.factories import CourseFactory
from ecommerce.extensions.catalogue.tests.mixins import CourseCatalogTestMixin
from ecommerce.extensions.voucher.utils import create_vouchers, generate_coupon_report, get_voucher_discount_info
......@@ -229,7 +232,7 @@ class UtilTests(CouponMixin, CourseCatalogTestMixin, TestCase):
enrollment_code_row = rows[-2]
self.assertEqual(enrollment_code_row['Name'], 'Discount code')
self.assertEqual(enrollment_code_row['Code'], VOUCHER_CODE)
self.assertEqual(enrollment_code_row['URL'], get_ecommerce_url() + REDEMPTION_URL.format(VOUCHER_CODE))
self.assertEqual(enrollment_code_row['URL'], settings.ECOMMERCE_URL_ROOT + REDEMPTION_URL.format(VOUCHER_CODE))
self.assertEqual(enrollment_code_row['CourseID'], self.course.id)
self.assertEqual(enrollment_code_row['Price'], currency(100.00))
self.assertEqual(enrollment_code_row['Invoiced Amount'], currency(100.00))
......@@ -245,7 +248,7 @@ class UtilTests(CouponMixin, CourseCatalogTestMixin, TestCase):
self.assertEqual(enrollment_code_row['Discount'], '$100.00')
self.assertEqual(
enrollment_code_row['URL'],
get_ecommerce_url() + REDEMPTION_URL.format(enrollment_code_row['Code'])
settings.ECOMMERCE_URL_ROOT + REDEMPTION_URL.format(enrollment_code_row['Code'])
)
def test_get_voucher_discount_info(self):
......
......@@ -11,8 +11,6 @@ from django.utils.translation import ugettext_lazy as _
from oscar.core.loading import get_model
from oscar.templatetags.currency_filters import currency
from ecommerce.core.url_utils import get_ecommerce_url
logger = logging.getLogger(__name__)
Benefit = get_model('offer', 'Benefit')
......@@ -79,7 +77,7 @@ def generate_coupon_report(coupon_vouchers):
discount = _("{percentage} %").format(percentage=benefit_value)
else:
discount = currency(benefit_value)
URL = '{url}?code={code}'.format(url=get_ecommerce_url('/coupons/redeem/'), code=voucher.code)
URL = '{}/coupons/redeem/?code={}'.format(settings.ECOMMERCE_URL_ROOT, voucher.code)
author = history.history_user.full_name
rows.append({
......
from urlparse import urljoin
def get_lms_url(path):
from django.conf import settings
return urljoin(settings.LMS_URL_ROOT, path)
......@@ -102,27 +102,23 @@ PAYMENT_PROCESSORS = (
'ecommerce.extensions.payment.processors.paypal.Paypal',
)
PAYMENT_PROCESSOR_RECEIPT_PATH = '/commerce/checkout/receipt/'
PAYMENT_PROCESSOR_CANCEL_PATH = '/commerce/checkout/cancel/'
PAYMENT_PROCESSOR_ERROR_PATH = '/commerce/checkout/error/'
PAYMENT_PROCESSOR_CONFIG = {
'cybersource': {
'profile_id': None,
'access_key': None,
'secret_key': None,
'payment_page_url': None,
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'receipt_page_url': None,
'cancel_page_url': None,
},
'paypal': {
# 'mode' can be either 'sandbox' or 'live'
'mode': None,
'client_id': None,
'client_secret': None,
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'error_path': PAYMENT_PROCESSOR_ERROR_PATH,
'receipt_url': None,
'cancel_url': None,
'error_url': None,
},
}
......
......@@ -197,7 +197,6 @@ MIDDLEWARE_CLASSES = (
'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware',
'social.apps.django_app.middleware.SocialAuthExceptionMiddleware',
'simple_history.middleware.HistoryRequestMiddleware',
'threadlocals.middleware.ThreadLocalMiddleware',
)
# END MIDDLEWARE CONFIGURATION
......@@ -206,8 +205,24 @@ MIDDLEWARE_CLASSES = (
# See: https://docs.djangoproject.com/en/dev/ref/settings/#root-urlconf
ROOT_URLCONF = '{}.urls'.format(SITE_NAME)
# Absolute URL used to construct URLs pointing back to the ecommerce service.
ECOMMERCE_URL_ROOT = None
# Absolute URL used to construct LMS URLs.
LMS_URL_ROOT = None
# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = None
# The location of the LMS student dashboard
LMS_DASHBOARD_URL = None
# URL to which enrollment requests should be made
ENROLLMENT_API_URL = None
# Commerce API settings used for publishing information to LMS.
COMMERCE_API_TIMEOUT = 7
COMMERCE_API_URL = None
# Cache course info from course API.
COURSES_API_CACHE_TIMEOUT = 3600 # Value is in seconds
......@@ -215,6 +230,9 @@ COURSES_API_CACHE_TIMEOUT = 3600 # Value is in seconds
# PROVIDER DATA PROCESSING
PROVIDER_DATA_PROCESSING_TIMEOUT = 15 # Value is in seconds.
CREDIT_PROVIDER_CACHE_TIMEOUT = 600
# OAuth2 provider URL used for OAuth2 transactions (e.g. validating access tokens)
OAUTH2_PROVIDER_URL = None
# END URL CONFIGURATION
......@@ -324,8 +342,6 @@ INSTALLED_APPS += ['social.apps.django_app.default']
AUTHENTICATION_BACKENDS = ('auth_backends.backends.EdXOpenIdConnect',) + AUTHENTICATION_BACKENDS
SOCIAL_AUTH_STRATEGY = 'ecommerce.social_auth.strategies.CurrentSiteDjangoStrategy'
# Set to true if using SSL and running behind a proxy
SOCIAL_AUTH_REDIRECT_IS_HTTPS = False
......
"""Devstack settings"""
from os import environ
from django.conf import settings
import yaml
from ecommerce.settings import get_lms_url
from ecommerce.settings.base import *
from ecommerce.settings.logger import get_logger_config
......@@ -29,16 +31,16 @@ PAYMENT_PROCESSOR_CONFIG = {
'access_key': 'fake-access-key',
'secret_key': 'fake-secret-key',
'payment_page_url': 'https://testsecureacceptance.cybersource.com/pay',
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'receipt_page_url': get_lms_url(settings.RECEIPT_PAGE_PATH),
'cancel_page_url': get_lms_url('/commerce/checkout/cancel/'),
},
'paypal': {
'mode': 'sandbox',
'client_id': 'fake-client-id',
'client_secret': 'fake-client-secret',
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'error_path': PAYMENT_PROCESSOR_ERROR_PATH,
'receipt_url': get_lms_url(settings.RECEIPT_PAGE_PATH),
'cancel_url': get_lms_url('/commerce/checkout/cancel/'),
'error_url': get_lms_url('/commerce/checkout/error/'),
},
}
# END PAYMENT PROCESSING
......
"""Development settings and globals."""
from __future__ import absolute_import
import os
from os.path import join, normpath
from django.conf import settings
from ecommerce.settings import get_lms_url
from ecommerce.settings.base import *
from ecommerce.settings.logger import get_logger_config
......@@ -60,11 +66,34 @@ INTERNAL_IPS = ('127.0.0.1',)
# END TOOLBAR CONFIGURATION
# URL CONFIGURATION
ECOMMERCE_URL_ROOT = 'http://localhost:8002'
LMS_URL_ROOT = 'http://127.0.0.1:8000'
# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = get_lms_url('/heartbeat')
# The location of the LMS student dashboard
LMS_DASHBOARD_URL = get_lms_url('/dashboard')
OAUTH2_PROVIDER_URL = get_lms_url('/oauth2')
COMMERCE_API_URL = get_lms_url('/api/commerce/v1/')
# END URL CONFIGURATION
# AUTHENTICATION
# Set these to the correct values for your OAuth2/OpenID Connect provider (e.g., devstack)
SOCIAL_AUTH_EDX_OIDC_KEY = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_SECRET = 'replace-me'
SOCIAL_AUTH_EDX_OIDC_URL_ROOT = OAUTH2_PROVIDER_URL
SOCIAL_AUTH_EDX_OIDC_ID_TOKEN_DECRYPTION_KEY = SOCIAL_AUTH_EDX_OIDC_SECRET
JWT_AUTH.update({
'JWT_SECRET_KEY': 'insecure-secret-key',
'JWT_ISSUERS': (
'127.0.0.1:8000/oauth2',
OAUTH2_PROVIDER_URL,
# Must match the value of JWT_ISSUER configured for the ecommerce worker.
'ecommerce_worker',
),
......@@ -73,6 +102,7 @@ JWT_AUTH.update({
# ORDER PROCESSING
ENROLLMENT_API_URL = get_lms_url('/api/enrollment/v1/enrollment')
ENROLLMENT_FULFILLMENT_TIMEOUT = 15 # devstack is slow!
EDX_API_KEY = 'replace-me'
......@@ -89,16 +119,16 @@ PAYMENT_PROCESSOR_CONFIG = {
'access_key': 'fake-access-key',
'secret_key': 'fake-secret-key',
'payment_page_url': 'https://testsecureacceptance.cybersource.com/pay',
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'receipt_page_url': get_lms_url(settings.RECEIPT_PAGE_PATH),
'cancel_page_url': get_lms_url('/commerce/checkout/cancel/'),
},
'paypal': {
'mode': 'sandbox',
'client_id': 'fake-client-id',
'client_secret': 'fake-client-secret',
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'error_path': PAYMENT_PROCESSOR_ERROR_PATH,
'receipt_url': get_lms_url(settings.RECEIPT_PAGE_PATH),
'cancel_url': get_lms_url('/commerce/checkout/cancel/'),
'error_url': get_lms_url('/commerce/checkout/error/'),
},
}
# END PAYMENT PROCESSING
......
......@@ -11,10 +11,6 @@ from ecommerce.settings.base import *
from ecommerce.settings.logger import get_logger_config
# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
# This needs to be set to None in order to support multitenancy
SITE_ID = None
# Enable offline compression of CSS/JS
COMPRESS_ENABLED = True
COMPRESS_OFFLINE = True
......@@ -62,13 +58,3 @@ DB_OVERRIDES = dict(
for override, value in DB_OVERRIDES.iteritems():
DATABASES['default'][override] = value
# PAYMENT PROCESSOR OVERRIDES
for __, config in PAYMENT_PROCESSOR_CONFIG.iteritems():
config.update({
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'error_path': PAYMENT_PROCESSOR_ERROR_PATH,
})
# END PAYMENT PROCESSOR OVERRIDES
from __future__ import absolute_import
from django.conf import settings
from ecommerce.settings import get_lms_url
from ecommerce.settings.base import *
from ecommerce.settings.logger import get_logger_config
SITE_ID = 1
# TEST SETTINGS
INSTALLED_APPS += (
'django_nose',
......@@ -45,6 +46,21 @@ DATABASES = {
# END IN-MEMORY TEST DATABASE
# URL CONFIGURATION
ECOMMERCE_URL_ROOT = 'http://localhost:8002'
LMS_URL_ROOT = 'http://127.0.0.1:8000'
# The location of the LMS heartbeat page
LMS_HEARTBEAT_URL = get_lms_url('/heartbeat')
# The location of the LMS student dashboard
LMS_DASHBOARD_URL = get_lms_url('/dashboard')
COMMERCE_API_URL = get_lms_url('/api/commerce/v1/')
# END URL CONFIGURATION
# AUTHENTICATION
ENABLE_AUTO_AUTH = True
......@@ -60,6 +76,8 @@ PASSWORD_HASHERS = (
# ORDER PROCESSING
ENROLLMENT_API_URL = LMS_URL_ROOT + '/api/enrollment/v1/enrollment'
EDX_API_KEY = 'replace-me'
# END ORDER PROCESSING
......@@ -74,16 +92,16 @@ PAYMENT_PROCESSOR_CONFIG = {
'access_key': 'fake-access-key',
'secret_key': 'fake-secret-key',
'payment_page_url': 'https://replace-me/',
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'receipt_page_url': get_lms_url(settings.RECEIPT_PAGE_PATH),
'cancel_page_url': get_lms_url('/commerce/checkout/cancel/'),
},
'paypal': {
'mode': 'sandbox',
'client_id': 'fake-client-id',
'client_secret': 'fake-client-secret',
'receipt_path': PAYMENT_PROCESSOR_RECEIPT_PATH,
'cancel_path': PAYMENT_PROCESSOR_CANCEL_PATH,
'error_path': PAYMENT_PROCESSOR_ERROR_PATH,
'receipt_url': get_lms_url(settings.RECEIPT_PAGE_PATH),
'cancel_url': get_lms_url('/commerce/checkout/cancel/'),
'error_url': get_lms_url('/commerce/checkout/error/'),
},
}
# END PAYMENT PROCESSING
......
from django.conf import settings
from social.strategies.django_strategy import DjangoStrategy
class CurrentSiteDjangoStrategy(DjangoStrategy):
"""
Python Social Auth strategy which accounts for the current
Site when enabling third party authentication.
"""
def get_setting(self, name):
# First check the current SiteConfiguration for the setting
setting = self.request.site.siteconfiguration.oauth_settings.get(name)
if not setting:
# Then check Django settings for the setting
setting = getattr(settings, name)
return setting
"""Tests of social auth strategies."""
from django.conf import settings
from threadlocals.threadlocals import get_current_request
from ecommerce.social_auth.strategies import CurrentSiteDjangoStrategy
from ecommerce.tests.testcases import TestCase
class CurrentSiteDjangoStrategyTests(TestCase):
"""Tests of the CurrentSiteDjangoStrategy."""
def setUp(self):
super(CurrentSiteDjangoStrategyTests, self).setUp()
self.site.siteconfiguration.oauth_settings = {
'SOCIAL_AUTH_EDX_OIDC_KEY': 'test-key'
}
self.strategy = CurrentSiteDjangoStrategy(None, get_current_request())
def test_get_setting_from_siteconfiguration(self):
"""Test that a setting can be retrieved from the site configuration."""
setting_name = 'SOCIAL_AUTH_EDX_OIDC_KEY'
self.assertEqual(
self.strategy.get_setting(setting_name),
self.site.siteconfiguration.oauth_settings.get(setting_name)
)
def test_get_setting_from_django_settings(self):
"""Test that a setting can be retrieved from django settings if it doesn't exist in site configuration."""
setting_name = 'SOCIAL_AUTH_EDX_OIDC_SECRET'
self.assertEqual(
self.strategy.get_setting(setting_name),
getattr(settings, setting_name)
)
def test_get_setting_raises_exception_on_missing_setting(self):
"""Test that a setting that does not exist raises exception."""
with self.assertRaises(AttributeError):
self.strategy.get_setting('FAKE_SETTING')
......@@ -28,7 +28,7 @@
{% if user.is_authenticated %}
<li class="btn-group user-menu">
<button type="button" class="btn btn-default hidden-xs main-btn nav-button"
onclick="window.open('{{ lms_dashboard_url }}');">
onclick="window.open('{% settings_value 'LMS_DASHBOARD_URL' %}');">
<i class="icon fa fa-home" aria-hidden="true"></i>
<span class="sr-only">{% trans "Dashboard for:" %}</span>
{{ user.username }}
......
......@@ -28,7 +28,7 @@
{% if user.is_authenticated %}
<li class="btn-group user-menu">
<button type="button" class="btn btn-default hidden-xs main-btn nav-button"
onclick="window.open('{{ lms_dashboard_url }}');">
onclick="window.open('{% settings_value 'LMS_DASHBOARD_URL' %}');">
<i class="icon fa fa-home" aria-hidden="true"></i>
<span class="sr-only">{% trans "Dashboard for:" %}</span>
{{ user.username }}
......
......@@ -5,7 +5,7 @@
{% block additional_options %}
<li class="{{ additional_class }}">
<a class="nav-link" href="{{ lms_dashboard_url }}">{% trans "Student Dashboard" %}</a>
<a class="nav-link" href="{% settings_value 'LMS_DASHBOARD_URL' %}">{% trans "Student Dashboard" %}</a>
</li>
<li class="{{ additional_class }}">
<a class="nav-link" href="{% url 'courses:app' '' %}">{% trans "Course Admin Tool" %}</a>
......
......@@ -22,7 +22,7 @@
{% if user.is_authenticated %}
<li class="btn-group user-menu">
<button type="button" class="btn btn-default hidden-xs main-btn nav-button"
onclick="window.open('{{ lms_dashboard_url }}');">
onclick="window.open('{% settings_value 'LMS_DASHBOARD_URL' %}');">
<i class="icon fa fa-home" aria-hidden="true"></i>
<span class="sr-only">{% trans "Dashboard for:" %}</span>
{{ user.username }}
......
......@@ -23,7 +23,6 @@ class SiteConfigurationFactory(factory.DjangoModelFactory):
class Meta(object):
model = SiteConfiguration
lms_url_root = factory.LazyAttribute(lambda obj: "http://lms.testserver.fake")
site = factory.SubFactory(SiteFactory)
partner = factory.SubFactory(PartnerFactory)
......
......@@ -9,20 +9,18 @@ from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core.cache import cache
from django.core.urlresolvers import reverse
from django.test.client import RequestFactory
import httpretty
import jwt
from mock import patch
from oscar.core.loading import get_class, get_model
from oscar.test import factories
from threadlocals.threadlocals import set_thread_variable
from social.apps.django_app.default.models import UserSocialAuth
from ecommerce.core.url_utils import get_lms_url
from ecommerce.courses.utils import mode_for_seat
from ecommerce.extensions.api.constants import APIConstants as AC
from ecommerce.extensions.api.v2.views.coupons import CouponViewSet
from ecommerce.extensions.fulfillment.signals import SHIPPING_EVENT_NAME
from ecommerce.settings import get_lms_url
from ecommerce.tests.factories import PartnerFactory, SiteConfigurationFactory
Basket = get_model('basket', 'Basket')
......@@ -258,11 +256,6 @@ class SiteMixin(object):
self.partner = site_configuration.partner
self.site = site_configuration.site
request = RequestFactory().get('')
request.session = None
request.site = self.site
set_thread_variable('request', request)
class TestServerUrlMixin(object):
def get_full_url(self, path, site=None):
......
from django.conf import settings
from django.core.urlresolvers import reverse
from ecommerce.core.url_utils import get_lms_dashboard_url
from ecommerce.tests.testcases import TestCase
......@@ -16,4 +16,4 @@ class TestUrls(TestCase):
response = self.client.get(reverse('dashboard:index'))
# Test client can't fetch external URLs, so fetch_redirect_response is set to
# False to avoid loading the final page
self.assertRedirects(response, get_lms_dashboard_url(), fetch_redirect_response=False)
self.assertRedirects(response, settings.LMS_DASHBOARD_URL, fetch_redirect_response=False)
......@@ -10,7 +10,6 @@ from django.shortcuts import redirect
from django.views.generic import RedirectView, TemplateView
from ecommerce.core import views as core_views
from ecommerce.core.url_utils import get_lms_dashboard_url
from ecommerce.extensions.urls import urlpatterns as extensions_patterns
......@@ -29,7 +28,7 @@ def handler403(_):
Oscar's front-end are redirected to the LMS student dashboard, as one would
usually be after signing into the LMS.
"""
return redirect(get_lms_dashboard_url())
return redirect(settings.LMS_DASHBOARD_URL)
admin.autodiscover()
......
......@@ -8,12 +8,11 @@ django-libsass==0.5
django-oscar==1.1.1
django-rest-swagger==0.3.5
django_simple_history==1.6.3
django-threadlocals==0.8
django-waffle==0.10.1
djangorestframework==3.2.3
djangorestframework-jwt==1.7.2
drf-extensions==0.2.8
edx-auth-backends==0.2.0
edx-auth-backends==0.1.3
edx-ecommerce-worker==0.3.3
edx-rest-api-client==1.5.0
jsonfield==1.0.3
......
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