diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py index 8cc72dfa7a..bf0ed94a89 100644 --- a/lms/djangoapps/commerce/api/v0/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py @@ -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): diff --git a/lms/djangoapps/commerce/api/v0/views.py b/lms/djangoapps/commerce/api/v0/views.py index c8a898e7b8..fb9f348dd4 100644 --- a/lms/djangoapps/commerce/api/v0/views.py +++ b/lms/djangoapps/commerce/api/v0/views.py @@ -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)]), }, ) diff --git a/lms/djangoapps/commerce/api/v1/models.py b/lms/djangoapps/commerce/api/v1/models.py index fa0505c590..2f06bad441 100644 --- a/lms/djangoapps/commerce/api/v1/models.py +++ b/lms/djangoapps/commerce/api/v1/models.py @@ -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) diff --git a/lms/djangoapps/commerce/api/v1/serializers.py b/lms/djangoapps/commerce/api/v1/serializers.py index 5c2d16f003..41e305ef24 100644 --- a/lms/djangoapps/commerce/api/v1/serializers.py +++ b/lms/djangoapps/commerce/api/v1/serializers.py @@ -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' diff --git a/lms/djangoapps/commerce/api/v1/tests/test_models.py b/lms/djangoapps/commerce/api/v1/tests/test_models.py index 0eaeaf33b3..a13cf56475 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_models.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_models.py @@ -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 diff --git a/lms/djangoapps/commerce/api/v1/tests/test_serializers.py b/lms/djangoapps/commerce/api/v1/tests/test_serializers.py index ff5f9db2a0..c62eb7df59 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_serializers.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_serializers.py @@ -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) diff --git a/lms/djangoapps/commerce/api/v1/tests/test_views.py b/lms/djangoapps/commerce/api/v1/tests/test_views.py index 48dc2c8ff8..cf5e71e0f7 100644 --- a/lms/djangoapps/commerce/api/v1/tests/test_views.py +++ b/lms/djangoapps/commerce/api/v1/tests/test_views.py @@ -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): diff --git a/lms/djangoapps/commerce/api/v1/urls.py b/lms/djangoapps/commerce/api/v1/urls.py index b07d59f70e..336a24f060 100644 --- a/lms/djangoapps/commerce/api/v1/urls.py +++ b/lms/djangoapps/commerce/api/v1/urls.py @@ -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 = ([ diff --git a/lms/djangoapps/commerce/api/v1/views.py b/lms/djangoapps/commerce/api/v1/views.py index 80d3bd7fed..c85719eccd 100644 --- a/lms/djangoapps/commerce/api/v1/views.py +++ b/lms/djangoapps/commerce/api/v1/views.py @@ -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 diff --git a/lms/djangoapps/commerce/constants.py b/lms/djangoapps/commerce/constants.py index 88d5792802..58847a391a 100644 --- a/lms/djangoapps/commerce/constants.py +++ b/lms/djangoapps/commerce/constants.py @@ -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}.' diff --git a/lms/djangoapps/commerce/http.py b/lms/djangoapps/commerce/http.py index 72406b652f..32dda50030 100644 --- a/lms/djangoapps/commerce/http.py +++ b/lms/djangoapps/commerce/http.py @@ -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) diff --git a/lms/djangoapps/commerce/management/commands/create_orders_for_old_enterprise_course_enrollment.py b/lms/djangoapps/commerce/management/commands/create_orders_for_old_enterprise_course_enrollment.py index 3fbe39a6ea..e2d1e021f1 100644 --- a/lms/djangoapps/commerce/management/commands/create_orders_for_old_enterprise_course_enrollment.py +++ b/lms/djangoapps/commerce/management/commands/create_orders_for_old_enterprise_course_enrollment.py @@ -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 diff --git a/lms/djangoapps/commerce/management/commands/tests/test_create_orders_for_old_enterprise_course_enrollmnet.py b/lms/djangoapps/commerce/management/commands/tests/test_create_orders_for_old_enterprise_course_enrollmnet.py index 5dc0772dc0..8b2472f214 100644 --- a/lms/djangoapps/commerce/management/commands/tests/test_create_orders_for_old_enterprise_course_enrollmnet.py +++ b/lms/djangoapps/commerce/management/commands/tests/test_create_orders_for_old_enterprise_course_enrollmnet.py @@ -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) diff --git a/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py b/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py index 0ebf5b6fe0..bc83ce2288 100644 --- a/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py +++ b/lms/djangoapps/commerce/migrations/0001_data__add_ecommerce_service_user.py @@ -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 diff --git a/lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py b/lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py index e84e2440b1..74ab5a06a1 100644 --- a/lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py +++ b/lms/djangoapps/commerce/migrations/0002_commerceconfiguration.py @@ -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={ diff --git a/lms/djangoapps/commerce/migrations/0003_auto_20160329_0709.py b/lms/djangoapps/commerce/migrations/0003_auto_20160329_0709.py index 87ed65ed24..83d55f490b 100644 --- a/lms/djangoapps/commerce/migrations/0003_auto_20160329_0709.py +++ b/lms/djangoapps/commerce/migrations/0003_auto_20160329_0709.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/lms/djangoapps/commerce/migrations/0004_auto_20160531_0950.py b/lms/djangoapps/commerce/migrations/0004_auto_20160531_0950.py index 51e0a16402..e6e092aa47 100644 --- a/lms/djangoapps/commerce/migrations/0004_auto_20160531_0950.py +++ b/lms/djangoapps/commerce/migrations/0004_auto_20160531_0950.py @@ -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), ), ] diff --git a/lms/djangoapps/commerce/migrations/0005_commerceconfiguration_enable_automatic_refund_approval.py b/lms/djangoapps/commerce/migrations/0005_commerceconfiguration_enable_automatic_refund_approval.py index fae1e04afd..f2aac135a8 100644 --- a/lms/djangoapps/commerce/migrations/0005_commerceconfiguration_enable_automatic_refund_approval.py +++ b/lms/djangoapps/commerce/migrations/0005_commerceconfiguration_enable_automatic_refund_approval.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/lms/djangoapps/commerce/migrations/0006_auto_20170424_1734.py b/lms/djangoapps/commerce/migrations/0006_auto_20170424_1734.py index b99e000ad6..0a27a1cdb0 100644 --- a/lms/djangoapps/commerce/migrations/0006_auto_20170424_1734.py +++ b/lms/djangoapps/commerce/migrations/0006_auto_20170424_1734.py @@ -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), ), ] diff --git a/lms/djangoapps/commerce/migrations/0007_auto_20180313_0609.py b/lms/djangoapps/commerce/migrations/0007_auto_20180313_0609.py index 5e866a8f55..3842ac9ccc 100644 --- a/lms/djangoapps/commerce/migrations/0007_auto_20180313_0609.py +++ b/lms/djangoapps/commerce/migrations/0007_auto_20180313_0609.py @@ -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), ), ] diff --git a/lms/djangoapps/commerce/migrations/0008_auto_20191024_2048.py b/lms/djangoapps/commerce/migrations/0008_auto_20191024_2048.py index 633fd2a45c..a78518765b 100644 --- a/lms/djangoapps/commerce/migrations/0008_auto_20191024_2048.py +++ b/lms/djangoapps/commerce/migrations/0008_auto_20191024_2048.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.25 on 2019-10-24 20:48 diff --git a/lms/djangoapps/commerce/models.py b/lms/djangoapps/commerce/models.py index acca0b6c7c..b425c1d251 100644 --- a/lms/djangoapps/commerce/models.py +++ b/lms/djangoapps/commerce/models.py @@ -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( diff --git a/lms/djangoapps/commerce/signals.py b/lms/djangoapps/commerce/signals.py index 9ba69c9f72..eaf85bdfba 100644 --- a/lms/djangoapps/commerce/signals.py +++ b/lms/djangoapps/commerce/signals.py @@ -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 diff --git a/lms/djangoapps/commerce/tests/__init__.py b/lms/djangoapps/commerce/tests/__init__.py index 2377a2908a..d932f5acba 100644 --- a/lms/djangoapps/commerce/tests/__init__.py +++ b/lms/djangoapps/commerce/tests/__init__.py @@ -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'} diff --git a/lms/djangoapps/commerce/tests/factories.py b/lms/djangoapps/commerce/tests/factories.py index eec08932a7..16fd369714 100644 --- a/lms/djangoapps/commerce/tests/factories.py +++ b/lms/djangoapps/commerce/tests/factories.py @@ -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() diff --git a/lms/djangoapps/commerce/tests/mocks.py b/lms/djangoapps/commerce/tests/mocks.py index 15a5c88f49..6adabc678a 100644 --- a/lms/djangoapps/commerce/tests/mocks.py +++ b/lms/djangoapps/commerce/tests/mocks.py @@ -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): diff --git a/lms/djangoapps/commerce/tests/test_signals.py b/lms/djangoapps/commerce/tests/test_signals.py index 88c358dda0..6f746649fe 100644 --- a/lms/djangoapps/commerce/tests/test_signals.py +++ b/lms/djangoapps/commerce/tests/test_signals.py @@ -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) diff --git a/lms/djangoapps/commerce/tests/test_utils.py b/lms/djangoapps/commerce/tests/test_utils.py index 20c7221c0b..4ca7bf4af3 100644 --- a/lms/djangoapps/commerce/tests/test_utils.py +++ b/lms/djangoapps/commerce/tests/test_utils.py @@ -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) diff --git a/lms/djangoapps/commerce/tests/test_views.py b/lms/djangoapps/commerce/tests/test_views.py index e7d3c42865..9af2ee7d97 100644 --- a/lms/djangoapps/commerce/tests/test_views.py +++ b/lms/djangoapps/commerce/tests/test_views.py @@ -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): diff --git a/lms/djangoapps/commerce/utils.py b/lms/djangoapps/commerce/utils.py index f459e70ab1..5f648a2598 100644 --- a/lms/djangoapps/commerce/utils.py +++ b/lms/djangoapps/commerce/utils.py @@ -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.')