refactor: ran pyupgrade on lms/djangoapps/commerce (#26734)

This commit is contained in:
Usama Sadiq
2021-03-09 14:21:08 +05:00
committed by GitHub
parent 1310f9ed70
commit 2adf8b759e
30 changed files with 206 additions and 236 deletions

View File

@@ -4,12 +4,11 @@
import itertools
import json
from datetime import datetime, timedelta
from unittest import mock
from uuid import uuid4
import ddt
import mock
import pytz
import six
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
@@ -17,11 +16,11 @@ from django.urls import reverse, reverse_lazy
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.tests import EnrollmentEventTestMixin
from openedx.core.djangoapps.embargo.test_utils import restrict_course
from openedx.core.djangoapps.enrollments.api import get_enrollment
from openedx.core.lib.django_test_client_utils import get_absolute_url
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.tests import EnrollmentEventTestMixin
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -53,7 +52,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
:return: Response
"""
payload = {
"course_id": six.text_type(course_id or self.course.id)
"course_id": str(course_id or self.course.id)
}
if marketing_email_opt_in:
payload["email_opt_in"] = True
@@ -69,7 +68,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
assert actual == expected_msg
def setUp(self):
super(BasketsViewTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.url = reverse('commerce_api:v0:baskets:create')
self._login()
@@ -78,13 +77,13 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
# TODO Verify this is the best method to create CourseMode objects.
# TODO Find/create constants for the modes.
for mode in [CourseMode.HONOR, CourseMode.VERIFIED, CourseMode.AUDIT]:
sku_string = six.text_type(uuid4().hex)
sku_string = str(uuid4().hex)
CourseModeFactory.create(
course_id=self.course.id,
mode_slug=mode,
mode_display_name=mode,
sku=sku_string,
bulk_sku='BULK-{}'.format(sku_string)
bulk_sku=f'BULK-{sku_string}'
)
# Ignore events fired from UserFactory creation
@@ -200,9 +199,9 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
CourseMode.objects.filter(course_id=self.course.id).delete()
mode = CourseMode.NO_ID_PROFESSIONAL_MODE
sku_string = six.text_type(uuid4().hex)
sku_string = str(uuid4().hex)
CourseModeFactory.create(course_id=self.course.id, mode_slug=mode, mode_display_name=mode,
sku=sku_string, bulk_sku='BULK-{}'.format(sku_string))
sku=sku_string, bulk_sku=f'BULK-{sku_string}')
response = self._post_to_view()
# The view should return an error status code
@@ -252,7 +251,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
CourseEnrollment.enroll(self.user, self.course.id)
CourseEnrollment.unenroll(self.user, self.course.id, True)
assert not CourseEnrollment.is_enrolled(self.user, self.course.id)
assert get_enrollment(self.user.username, six.text_type(self.course.id)) is not None
assert get_enrollment(self.user.username, str(self.course.id)) is not None
@mock.patch('lms.djangoapps.commerce.api.v0.views.update_email_opt_in')
@ddt.data(*itertools.product((False, True), (False, True), (False, True)))
@@ -290,7 +289,7 @@ class BasketOrderViewTests(UserMixin, TestCase):
path = reverse_lazy(view_name, kwargs={'basket_id': 1})
def setUp(self):
super(BasketOrderViewTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self._login()
def test_order_found(self):

View File

@@ -3,7 +3,6 @@
import logging
import six
from django.urls import reverse
from edx_rest_api_client import exceptions
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
@@ -13,20 +12,19 @@ from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.status import HTTP_406_NOT_ACCEPTABLE, HTTP_409_CONFLICT
from rest_framework.views import APIView
from six import text_type
from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.courseware import courses
from common.djangoapps.entitlements.models import CourseEntitlement
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.signals import SAILTHRU_AUDIT_PURCHASE
from common.djangoapps.util.json_request import JsonResponse
from lms.djangoapps.courseware import courses
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.djangoapps.embargo import api as embargo_api
from openedx.core.djangoapps.enrollments.api import add_enrollment
from openedx.core.djangoapps.enrollments.views import EnrollmentCrossDomainSessionAuth
from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.signals import SAILTHRU_AUDIT_PURCHASE
from common.djangoapps.util.json_request import JsonResponse
from ...constants import Messages
from ...http import DetailResponse
@@ -57,20 +55,20 @@ class BasketsView(APIView):
course_id = request.data.get('course_id')
if not course_id:
return False, None, u'Field course_id is missing.'
return False, None, 'Field course_id is missing.'
try:
course_key = CourseKey.from_string(course_id)
courses.get_course(course_key)
except (InvalidKeyError, ValueError) as ex:
log.exception(u'Unable to locate course matching %s.', course_id)
return False, None, text_type(ex)
log.exception('Unable to locate course matching %s.', course_id)
return False, None, str(ex)
return True, course_key, None
def _enroll(self, course_key, user, mode=CourseMode.DEFAULT_MODE_SLUG):
""" Enroll the user in the course. """
add_enrollment(user.username, six.text_type(course_key), mode)
add_enrollment(user.username, str(course_key), mode)
def _handle_marketing_opt_in(self, request, course_key, user):
"""
@@ -85,7 +83,7 @@ class BasketsView(APIView):
except Exception: # pylint: disable=broad-except
# log the error, return silently
log.exception(
u'Failed to handle marketing opt-in flag: user="%s", course="%s"', user.username, course_key
'Failed to handle marketing opt-in flag: user="%s", course="%s"', user.username, course_key
)
def post(self, request, *args, **kwargs): # lint-amnesty, pylint: disable=unused-argument
@@ -103,7 +101,7 @@ class BasketsView(APIView):
return embargo_response
# Don't do anything if an enrollment already exists
course_id = six.text_type(course_key)
course_id = str(course_key)
enrollment = CourseEnrollment.get_enrollment(user, course_key)
if enrollment and enrollment.is_active:
msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username)
@@ -113,7 +111,7 @@ class BasketsView(APIView):
course = courses.get_course(course_key)
if CourseEnrollment.is_enrollment_closed(user, course):
msg = Messages.ENROLLMENT_CLOSED.format(course_id=course_id)
log.info(u'Unable to enroll user %s in closed course %s.', user.id, course_id)
log.info('Unable to enroll user %s in closed course %s.', user.id, course_id)
return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)
# If there is no audit or honor course mode, this most likely
@@ -126,7 +124,7 @@ class BasketsView(APIView):
if CourseEntitlement.check_for_existing_entitlement_and_enroll(user=user, course_run_key=course_key):
return JsonResponse(
{
'redirect_destination': reverse('courseware', args=[six.text_type(course_id)]),
'redirect_destination': reverse('courseware', args=[str(course_id)]),
},
)

View File

@@ -4,7 +4,6 @@
import logging
from itertools import groupby
import six
from django.db import transaction
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -18,14 +17,14 @@ log = logging.getLogger(__name__)
UNDEFINED = object()
class Course(object):
class Course:
""" Pseudo-course model used to group CourseMode objects. """
id = None # pylint: disable=invalid-name
modes = None
_deleted_modes = None
def __init__(self, id, modes, **kwargs): # pylint: disable=redefined-builtin
self.id = CourseKey.from_string(six.text_type(id)) # pylint: disable=invalid-name
self.id = CourseKey.from_string(str(id)) # pylint: disable=invalid-name
self.modes = list(modes)
self.verification_deadline = UNDEFINED
if 'verification_deadline' in kwargs:
@@ -35,14 +34,14 @@ class Course(object):
@property
def name(self):
""" Return course name. """
course_id = CourseKey.from_string(six.text_type(self.id))
course_id = CourseKey.from_string(str(self.id))
try:
return CourseOverview.get_from_id(course_id).display_name
except CourseOverview.DoesNotExist:
# NOTE (CCB): Ideally, the course modes table should only contain data for courses that exist in
# modulestore. If that is not the case, say for local development/testing, carry on without failure.
log.warning(u'Failed to retrieve CourseOverview for [%s]. Using empty course name.', course_id)
log.warning('Failed to retrieve CourseOverview for [%s]. Using empty course name.', course_id)
return None
def get_mode_display_name(self, mode):
@@ -127,9 +126,9 @@ class Course(object):
def get(cls, course_id):
""" Retrieve a single course. """
try:
course_id = CourseKey.from_string(six.text_type(course_id))
course_id = CourseKey.from_string(str(course_id))
except InvalidKeyError:
log.debug(u'[%s] is not a valid course key.', course_id)
log.debug('[%s] is not a valid course key.', course_id)
raise ValueError # lint-amnesty, pylint: disable=raise-missing-from
course_modes = CourseMode.objects.filter(course_id=course_id)

View File

@@ -4,7 +4,6 @@
from datetime import datetime
import pytz
import six
from django.utils.translation import ugettext as _
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -33,7 +32,7 @@ class CourseModeSerializer(serializers.ModelSerializer):
except AttributeError:
return None
class Meta(object):
class Meta:
model = CourseMode
fields = ('name', 'currency', 'price', 'sku', 'bulk_sku', 'expires')
# For disambiguating within the drf-yasg swagger schema
@@ -45,17 +44,17 @@ def validate_course_id(course_id):
Check that course id is valid and exists in modulestore.
"""
try:
course_key = CourseKey.from_string(six.text_type(course_id))
course_key = CourseKey.from_string(str(course_id))
except InvalidKeyError:
raise serializers.ValidationError( # lint-amnesty, pylint: disable=raise-missing-from
_(u"{course_id} is not a valid course key.").format(
_("{course_id} is not a valid course key.").format(
course_id=course_id
)
)
if not modulestore().has_course(course_key):
raise serializers.ValidationError(
_(u'Course {course_id} does not exist.').format(
_('Course {course_id} does not exist.').format(
course_id=course_id
)
)
@@ -69,7 +68,7 @@ class PossiblyUndefinedDateTimeField(serializers.DateTimeField):
def to_representation(self, value):
if value is UNDEFINED:
return None
return super(PossiblyUndefinedDateTimeField, self).to_representation(value) # lint-amnesty, pylint: disable=super-with-arguments
return super().to_representation(value)
class CourseSerializer(serializers.Serializer):
@@ -79,7 +78,7 @@ class CourseSerializer(serializers.Serializer):
verification_deadline = PossiblyUndefinedDateTimeField(format=None, allow_null=True, required=False)
modes = CourseModeSerializer(many=True)
class Meta(object):
class Meta:
# For disambiguating within the drf-yasg swagger schema
ref_name = 'commerce.Course'

View File

@@ -14,7 +14,7 @@ class CourseTests(TestCase):
""" Tests for Course model. """
def setUp(self):
super(CourseTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course = Course('a/b/c', [])
@ddt.unpack

View File

@@ -13,7 +13,7 @@ class CourseValidatorTests(TestCase):
""" Verify a validator checking non-existent courses."""
course_key = 'non/existing/keyone'
error_msg = u"Course {} does not exist.".format(course_key)
error_msg = f"Course {course_key} does not exist."
with self.assertRaisesRegex(serializers.ValidationError, error_msg):
validate_course_id(course_key)
@@ -21,6 +21,6 @@ class CourseValidatorTests(TestCase):
""" Verify a validator checking invalid course keys."""
course_key = 'invalidkey'
error_msg = u"{} is not a valid course key.".format(course_key)
error_msg = f"{course_key} is not a valid course key."
with self.assertRaisesRegex(serializers.ValidationError, error_msg):
validate_course_id(course_key)

View File

@@ -7,7 +7,6 @@ from datetime import datetime, timedelta
import ddt
import pytz
import six
from django.conf import settings
from django.contrib.auth.models import Permission
from django.test import TestCase
@@ -16,8 +15,8 @@ from django.urls import reverse, reverse_lazy
from rest_framework.utils.encoders import JSONEncoder
from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.verify_student.models import VerificationDeadline
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.verify_student.models import VerificationDeadline
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -28,22 +27,22 @@ PASSWORD = 'test'
JSON_CONTENT_TYPE = 'application/json'
class CourseApiViewTestMixin(object):
class CourseApiViewTestMixin:
""" Mixin for CourseApi views.
Automatically creates a course and CourseMode.
"""
def setUp(self):
super(CourseApiViewTestMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.course = CourseFactory.create()
self.course_mode = CourseMode.objects.create(
course_id=self.course.id,
mode_slug=u'verified',
mode_slug='verified',
min_price=100,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123'
currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123'
)
@classmethod
@@ -60,12 +59,12 @@ class CourseApiViewTestMixin(object):
def _serialize_course_mode(cls, course_mode):
""" Serialize a CourseMode to a dict. """
return {
u'name': course_mode.mode_slug,
u'currency': course_mode.currency.lower(),
u'price': course_mode.min_price,
u'sku': course_mode.sku,
u'bulk_sku': course_mode.bulk_sku,
u'expires': cls._serialize_datetime(course_mode.expiration_datetime),
'name': course_mode.mode_slug,
'currency': course_mode.currency.lower(),
'price': course_mode.min_price,
'sku': course_mode.sku,
'bulk_sku': course_mode.bulk_sku,
'expires': cls._serialize_datetime(course_mode.expiration_datetime),
}
@classmethod
@@ -75,10 +74,10 @@ class CourseApiViewTestMixin(object):
verification_deadline = verification_deadline or VerificationDeadline.deadline_for_course(course.id)
return {
u'id': six.text_type(course.id),
u'name': six.text_type(course.display_name),
u'verification_deadline': cls._serialize_datetime(verification_deadline),
u'modes': [cls._serialize_course_mode(mode) for mode in modes]
'id': str(course.id),
'name': str(course.display_name),
'verification_deadline': cls._serialize_datetime(verification_deadline),
'modes': [cls._serialize_course_mode(mode) for mode in modes]
}
@@ -114,8 +113,8 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
}
def setUp(self):
super(CourseRetrieveUpdateViewTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.path = reverse('commerce_api:v1:courses:retrieve_update', args=[six.text_type(self.course.id)])
super().setUp()
self.path = reverse('commerce_api:v1:courses:retrieve_update', args=[str(self.course.id)])
self.user = UserFactory.create()
self.client.login(username=self.user.username, password=PASSWORD)
@@ -155,11 +154,11 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
def _get_update_response_and_expected_data(self, mode_expiration, verification_deadline):
""" Returns expected data and response for course update. """
expected_course_mode = CourseMode(
mode_slug=u'verified',
mode_slug='verified',
min_price=200,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123',
currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123',
expiration_datetime=mode_expiration
)
expected = self._serialize_course(self.course, [expected_course_mode], verification_deadline)
@@ -226,11 +225,11 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
assert VerificationDeadline.deadline_for_course(self.course.id) == verification_deadline
verified_mode = CourseMode(
mode_slug=u'verified',
mode_slug='verified',
min_price=200,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123',
currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123',
expiration_datetime=None
)
updated_data = self._serialize_course(self.course, [verified_mode], None)
@@ -251,11 +250,11 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
assert VerificationDeadline.deadline_for_course(self.course.id) == verification_deadline
verified_mode = CourseMode(
mode_slug=u'verified',
mode_slug='verified',
min_price=200,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123',
currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123',
expiration_datetime=None
)
updated_data = self._serialize_course(self.course, [verified_mode], None)
@@ -296,22 +295,22 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
existing_mode = self.course_mode
existing_masters_mode = CourseMode.objects.create(
course_id=self.course.id,
mode_slug=u'masters',
mode_slug='masters',
min_price=10000,
currency=u'USD',
sku=u'DEF456',
bulk_sku=u'BULK-DEF456'
currency='USD',
sku='DEF456',
bulk_sku='BULK-DEF456'
)
new_mode = CourseMode(
course_id=self.course.id,
mode_slug=u'credit',
mode_slug='credit',
min_price=500,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123'
currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123'
)
path = reverse('commerce_api:v1:courses:retrieve_update', args=[six.text_type(self.course.id)])
path = reverse('commerce_api:v1:courses:retrieve_update', args=[str(self.course.id)])
data = json.dumps(self._serialize_course(self.course, [new_mode]))
response = self.client.put(path, data, content_type=JSON_CONTENT_TYPE)
assert response.status_code == 200
@@ -341,14 +340,14 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
CourseMode(
mode_slug=mode_slug,
min_price=500,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123',
currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123',
expiration_datetime=expiration_datetime
)
)
course_id = six.text_type(self.course.id)
payload = {u'id': course_id, u'modes': [mode]}
course_id = str(self.course.id)
payload = {'id': course_id, 'modes': [mode]}
path = reverse('commerce_api:v1:courses:retrieve_update', args=[course_id])
expected_status = 400 if CourseMode.is_professional_slug(mode_slug) and expiration_datetime is not None else 200
@@ -360,22 +359,22 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
course = CourseFactory.create()
expected_modes = [
CourseMode(
mode_slug=u'verified',
mode_slug='verified',
min_price=150,
currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123'
currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123'
),
CourseMode(
mode_slug=u'honor',
mode_slug='honor',
min_price=0,
currency=u'USD',
sku=u'DEADBEEF',
bulk_sku=u'BULK-DEADBEEF'
currency='USD',
sku='DEADBEEF',
bulk_sku='BULK-DEADBEEF'
)
]
expected = self._serialize_course(course, expected_modes)
path = reverse('commerce_api:v1:courses:retrieve_update', args=[six.text_type(course.id)])
path = reverse('commerce_api:v1:courses:retrieve_update', args=[str(course.id)])
response = self.client.put(path, json.dumps(expected), content_type=JSON_CONTENT_TYPE, **request_kwargs)
assert response.status_code == 201
@@ -412,29 +411,29 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
expected_modes = [
CourseMode(
mode_slug=CourseMode.DEFAULT_MODE_SLUG,
min_price=150, currency=u'USD',
sku=u'ABC123',
bulk_sku=u'BULK-ABC123'
min_price=150, currency='USD',
sku='ABC123',
bulk_sku='BULK-ABC123'
)
]
course_key = 'non/existing/key'
course_dict = {
u'id': six.text_type(course_key),
u'name': six.text_type('Non Existing Course'),
u'verification_deadline': None,
u'modes': [self._serialize_course_mode(mode) for mode in expected_modes]
'id': str(course_key),
'name': 'Non Existing Course',
'verification_deadline': None,
'modes': [self._serialize_course_mode(mode) for mode in expected_modes]
}
path = reverse('commerce_api:v1:courses:retrieve_update', args=[six.text_type(course_key)])
path = reverse('commerce_api:v1:courses:retrieve_update', args=[str(course_key)])
response = self.client.put(path, json.dumps(course_dict), content_type=JSON_CONTENT_TYPE)
assert response.status_code == 400
expected_dict = {
'id': [
u'Course {} does not exist.'.format(
'Course {} does not exist.'.format(
course_key
)
]
@@ -450,7 +449,7 @@ class OrderViewTests(UserMixin, TestCase):
path = reverse_lazy(view_name, kwargs={'number': ORDER_NUMBER})
def setUp(self):
super(OrderViewTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self._login()
def test_order_found(self):

View File

@@ -10,7 +10,7 @@ from . import views
COURSE_URLS = ([
url(r'^$', views.CourseListView.as_view(), name='list'),
url(r'^{}/$'.format(settings.COURSE_ID_PATTERN), views.CourseRetrieveUpdateView.as_view(), name='retrieve_update'),
url(fr'^{settings.COURSE_ID_PATTERN}/$', views.CourseRetrieveUpdateView.as_view(), name='retrieve_update'),
], 'courses')
ORDER_URLS = ([

View File

@@ -13,12 +13,12 @@ from rest_framework.authentication import SessionAuthentication
from rest_framework.generics import ListAPIView, RetrieveUpdateAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView
from openedx.core.lib.api.authentication import BearerAuthentication
from common.djangoapps.course_modes.models import CourseMode
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.lib.api.mixins import PutAsCreateMixin
from common.djangoapps.util.json_request import JsonResponse
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.lib.api.authentication import BearerAuthentication
from openedx.core.lib.api.mixins import PutAsCreateMixin
from ...utils import is_account_activation_requirement_disabled
from .models import Course

View File

@@ -1,22 +1,22 @@
""" Constants for this app as well as the external API. """
class OrderStatus(object):
class OrderStatus:
"""Constants representing all known order statuses. """
OPEN = 'Open'
FULFILLMENT_ERROR = 'Fulfillment Error'
COMPLETE = 'Complete'
class Messages(object):
class Messages:
""" Strings used to populate response messages. """
NO_ECOM_API = u'E-Commerce API not setup. Enrolled {username} in {course_id} directly.'
NO_SKU_ENROLLED = u'The {enrollment_mode} mode for {course_id}, {course_name}, does not have a SKU. Enrolling ' \
u'{username} directly. Course announcement is {announcement}.'
ENROLL_DIRECTLY = u'Enroll {username} in {course_id} directly because no need for E-Commerce baskets and orders.'
ORDER_COMPLETED = u'Order {order_number} was completed.'
ORDER_INCOMPLETE_ENROLLED = u'Order {order_number} was created, but is not yet complete. User was enrolled.'
NO_HONOR_MODE = u'Course {course_id} does not have an honor mode.'
NO_DEFAULT_ENROLLMENT_MODE = u'Course {course_id} does not have an honor or audit mode.'
ENROLLMENT_EXISTS = u'User {username} is already enrolled in {course_id}.'
ENROLLMENT_CLOSED = u'Enrollment is closed for {course_id}.'
NO_ECOM_API = 'E-Commerce API not setup. Enrolled {username} in {course_id} directly.'
NO_SKU_ENROLLED = 'The {enrollment_mode} mode for {course_id}, {course_name}, does not have a SKU. Enrolling ' \
'{username} directly. Course announcement is {announcement}.'
ENROLL_DIRECTLY = 'Enroll {username} in {course_id} directly because no need for E-Commerce baskets and orders.'
ORDER_COMPLETED = 'Order {order_number} was completed.'
ORDER_INCOMPLETE_ENROLLED = 'Order {order_number} was created, but is not yet complete. User was enrolled.'
NO_HONOR_MODE = 'Course {course_id} does not have an honor mode.'
NO_DEFAULT_ENROLLMENT_MODE = 'Course {course_id} does not have an honor or audit mode.'
ENROLLMENT_EXISTS = 'User {username} is already enrolled in {course_id}.'
ENROLLMENT_CLOSED = 'Enrollment is closed for {course_id}.'

View File

@@ -11,7 +11,7 @@ class DetailResponse(JsonResponse):
def __init__(self, message, status=HTTP_200_OK):
data = {'detail': message}
super(DetailResponse, self).__init__(resp_obj=data, status=status) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(resp_obj=data, status=status)
class InternalRequestErrorResponse(DetailResponse):
@@ -19,7 +19,7 @@ class InternalRequestErrorResponse(DetailResponse):
def __init__(self, internal_message):
message = (
u'Call to E-Commerce API failed. Internal Service Message: [{internal_message}]'
'Call to E-Commerce API failed. Internal Service Message: [{internal_message}]'
.format(internal_message=internal_message)
)
super(InternalRequestErrorResponse, self).__init__(message=message, status=HTTP_500_INTERNAL_SERVER_ERROR) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(message=message, status=HTTP_500_INTERNAL_SERVER_ERROR)

View File

@@ -11,17 +11,15 @@ from textwrap import dedent
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand, CommandError
from enterprise.models import EnterpriseCourseEnrollment
from opaque_keys.edx.keys import CourseKey
from requests import Timeout
from slumber.exceptions import HttpServerError, SlumberBaseException
from enterprise.models import EnterpriseCourseEnrollment
from common.djangoapps.student.models import CourseEnrollment
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from common.djangoapps.util.query import use_read_replica_if_available
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
User = get_user_model()
@@ -48,7 +46,7 @@ class Command(BaseCommand):
EnterpriseCourseEnrollments Queryset
"""
self.stdout.write(u'Getting enrollments from {start} to {end} index (as per command params)'
self.stdout.write('Getting enrollments from {start} to {end} index (as per command params)'
.format(start=start_index or 'start', end=end_index or 'end'))
enrollments_qs = EnterpriseCourseEnrollment.objects.filter(
source__isnull=True
@@ -118,7 +116,7 @@ class Command(BaseCommand):
"""
self.stdout.write(
u'\tFetching Enrollments from {start} to {end}'.format(start=offset, end=offset + batch_size)
'\tFetching Enrollments from {start} to {end}'.format(start=offset, end=offset + batch_size)
)
enrollments = enrollments_queryset.select_related(
'enterprise_customer_user', 'enterprise_customer_user__enterprise_customer'
@@ -135,7 +133,7 @@ class Command(BaseCommand):
invalid = 0
self.stdout.write(
u'\t\tProcessing Total : {},'.format(len(enrollments_batch))
'\t\tProcessing Total : {},'.format(len(enrollments_batch))
)
for enrollment in enrollments_batch:
@@ -160,23 +158,23 @@ class Command(BaseCommand):
"enterprise_customer_uuid": str(enterprise_customer.uuid),
}
except CourseEnrollment.DoesNotExist:
self.stderr.write(u'\t\tskipping enrollment {}, as CourseEnrollment not found'.format(enrollment.id))
self.stderr.write(f'\t\tskipping enrollment {enrollment.id}, as CourseEnrollment not found')
invalid += 1
continue
except Exception as ex: # pylint: disable=broad-except
self.stderr.write(u'\t\tskipping enrollment {} due to invalid data. {}'.format(enrollment.id, ex))
self.stderr.write(f'\t\tskipping enrollment {enrollment.id} due to invalid data. {ex}')
invalid += 1
continue
enrollments_payload.append(enrollment_payload)
self.stdout.write(u'\t\tFound {count} Paid enrollments to sync'.format(count=len(enrollments_payload)))
self.stdout.write('\t\tFound {count} Paid enrollments to sync'.format(count=len(enrollments_payload)))
if not enrollments_payload:
return 0, 0, 0, invalid, non_paid, []
self.stdout.write(u'\t\tSyncing started...')
self.stdout.write('\t\tSyncing started...')
success, new, failed, order_numbers = self._create_manual_enrollment_orders(enrollments_payload)
self.stdout.write(
u'\t\tSuccess: {} , New: {}, Failed: {}, Invalid:{} , Non-Paid: {}'.format(
'\t\tSuccess: {} , New: {}, Failed: {}, Invalid:{} , Non-Paid: {}'.format(
success, new, failed, invalid, non_paid,
)
)
@@ -186,7 +184,7 @@ class Command(BaseCommand):
"""
Syncs a single site
"""
self.stdout.write(u'Syncing process started.')
self.stdout.write('Syncing process started.')
offset = 0
enrollments_queue = []
@@ -201,7 +199,7 @@ class Command(BaseCommand):
while offset < enrollments_count:
is_last_iteration = (offset + enrollments_query_batch_size) >= enrollments_count
self.stdout.write(
u'\tSyncing enrollments batch from {start} to {end}.'.format(
'\tSyncing enrollments batch from {start} to {end}.'.format(
start=offset, end=offset + enrollments_query_batch_size
)
)
@@ -221,23 +219,23 @@ class Command(BaseCommand):
invalid_enrollments += invalid
non_paid_enrollments += non_paid
new_created_order_numbers += order_numbers
self.stdout.write(u'\t\tsleeping for {} second/seconds'.format(sleep_time))
self.stdout.write(f'\t\tsleeping for {sleep_time} second/seconds')
time.sleep(sleep_time)
self.stdout.write(
u'\tSuccessfully synced enrollments batch from {start} to {end}'.format(
'\tSuccessfully synced enrollments batch from {start} to {end}'.format(
start=offset, end=offset + enrollments_query_batch_size,
)
)
offset += enrollments_query_batch_size
self.stdout.write(
u'[Final Summary] Enrollments Success: {}, New: {}, Failed: {}, Invalid: {} , Non-Paid: {}'.format(
'[Final Summary] Enrollments Success: {}, New: {}, Failed: {}, Invalid: {} , Non-Paid: {}'.format(
successfully_synced_enrollments, new_created_orders, failed_to_synced_enrollments, invalid_enrollments,
non_paid_enrollments
)
)
self.stdout.write('New created order numbers {}'.format(new_created_order_numbers))
self.stdout.write(f'New created order numbers {new_created_order_numbers}')
def add_arguments(self, parser):
"""
@@ -281,12 +279,12 @@ class Command(BaseCommand):
sleep_time = options['sleep_time']
try:
self.stdout.write(u'Command execution started with options = {}.'.format(options))
self.stdout.write(f'Command execution started with options = {options}.')
enrollments_queryset = self._get_enrollments_queryset(start_index, end_index)
enrollments_count = enrollments_queryset.count()
self.stdout.write(u'Total Enrollments count to process: {count}'.format(count=enrollments_count))
self.stdout.write(f'Total Enrollments count to process: {enrollments_count}')
self._sync(enrollments_queryset, enrollments_count, batch_size, sleep_time)
except Exception as ex:
traceback.print_exc()
raise CommandError(u'Command failed with traceback %s' % str(ex)) # lint-amnesty, pylint: disable=raise-missing-from
raise CommandError('Command failed with traceback %s' % str(ex)) # lint-amnesty, pylint: disable=raise-missing-from

View File

@@ -3,19 +3,19 @@ Test the create_orders_for_old_enterprise_course_enrollment management command
"""
import re
from io import StringIO
from unittest.mock import patch
from django.core.management import call_command
from django.test import TestCase, override_settings
from django.utils.six import StringIO
from mock import patch
from six.moves import range
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.tests.factories import UserFactory, CourseEnrollmentFactory
from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory
from openedx.core.djangoapps.credit.tests.test_api import TEST_ECOMMERCE_WORKER
from openedx.core.djangolib.testing.utils import skip_unless_lms
from openedx.features.enterprise_support.tests.factories import (
EnterpriseCourseEnrollmentFactory, EnterpriseCustomerUserFactory
EnterpriseCourseEnrollmentFactory,
EnterpriseCustomerUserFactory
)
@@ -28,7 +28,7 @@ class TestEnterpriseCourseEnrollmentCreateOldOrder(TestCase):
@classmethod
def setUpTestData(cls):
super(TestEnterpriseCourseEnrollmentCreateOldOrder, cls).setUpTestData()
super().setUpTestData()
UserFactory(username=TEST_ECOMMERCE_WORKER)
cls._create_enterprise_course_enrollments(30)

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import User

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
@@ -21,7 +18,7 @@ class Migration(migrations.Migration):
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
('checkout_on_ecommerce_service', models.BooleanField(default=False, help_text='Use the checkout page hosted by the E-Commerce service.')),
('single_course_checkout_page', models.CharField(default=u'/basket/single-item/', help_text='Path to single course checkout page hosted by the E-Commerce service.', max_length=255)),
('single_course_checkout_page', models.CharField(default='/basket/single-item/', help_text='Path to single course checkout page hosted by the E-Commerce service.', max_length=255)),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
],
options={

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
@@ -19,6 +16,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='commerceconfiguration',
name='receipt_page',
field=models.CharField(default=u'/commerce/checkout/receipt/?orderNum=', help_text='Path to order receipt page.', max_length=255),
field=models.CharField(default='/commerce/checkout/receipt/?orderNum=', help_text='Path to order receipt page.', max_length=255),
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
@@ -14,6 +11,6 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='commerceconfiguration',
name='receipt_page',
field=models.CharField(default=u'/checkout/receipt/?order_number=', help_text='Path to order receipt page.', max_length=255),
field=models.CharField(default='/checkout/receipt/?order_number=', help_text='Path to order receipt page.', max_length=255),
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
@@ -18,6 +15,6 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='commerceconfiguration',
name='basket_checkout_page',
field=models.CharField(default=u'/basket/add/', help_text='Path to course(s) checkout page hosted by the E-Commerce service.', max_length=255),
field=models.CharField(default='/basket/add/', help_text='Path to course(s) checkout page hosted by the E-Commerce service.', max_length=255),
),
]

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.25 on 2019-10-24 20:48

View File

@@ -17,7 +17,7 @@ class CommerceConfiguration(ConfigurationModel):
.. no_pii:
"""
class Meta(object):
class Meta:
app_label = "commerce"
API_NAME = 'commerce'
@@ -32,7 +32,7 @@ class CommerceConfiguration(ConfigurationModel):
basket_checkout_page = models.CharField(
max_length=255,
default=u'/basket/add/',
default='/basket/add/',
help_text=_('Path to course(s) checkout page hosted by the E-Commerce service.')
)
cache_ttl = models.PositiveIntegerField(

View File

@@ -9,8 +9,8 @@ from crum import get_current_request
from django.contrib.auth.models import AnonymousUser
from django.dispatch import receiver
from openedx.core.djangoapps.commerce.utils import is_commerce_service_configured
from common.djangoapps.student.signals import REFUND_ORDER
from openedx.core.djangoapps.commerce.utils import is_commerce_service_configured
from .utils import refund_seat

View File

@@ -1,16 +1,16 @@
# -*- coding: utf-8 -*-
""" Commerce app tests package. """
from unittest import mock
import httpretty
import mock
from django.conf import settings
from django.test import TestCase
from freezegun import freeze_time
from common.djangoapps.student.tests.factories import UserFactory
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user
from common.djangoapps.student.tests.factories import UserFactory
JSON = 'application/json'
TEST_PUBLIC_URL_ROOT = 'http://www.example.com'
@@ -34,7 +34,7 @@ class EdxRestApiClientTest(TestCase):
]
def setUp(self):
super(EdxRestApiClientTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.user = UserFactory()
@httpretty.activate
@@ -67,7 +67,7 @@ class EdxRestApiClientTest(TestCase):
}
}
expected_jwt = create_jwt_for_user(self.user, additional_claims=claims, scopes=self.SCOPES)
expected_header = u'JWT {}'.format(expected_jwt)
expected_header = f'JWT {expected_jwt}'
assert actual_header == expected_header
@httpretty.activate
@@ -86,4 +86,4 @@ class EdxRestApiClientTest(TestCase):
adding_headers={'Content-Type': JSON},
)
actual_object = ecommerce_api_client(self.user).baskets(1).order.get()
assert actual_object == {u'result': u'Préparatoire'}
assert actual_object == {'result': 'Préparatoire'}

View File

@@ -7,7 +7,7 @@ from factory.fuzzy import FuzzyText
class OrderFactory(factory.Factory):
""" Factory for stubbing orders resources from Ecommerce (v2). """
class Meta(object):
class Meta:
model = dict
number = factory.Sequence(lambda n: 'edx-%d' % n)
@@ -20,7 +20,7 @@ class OrderFactory(factory.Factory):
class OrderLineFactory(factory.Factory):
""" Factory for stubbing order lines resources from Ecommerce (v2). """
class Meta(object):
class Meta:
model = dict
title = FuzzyText(prefix='Seat in ')
@@ -34,7 +34,7 @@ class OrderLineFactory(factory.Factory):
class ProductFactory(factory.Factory):
""" Factory for stubbing Product resources from Ecommerce (v2). """
class Meta(object):
class Meta:
model = dict
id = factory.Sequence(lambda n: n) # pylint: disable=invalid-name
@@ -49,7 +49,7 @@ class ProductAttributeFactory(factory.Factory):
""" Factory for stubbing product attribute resources from
Ecommerce (v2).
"""
class Meta(object):
class Meta:
model = dict
name = FuzzyText()

View File

@@ -10,7 +10,7 @@ from django.conf import settings
from . import factories
class mock_ecommerce_api_endpoint(object):
class mock_ecommerce_api_endpoint:
"""
Base class for contextmanagers used to mock calls to api endpoints.
@@ -95,11 +95,11 @@ class mock_basket_order(mock_ecommerce_api_endpoint):
method = httpretty.GET
def __init__(self, basket_id, **kwargs):
super(mock_basket_order, self).__init__(**kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(**kwargs)
self.basket_id = basket_id
def get_path(self):
return '/baskets/{}/order/'.format(self.basket_id)
return f'/baskets/{self.basket_id}/order/'
class mock_create_refund(mock_ecommerce_api_endpoint):
@@ -131,11 +131,11 @@ class mock_process_refund(mock_ecommerce_api_endpoint):
method = httpretty.PUT
def __init__(self, refund_id, **kwargs):
super(mock_process_refund, self).__init__(**kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(**kwargs)
self.refund_id = refund_id
def get_path(self):
return '/refunds/{}/process/'.format(self.refund_id)
return f'/refunds/{self.refund_id}/process/'
class mock_order_endpoint(mock_ecommerce_api_endpoint):
@@ -145,11 +145,11 @@ class mock_order_endpoint(mock_ecommerce_api_endpoint):
method = httpretty.GET
def __init__(self, order_number, **kwargs):
super(mock_order_endpoint, self).__init__(**kwargs) # lint-amnesty, pylint: disable=super-with-arguments
super().__init__(**kwargs)
self.order_number = order_number
def get_path(self):
return '/orders/{}/'.format(self.order_number)
return f'/orders/{self.order_number}/'
class mock_get_orders(mock_ecommerce_api_endpoint):

View File

@@ -1,4 +1,3 @@
# coding=UTF-8
"""
Tests for signal handling in commerce djangoapp.
"""
@@ -6,17 +5,18 @@ Tests for signal handling in commerce djangoapp.
import base64
import json
from unittest import mock
from urllib.parse import urljoin
import pytest
import ddt
import httpretty
import mock
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.test import TestCase
from django.test.utils import override_settings
from opaque_keys.edx.keys import CourseKey
from requests import Timeout
from six.moves.urllib.parse import urljoin
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.signals import REFUND_ORDER
@@ -40,7 +40,7 @@ class TestRefundSignal(TestCase):
"""
def setUp(self):
super(TestRefundSignal, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
# Ensure the E-Commerce service user exists
UserFactory(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME, is_staff=True)
@@ -306,7 +306,7 @@ class TestRefundSignal(TestCase):
expected = {
'content-type': JSON,
'Authorization': 'Basic {}'.format(base64.b64encode(
'{user}/token:{pwd}'.format(user=ZENDESK_USER, pwd=ZENDESK_API_KEY).encode('utf8')).decode('utf8')
f'{ZENDESK_USER}/token:{ZENDESK_API_KEY}'.encode('utf8')).decode('utf8')
)
}
self.assertDictContainsSubset(expected, last_request.headers)

View File

@@ -2,6 +2,8 @@
import json
from unittest.mock import patch
from urllib.parse import urlencode
import ddt
import httpretty
@@ -9,19 +11,17 @@ from django.conf import settings
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from mock import patch
from opaque_keys.edx.locator import CourseLocator
from six.moves.urllib.parse import urlencode
from waffle.testutils import override_switch
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.factories import TEST_PASSWORD, UserFactory
from lms.djangoapps.commerce.models import CommerceConfiguration
from lms.djangoapps.commerce.utils import EcommerceService, refund_entitlement, refund_seat
from openedx.core.djangolib.testing.utils import skip_unless_lms
from openedx.core.lib.log_utils import audit_log
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.factories import TEST_PASSWORD, UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -61,7 +61,7 @@ class EcommerceServiceTests(TestCase):
self.user = UserFactory.create()
self.request = self.request_factory.get("foo")
update_commerce_config(enabled=True)
super(EcommerceServiceTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
def test_is_enabled(self):
"""Verify that is_enabled() returns True when ecomm checkout is enabled. """
@@ -105,7 +105,7 @@ class EcommerceServiceTests(TestCase):
"""Verify that the proper Receipt page URL is returned."""
order_number = 'ORDER1'
url = EcommerceService().get_receipt_page_url(order_number)
expected_url = 'http://ecommerce_url/checkout/receipt/?order_number={}'.format(order_number)
expected_url = f'http://ecommerce_url/checkout/receipt/?order_number={order_number}'
assert url == expected_url
@override_settings(ECOMMERCE_PUBLIC_URL_ROOT='http://ecommerce_url')
@@ -187,7 +187,7 @@ class RefundUtilMethodTests(ModuleStoreTestCase):
"""Tests for Refund Utilities"""
def setUp(self):
super(RefundUtilMethodTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.user = UserFactory()
UserFactory(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME, is_staff=True)

View File

@@ -3,11 +3,11 @@
from common.djangoapps.student.tests.factories import UserFactory
class UserMixin(object):
class UserMixin:
""" Mixin for tests involving users. """
def setUp(self):
super(UserMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
super().setUp()
self.user = UserFactory()
def _login(self):

View File

@@ -3,18 +3,18 @@
import json
import logging
from urllib.parse import urlencode, urljoin
import requests
import six
import waffle
from django.conf import settings
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.utils.translation import ugettext as _
from opaque_keys.edx.keys import CourseKey
from six.moves.urllib.parse import urlencode, urljoin
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment # lint-amnesty, pylint: disable=unused-import
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client, is_commerce_service_configured
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.theming import helpers as theming_helpers
@@ -38,7 +38,7 @@ def is_account_activation_requirement_disabled():
return waffle.switch_is_active(switch_name)
class EcommerceService(object):
class EcommerceService:
""" Helper class for ecommerce service integration. """
def __init__(self):
self.config = CommerceConfiguration.current()
@@ -157,7 +157,7 @@ def refund_entitlement(course_entitlement):
if not is_commerce_service_configured():
log.error(
u'Ecommerce service is not configured, cannot refund for user [%s], course entitlement [%s].',
'Ecommerce service is not configured, cannot refund for user [%s], course entitlement [%s].',
enrollee.id,
entitlement_uuid
)
@@ -167,7 +167,7 @@ def refund_entitlement(course_entitlement):
api_client = ecommerce_api_client(service_user)
log.info(
u'Attempting to create a refund for user [%s], course entitlement [%s]...',
'Attempting to create a refund for user [%s], course entitlement [%s]...',
enrollee.id,
entitlement_uuid
)
@@ -183,8 +183,8 @@ def refund_entitlement(course_entitlement):
except Exception as exc: # pylint: disable=broad-except
# Catch any possible exceptions from the Ecommerce service to ensure we fail gracefully
log.exception(
u"Unexpected exception while attempting to initiate refund for user [%s], "
u"course entitlement [%s] message: [%s]",
"Unexpected exception while attempting to initiate refund for user [%s], "
"course entitlement [%s] message: [%s]",
enrollee.id,
course_entitlement.uuid,
str(exc)
@@ -193,7 +193,7 @@ def refund_entitlement(course_entitlement):
if refund_ids:
log.info(
u'Refund successfully opened for user [%s], course entitlement [%s]: %r',
'Refund successfully opened for user [%s], course entitlement [%s]: %r',
enrollee.id,
entitlement_uuid,
refund_ids,
@@ -207,7 +207,7 @@ def refund_entitlement(course_entitlement):
always_notify=True,
)
else:
log.warning(u'No refund opened for user [%s], course entitlement [%s]', enrollee.id, entitlement_uuid)
log.warning('No refund opened for user [%s], course entitlement [%s]', enrollee.id, entitlement_uuid)
return False
@@ -229,18 +229,18 @@ def refund_seat(course_enrollment, change_mode=False):
exceptions.Timeout: if the attempt to reach the commerce service timed out.
"""
User = get_user_model() # pylint:disable=invalid-name
course_key_str = six.text_type(course_enrollment.course_id)
course_key_str = str(course_enrollment.course_id)
enrollee = course_enrollment.user
service_user = User.objects.get(username=settings.ECOMMERCE_SERVICE_WORKER_USERNAME)
api_client = ecommerce_api_client(service_user)
log.info(u'Attempting to create a refund for user [%s], course [%s]...', enrollee.id, course_key_str)
log.info('Attempting to create a refund for user [%s], course [%s]...', enrollee.id, course_key_str)
refund_ids = api_client.refunds.post({'course_id': course_key_str, 'username': enrollee.username})
if refund_ids:
log.info(u'Refund successfully opened for user [%s], course [%s]: %r', enrollee.id, course_key_str, refund_ids)
log.info('Refund successfully opened for user [%s], course [%s]: %r', enrollee.id, course_key_str, refund_ids)
_process_refund(
refund_ids=refund_ids,
@@ -253,7 +253,7 @@ def refund_seat(course_enrollment, change_mode=False):
is_active=False, skip_refund=True)
course_enrollment.save()
else:
log.info(u'No refund opened for user [%s], course [%s]', enrollee.id, course_key_str)
log.info('No refund opened for user [%s], course [%s]', enrollee.id, course_key_str)
return refund_ids
@@ -285,10 +285,10 @@ def _process_refund(refund_ids, api_client, mode, user, always_notify=False):
# We are then able to approve payment. Additionally, this ensures we don't tie up an
# additional web worker when the E-Commerce Service tries to unenroll the learner.
api_client.refunds(refund_id).process.put({'action': 'approve_payment_only'})
log.info(u'Refund [%d] successfully approved.', refund_id)
log.info('Refund [%d] successfully approved.', refund_id)
except: # pylint: disable=bare-except
# Push the refund to Support to process
log.exception(u'Failed to automatically approve refund [%d]!', refund_id)
log.exception('Failed to automatically approve refund [%d]!', refund_id)
refunds_requiring_approval.append(refund_id)
else:
refunds_requiring_approval = refund_ids
@@ -303,7 +303,7 @@ def _process_refund(refund_ids, api_client, mode, user, always_notify=False):
# 'verified' is the only enrollment mode that should presently
# result in opening a refund request.
log.info(
u'Skipping refund support notification for non-verified mode for user [%s], mode: [%s]',
'Skipping refund support notification for non-verified mode for user [%s], mode: [%s]',
user.id,
mode,
)
@@ -344,18 +344,18 @@ def _send_refund_notification(user, refund_ids):
def _generate_refund_notification_body(student, refund_ids):
""" Returns a refund notification message body. """
msg = _(
u'A refund request has been initiated for {username} ({email}). '
'A refund request has been initiated for {username} ({email}). '
'To process this request, please visit the link(s) below.'
).format(username=student.username, email=student.email)
ecommerce_url_root = configuration_helpers.get_value(
'ECOMMERCE_PUBLIC_URL_ROOT', settings.ECOMMERCE_PUBLIC_URL_ROOT,
)
refund_urls = [urljoin(ecommerce_url_root, '/dashboard/refunds/{}/'.format(refund_id))
refund_urls = [urljoin(ecommerce_url_root, f'/dashboard/refunds/{refund_id}/')
for refund_id in refund_ids]
# emails contained in this message could contain unicode characters so encode as such
return u'{msg}\n\n{urls}'.format(msg=msg, urls='\n'.join(refund_urls))
return '{msg}\n\n{urls}'.format(msg=msg, urls='\n'.join(refund_urls))
def create_zendesk_ticket(requester_name, requester_email, subject, body, tags=None):
@@ -378,7 +378,7 @@ def create_zendesk_ticket(requester_name, requester_email, subject, body, tags=N
'ticket': {
'requester': {
'name': requester_name,
'email': six.text_type(requester_email)
'email': str(requester_email)
},
'subject': subject,
'comment': {'body': body},
@@ -391,7 +391,7 @@ def create_zendesk_ticket(requester_name, requester_email, subject, body, tags=N
# Set the request parameters
url = urljoin(settings.ZENDESK_URL, '/api/v2/tickets.json')
user = '{}/token'.format(settings.ZENDESK_USER)
user = f'{settings.ZENDESK_USER}/token'
pwd = settings.ZENDESK_API_KEY
headers = {'content-type': 'application/json'}
@@ -400,7 +400,7 @@ def create_zendesk_ticket(requester_name, requester_email, subject, body, tags=N
# Check for HTTP codes other than 201 (Created)
if response.status_code != 201:
log.error(u'Failed to create ticket. Status: [%d], Body: [%s]', response.status_code, response.content)
log.error('Failed to create ticket. Status: [%d], Body: [%s]', response.status_code, response.content)
return False
else:
log.debug('Successfully created ticket.')