diff --git a/openedx/core/djangoapps/profile_images/exceptions.py b/openedx/core/djangoapps/profile_images/exceptions.py index e834a83605..c9147e4a37 100644 --- a/openedx/core/djangoapps/profile_images/exceptions.py +++ b/openedx/core/djangoapps/profile_images/exceptions.py @@ -3,9 +3,6 @@ Exceptions related to the handling of profile images. """ -from six import text_type - - class ImageValidationError(Exception): """ Exception to use when the system rejects a user-supplied source image. @@ -15,4 +12,4 @@ class ImageValidationError(Exception): """ Translate the developer-facing exception message for API clients. """ - return text_type(self) + return str(self) diff --git a/openedx/core/djangoapps/profile_images/images.py b/openedx/core/djangoapps/profile_images/images.py index 065690bf4c..64b6d215f7 100644 --- a/openedx/core/djangoapps/profile_images/images.py +++ b/openedx/core/djangoapps/profile_images/images.py @@ -9,7 +9,6 @@ from contextlib import closing from io import BytesIO import piexif -import six from django.conf import settings from django.core.files.base import ContentFile from django.utils.translation import ugettext as _ @@ -96,25 +95,25 @@ def validate_uploaded_image(uploaded_file): if uploaded_file.size > settings.PROFILE_IMAGE_MAX_BYTES: # lint-amnesty, pylint: disable=no-else-raise file_upload_too_large = _( - u'The file must be smaller than {image_max_size} in size.' + 'The file must be smaller than {image_max_size} in size.' ).format( image_max_size=_user_friendly_size(settings.PROFILE_IMAGE_MAX_BYTES) ) raise ImageValidationError(file_upload_too_large) elif uploaded_file.size < settings.PROFILE_IMAGE_MIN_BYTES: file_upload_too_small = _( - u'The file must be at least {image_min_size} in size.' + 'The file must be at least {image_min_size} in size.' ).format( image_min_size=_user_friendly_size(settings.PROFILE_IMAGE_MIN_BYTES) ) raise ImageValidationError(file_upload_too_small) # check the file extension looks acceptable - filename = six.text_type(uploaded_file.name).lower() + filename = str(uploaded_file.name).lower() filetype = [ft for ft in IMAGE_TYPES if any(filename.endswith(ext) for ext in IMAGE_TYPES[ft].extensions)] if not filetype: file_upload_bad_type = _( - u'The file must be one of the following types: {valid_file_types}.' + 'The file must be one of the following types: {valid_file_types}.' ).format(valid_file_types=_get_valid_file_types()) raise ImageValidationError(file_upload_bad_type) filetype = filetype[0] @@ -122,8 +121,8 @@ def validate_uploaded_image(uploaded_file): # check mimetype matches expected file type if uploaded_file.content_type not in IMAGE_TYPES[filetype].mimetypes: file_upload_bad_mimetype = _( - u'The Content-Type header for this file does not match ' - u'the file data. The file may be corrupted.' + 'The Content-Type header for this file does not match ' + 'the file data. The file may be corrupted.' ) raise ImageValidationError(file_upload_bad_mimetype) @@ -131,8 +130,8 @@ def validate_uploaded_image(uploaded_file): headers = IMAGE_TYPES[filetype].magic if binascii.hexlify(uploaded_file.read(len(headers[0]) // 2)).decode('utf-8') not in headers: file_upload_bad_ext = _( - u'The file name extension for this file does not match ' - u'the file data. The file may be corrupted.' + 'The file name extension for this file does not match ' + 'the file data. The file may be corrupted.' ) raise ImageValidationError(file_upload_bad_ext) # avoid unexpected errors from subsequent modules expecting the fp to be at 0 @@ -245,4 +244,4 @@ def _user_friendly_size(size): while size >= 1024 and i < len(units): size //= 1024 i += 1 - return u'{} {}'.format(size, units[i]) + return '{} {}'.format(size, units[i]) diff --git a/openedx/core/djangoapps/profile_images/tests/helpers.py b/openedx/core/djangoapps/profile_images/tests/helpers.py index ec8f9aba1d..3170b58234 100644 --- a/openedx/core/djangoapps/profile_images/tests/helpers.py +++ b/openedx/core/djangoapps/profile_images/tests/helpers.py @@ -9,7 +9,6 @@ from tempfile import NamedTemporaryFile from django.core.files.uploadedfile import UploadedFile import piexif from PIL import Image -from six.moves import range @contextmanager diff --git a/openedx/core/djangoapps/profile_images/tests/test_images.py b/openedx/core/djangoapps/profile_images/tests/test_images.py index 3ff89e4b32..931b563361 100644 --- a/openedx/core/djangoapps/profile_images/tests/test_images.py +++ b/openedx/core/djangoapps/profile_images/tests/test_images.py @@ -1,9 +1,10 @@ """ Test cases for image processing functions in the profile image package. """ +from contextlib import closing +from unittest import mock import pytest -from contextlib import closing from itertools import product import os from tempfile import NamedTemporaryFile @@ -12,10 +13,8 @@ from django.core.files.uploadedfile import UploadedFile from django.test import TestCase from django.test.utils import override_settings import ddt -import mock import piexif from PIL import Image -from six import text_type from openedx.core.djangolib.testing.utils import skip_unless_lms from ..exceptions import ImageValidationError @@ -37,7 +36,7 @@ class TestValidateUploadedImage(TestCase): Test validate_uploaded_image """ FILE_UPLOAD_BAD_TYPE = ( - u'The file must be one of the following types: {valid_file_types}.'.format( + 'The file must be one of the following types: {valid_file_types}.'.format( valid_file_types=_get_valid_file_types() ) ) @@ -49,16 +48,16 @@ class TestValidateUploadedImage(TestCase): if expected_failure_message is not None: with pytest.raises(ImageValidationError) as ctx: validate_uploaded_image(uploaded_file) - assert text_type(ctx.value) == expected_failure_message + assert str(ctx.value) == expected_failure_message else: validate_uploaded_image(uploaded_file) assert uploaded_file.tell() == 0 @ddt.data( - (99, u"The file must be at least 100 bytes in size."), + (99, "The file must be at least 100 bytes in size."), (100, ), (1024, ), - (1025, u"The file must be smaller than 1 KB in size."), + (1025, "The file must be smaller than 1 KB in size."), ) @ddt.unpack @override_settings(PROFILE_IMAGE_MIN_BYTES=100, PROFILE_IMAGE_MAX_BYTES=1024) @@ -93,8 +92,8 @@ class TestValidateUploadedImage(TestCase): file data. """ file_upload_bad_ext = ( - u'The file name extension for this file does not match ' - u'the file data. The file may be corrupted.' + 'The file name extension for this file does not match ' + 'the file data. The file may be corrupted.' ) # make a bmp, try to fool the function into thinking it's a jpeg with make_image_file(extension=".bmp") as bmp_file: @@ -108,7 +107,7 @@ class TestValidateUploadedImage(TestCase): ) with pytest.raises(ImageValidationError) as ctx: validate_uploaded_image(uploaded_file) - assert text_type(ctx.value) == file_upload_bad_ext + assert str(ctx.value) == file_upload_bad_ext def test_content_type(self): """ @@ -116,13 +115,13 @@ class TestValidateUploadedImage(TestCase): extension do not match """ file_upload_bad_mimetype = ( - u'The Content-Type header for this file does not match ' - u'the file data. The file may be corrupted.' + 'The Content-Type header for this file does not match ' + 'the file data. The file may be corrupted.' ) with make_uploaded_file(extension=".jpeg", content_type="image/gif") as uploaded_file: with pytest.raises(ImageValidationError) as ctx: validate_uploaded_image(uploaded_file) - assert text_type(ctx.value) == file_upload_bad_mimetype + assert str(ctx.value) == file_upload_bad_mimetype @ddt.ddt diff --git a/openedx/core/djangoapps/profile_images/tests/test_views.py b/openedx/core/djangoapps/profile_images/tests/test_views.py index c53a00175c..cb43854aed 100644 --- a/openedx/core/djangoapps/profile_images/tests/test_views.py +++ b/openedx/core/djangoapps/profile_images/tests/test_views.py @@ -1,9 +1,11 @@ """ Test cases for the HTTP endpoints of the profile image api. """ +from contextlib import closing +from unittest import mock +from unittest.mock import patch import pytest -from contextlib import closing import datetime from pytz import UTC @@ -11,8 +13,6 @@ from django.urls import reverse from django.http import HttpResponse import ddt -import mock -from mock import patch from PIL import Image from rest_framework.test import APITestCase, APIClient @@ -44,7 +44,7 @@ class ProfileImageEndpointMixin(UserSettingsEventTestMixin): _view_name = None def setUp(self): - super(ProfileImageEndpointMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory.create(password=TEST_PASSWORD) # Ensure that parental controls don't apply to this user self.user.profile.year_of_birth = 1980 @@ -62,7 +62,7 @@ class ProfileImageEndpointMixin(UserSettingsEventTestMixin): self.reset_tracker() def tearDown(self): - super(ProfileImageEndpointMixin, self).tearDown() # lint-amnesty, pylint: disable=super-with-arguments + super().tearDown() for name in get_profile_image_names(self.user.username).values(): self.storage.delete(name) @@ -212,7 +212,7 @@ class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase): self.url, data, content_type=content_type, - HTTP_CONTENT_DISPOSITION='attachment;filename=filename{}'.format(extension), + HTTP_CONTENT_DISPOSITION=f'attachment;filename=filename{extension}', ) self.check_response(response, 204) self.check_images() @@ -297,8 +297,8 @@ class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase): response = self.client.post(self.url, {}, format='multipart') self.check_response( response, 400, - expected_developer_message=u"No file provided for profile image", - expected_user_message=u"No file provided for profile image", + expected_developer_message="No file provided for profile image", + expected_user_message="No file provided for profile image", ) self.check_images(False) self.check_has_profile_image(False) @@ -313,8 +313,8 @@ class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase): response = self.client.post(self.url, {'file': 'not a file'}, format='multipart') self.check_response( response, 400, - expected_developer_message=u"No file provided for profile image", - expected_user_message=u"No file provided for profile image", + expected_developer_message="No file provided for profile image", + expected_user_message="No file provided for profile image", ) self.check_images(False) self.check_has_profile_image(False) @@ -329,13 +329,13 @@ class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase): with make_image_file() as image_file: with mock.patch( 'openedx.core.djangoapps.profile_images.views.validate_uploaded_image', - side_effect=ImageValidationError(u"test error message") + side_effect=ImageValidationError("test error message") ): response = self.client.post(self.url, {'file': image_file}, format='multipart') self.check_response( response, 400, - expected_developer_message=u"test error message", - expected_user_message=u"test error message", + expected_developer_message="test error message", + expected_user_message="test error message", ) self.check_images(False) self.check_has_profile_image(False) @@ -348,7 +348,7 @@ class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase): Test that when upload validation fails, the proper HTTP response and messages are returned. """ - image_open.side_effect = [Exception(u"whoops"), None] + image_open.side_effect = [Exception("whoops"), None] with make_image_file() as image_file: with pytest.raises(Exception): self.client.post(self.url, {'file': image_file}, format='multipart') @@ -367,7 +367,7 @@ class ProfileImageViewDeleteTestCase(ProfileImageEndpointMixin, APITestCase): _view_name = "accounts_profile_image_api" def setUp(self): - super(ProfileImageViewDeleteTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() with make_image_file() as image_file: create_profile_images(image_file, get_profile_image_names(self.user.username)) self.check_images() @@ -447,7 +447,7 @@ class ProfileImageViewDeleteTestCase(ProfileImageEndpointMixin, APITestCase): Test that when remove validation fails, the proper HTTP response and messages are returned. """ - user_profile_save.side_effect = [Exception(u"whoops"), None] + user_profile_save.side_effect = [Exception("whoops"), None] with pytest.raises(Exception): self.client.delete(self.url) self.check_images(True) # thumbnails should remain intact. diff --git a/openedx/core/djangoapps/profile_images/views.py b/openedx/core/djangoapps/profile_images/views.py index eab3c3dabf..b20bce17e5 100644 --- a/openedx/core/djangoapps/profile_images/views.py +++ b/openedx/core/djangoapps/profile_images/views.py @@ -16,7 +16,6 @@ from rest_framework import permissions, status from rest_framework.parsers import FormParser, MultiPartParser from rest_framework.response import Response from rest_framework.views import APIView -from six import text_type from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_names, set_has_profile_image from openedx.core.djangoapps.user_api.errors import UserNotFound @@ -30,8 +29,8 @@ from .images import IMAGE_TYPES, create_profile_images, remove_profile_images, v log = logging.getLogger(__name__) -LOG_MESSAGE_CREATE = u'Generated and uploaded images %(image_names)s for user %(user_id)s' -LOG_MESSAGE_DELETE = u'Deleted images %(image_names)s for user %(user_id)s' +LOG_MESSAGE_CREATE = 'Generated and uploaded images %(image_names)s for user %(user_id)s' +LOG_MESSAGE_DELETE = 'Deleted images %(image_names)s for user %(user_id)s' def _make_upload_dt(): @@ -133,8 +132,8 @@ class ProfileImageView(DeveloperErrorViewMixin, APIView): if 'file' not in request.FILES: return Response( { - "developer_message": u"No file provided for profile image", - "user_message": _(u"No file provided for profile image"), + "developer_message": "No file provided for profile image", + "user_message": _("No file provided for profile image"), }, status=status.HTTP_400_BAD_REQUEST @@ -151,7 +150,7 @@ class ProfileImageView(DeveloperErrorViewMixin, APIView): validate_uploaded_image(uploaded_file) except ImageValidationError as error: return Response( - {"developer_message": text_type(error), "user_message": error.user_message}, + {"developer_message": str(error), "user_message": error.user_message}, status=status.HTTP_400_BAD_REQUEST, ) diff --git a/openedx/core/djangoapps/programs/apps.py b/openedx/core/djangoapps/programs/apps.py index 5d6ea1d9dc..7136834e9a 100644 --- a/openedx/core/djangoapps/programs/apps.py +++ b/openedx/core/djangoapps/programs/apps.py @@ -10,7 +10,7 @@ class ProgramsConfig(AppConfig): """ Default configuration for the "openedx.core.djangoapps.programs" Django application. """ - name = u'openedx.core.djangoapps.programs' + name = 'openedx.core.djangoapps.programs' def ready(self): # noinspection PyUnresolvedReferences diff --git a/openedx/core/djangoapps/programs/management/commands/backpopulate_program_credentials.py b/openedx/core/djangoapps/programs/management/commands/backpopulate_program_credentials.py index 8169dd4c53..d3ab2822ad 100644 --- a/openedx/core/djangoapps/programs/management/commands/backpopulate_program_credentials.py +++ b/openedx/core/djangoapps/programs/management/commands/backpopulate_program_credentials.py @@ -84,10 +84,10 @@ class Command(BaseCommand): self._load_usernames(users=usernames) if options.get('commit'): - logger.info(u'Enqueuing program certification tasks for %d candidates.', len(self.usernames)) + logger.info('Enqueuing program certification tasks for %d candidates.', len(self.usernames)) else: logger.info( - u'Found %d candidates. To enqueue program certification tasks, pass the -c or --commit flags.', + 'Found %d candidates. To enqueue program certification tasks, pass the -c or --commit flags.', len(self.usernames) ) return @@ -98,14 +98,14 @@ class Command(BaseCommand): award_program_certificates.delay(username) except: # pylint: disable=bare-except failed += 1 - logger.exception(u'Failed to enqueue task for user [%s]', username) + logger.exception('Failed to enqueue task for user [%s]', username) else: succeeded += 1 - logger.debug(u'Successfully enqueued task for user [%s]', username) + logger.debug('Successfully enqueued task for user [%s]', username) logger.info( - u'Done. Successfully enqueued tasks for %d candidates. ' - u'Failed to enqueue tasks for %d candidates.', + 'Done. Successfully enqueued tasks for %d candidates. ' + 'Failed to enqueue tasks for %d candidates.', succeeded, failed ) @@ -117,7 +117,7 @@ class Command(BaseCommand): programs.extend(get_programs(uuids=program_uuids)) else: for site in Site.objects.all(): - logger.info(u'Loading programs from the catalog for site %s.', site.domain) + logger.info('Loading programs from the catalog for site %s.', site.domain) programs.extend(get_programs(site)) self.course_runs = self._flatten(programs) diff --git a/openedx/core/djangoapps/programs/migrations/0001_initial.py b/openedx/core/djangoapps/programs/migrations/0001_initial.py index fdd6463ee0..0be3ac89fb 100644 --- a/openedx/core/djangoapps/programs/migrations/0001_initial.py +++ b/openedx/core/djangoapps/programs/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.db.models.deletion from django.conf import settings from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0002_programsapiconfig_cache_ttl.py b/openedx/core/djangoapps/programs/migrations/0002_programsapiconfig_cache_ttl.py index d397c50933..eb9b75bfc2 100644 --- a/openedx/core/djangoapps/programs/migrations/0002_programsapiconfig_cache_ttl.py +++ b/openedx/core/djangoapps/programs/migrations/0002_programsapiconfig_cache_ttl.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0003_auto_20151120_1613.py b/openedx/core/djangoapps/programs/migrations/0003_auto_20151120_1613.py index 7b545d87d1..3cb4f4ee4a 100644 --- a/openedx/core/djangoapps/programs/migrations/0003_auto_20151120_1613.py +++ b/openedx/core/djangoapps/programs/migrations/0003_auto_20151120_1613.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0004_programsapiconfig_enable_certification.py b/openedx/core/djangoapps/programs/migrations/0004_programsapiconfig_enable_certification.py index acf80ae487..0d10d786c5 100644 --- a/openedx/core/djangoapps/programs/migrations/0004_programsapiconfig_enable_certification.py +++ b/openedx/core/djangoapps/programs/migrations/0004_programsapiconfig_enable_certification.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0005_programsapiconfig_max_retries.py b/openedx/core/djangoapps/programs/migrations/0005_programsapiconfig_max_retries.py index 9fec18599f..b3eca5b692 100644 --- a/openedx/core/djangoapps/programs/migrations/0005_programsapiconfig_max_retries.py +++ b/openedx/core/djangoapps/programs/migrations/0005_programsapiconfig_max_retries.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0006_programsapiconfig_xseries_ad_enabled.py b/openedx/core/djangoapps/programs/migrations/0006_programsapiconfig_xseries_ad_enabled.py index 54344f6e28..29cf740812 100644 --- a/openedx/core/djangoapps/programs/migrations/0006_programsapiconfig_xseries_ad_enabled.py +++ b/openedx/core/djangoapps/programs/migrations/0006_programsapiconfig_xseries_ad_enabled.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0007_programsapiconfig_program_listing_enabled.py b/openedx/core/djangoapps/programs/migrations/0007_programsapiconfig_program_listing_enabled.py index 7cbfbdcb5b..c9024c4511 100644 --- a/openedx/core/djangoapps/programs/migrations/0007_programsapiconfig_program_listing_enabled.py +++ b/openedx/core/djangoapps/programs/migrations/0007_programsapiconfig_program_listing_enabled.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0008_programsapiconfig_program_details_enabled.py b/openedx/core/djangoapps/programs/migrations/0008_programsapiconfig_program_details_enabled.py index 39388fc536..37e0881d80 100644 --- a/openedx/core/djangoapps/programs/migrations/0008_programsapiconfig_program_details_enabled.py +++ b/openedx/core/djangoapps/programs/migrations/0008_programsapiconfig_program_details_enabled.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0009_programsapiconfig_marketing_path.py b/openedx/core/djangoapps/programs/migrations/0009_programsapiconfig_marketing_path.py index b650546752..bace1cd5b7 100644 --- a/openedx/core/djangoapps/programs/migrations/0009_programsapiconfig_marketing_path.py +++ b/openedx/core/djangoapps/programs/migrations/0009_programsapiconfig_marketing_path.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0010_auto_20170204_2332.py b/openedx/core/djangoapps/programs/migrations/0010_auto_20170204_2332.py index 108e50c1cc..c8be14e824 100644 --- a/openedx/core/djangoapps/programs/migrations/0010_auto_20170204_2332.py +++ b/openedx/core/djangoapps/programs/migrations/0010_auto_20170204_2332.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0011_auto_20170301_1844.py b/openedx/core/djangoapps/programs/migrations/0011_auto_20170301_1844.py index 5bce27b2f5..ccdb62159b 100644 --- a/openedx/core/djangoapps/programs/migrations/0011_auto_20170301_1844.py +++ b/openedx/core/djangoapps/programs/migrations/0011_auto_20170301_1844.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0012_auto_20170419_0018.py b/openedx/core/djangoapps/programs/migrations/0012_auto_20170419_0018.py index e1d1384319..1f7eac0d37 100644 --- a/openedx/core/djangoapps/programs/migrations/0012_auto_20170419_0018.py +++ b/openedx/core/djangoapps/programs/migrations/0012_auto_20170419_0018.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/openedx/core/djangoapps/programs/migrations/0013_customprogramsconfig.py b/openedx/core/djangoapps/programs/migrations/0013_customprogramsconfig.py index 51164f023b..e162f53393 100644 --- a/openedx/core/djangoapps/programs/migrations/0013_customprogramsconfig.py +++ b/openedx/core/djangoapps/programs/migrations/0013_customprogramsconfig.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.26 on 2019-12-13 07:44 diff --git a/openedx/core/djangoapps/programs/models.py b/openedx/core/djangoapps/programs/models.py index 0687938a3a..4917364bef 100644 --- a/openedx/core/djangoapps/programs/models.py +++ b/openedx/core/djangoapps/programs/models.py @@ -1,7 +1,4 @@ """Models providing Programs support for the LMS and Studio.""" - - -import six from config_models.models import ConfigurationModel from django.db import models from django.utils.translation import ugettext_lazy as _ @@ -15,7 +12,7 @@ class ProgramsApiConfig(ConfigurationModel): .. no_pii: """ - class Meta(object): + class Meta: app_label = "programs" marketing_path = models.CharField( @@ -31,7 +28,7 @@ class CustomProgramsConfig(ConfigurationModel): # pylint: disable=model-missing """ Manages configuration for a run of the backpopulate_program_credentials management command. """ - class Meta(object): + class Meta: app_label = 'programs' verbose_name = 'backpopulate_program_credentials argument' @@ -42,4 +39,4 @@ class CustomProgramsConfig(ConfigurationModel): # pylint: disable=model-missing ) def __str__(self): - return six.text_type(self.arguments) + return str(self.arguments) diff --git a/openedx/core/djangoapps/programs/signals.py b/openedx/core/djangoapps/programs/signals.py index ce70ffa189..0ecb2f786e 100644 --- a/openedx/core/djangoapps/programs/signals.py +++ b/openedx/core/djangoapps/programs/signals.py @@ -51,7 +51,7 @@ def handle_course_cert_awarded(sender, user, course_key, mode, status, **kwargs) # schedule background task to process LOGGER.debug( - u'handling COURSE_CERT_AWARDED: username=%s, course_key=%s, mode=%s, status=%s', + 'handling COURSE_CERT_AWARDED: username=%s, course_key=%s, mode=%s, status=%s', user, course_key, mode, @@ -89,13 +89,13 @@ def handle_course_cert_changed(sender, user, course_key, mode, status, **kwargs) verbose = kwargs.get('verbose', False) if verbose: - msg = u"Starting handle_course_cert_changed with params: "\ - u"sender [{sender}], "\ - u"user [{username}], "\ - u"course_key [{course_key}], "\ - u"mode [{mode}], "\ - u"status [{status}], "\ - u"kwargs [{kw}]"\ + msg = "Starting handle_course_cert_changed with params: "\ + "sender [{sender}], "\ + "user [{username}], "\ + "course_key [{course_key}], "\ + "mode [{mode}], "\ + "status [{status}], "\ + "kwargs [{kw}]"\ .format( sender=sender, username=getattr(user, 'username', None), @@ -118,7 +118,7 @@ def handle_course_cert_changed(sender, user, course_key, mode, status, **kwargs) if not is_learner_records_enabled_for_org(course_key.org): if verbose: LOGGER.info( - u"Skipping send cert: ENABLE_LEARNER_RECORDS False for org [{org}]".format( + "Skipping send cert: ENABLE_LEARNER_RECORDS False for org [{org}]".format( org=course_key.org ) ) @@ -126,7 +126,7 @@ def handle_course_cert_changed(sender, user, course_key, mode, status, **kwargs) # schedule background task to process LOGGER.debug( - u'handling COURSE_CERT_CHANGED: username=%s, course_key=%s, mode=%s, status=%s', + 'handling COURSE_CERT_CHANGED: username=%s, course_key=%s, mode=%s, status=%s', user, course_key, mode, @@ -169,7 +169,7 @@ def handle_course_cert_revoked(sender, user, course_key, mode, status, **kwargs) # schedule background task to process LOGGER.info( - u'handling COURSE_CERT_REVOKED: username=%s, course_key=%s, mode=%s, status=%s', + 'handling COURSE_CERT_REVOKED: username=%s, course_key=%s, mode=%s, status=%s', user, course_key, mode, diff --git a/openedx/core/djangoapps/programs/tasks.py b/openedx/core/djangoapps/programs/tasks.py index e7ab399ae9..a955965ff0 100644 --- a/openedx/core/djangoapps/programs/tasks.py +++ b/openedx/core/djangoapps/programs/tasks.py @@ -366,7 +366,7 @@ def award_course_certificate(self, username, course_run_key, certificate_availab if certificate.mode in CourseMode.CERTIFICATE_RELEVANT_MODES: try: course_overview = CourseOverview.get_from_id(course_key) - except (CourseOverview.DoesNotExist, IOError): + except (CourseOverview.DoesNotExist, OSError): LOGGER.exception( f"Task award_course_certificate was called without course overview data for course {course_key}" ) diff --git a/openedx/core/djangoapps/programs/tests/factories.py b/openedx/core/djangoapps/programs/tests/factories.py index 37a54aaa66..837eed0633 100644 --- a/openedx/core/djangoapps/programs/tests/factories.py +++ b/openedx/core/djangoapps/programs/tests/factories.py @@ -6,7 +6,7 @@ import factory class ProgressFactory(factory.Factory): - class Meta(object): + class Meta: model = dict uuid = factory.Faker('uuid4') diff --git a/openedx/core/djangoapps/programs/tests/mixins.py b/openedx/core/djangoapps/programs/tests/mixins.py index 7c67d7f636..12f4fff905 100644 --- a/openedx/core/djangoapps/programs/tests/mixins.py +++ b/openedx/core/djangoapps/programs/tests/mixins.py @@ -4,7 +4,7 @@ from openedx.core.djangoapps.programs.models import ProgramsApiConfig -class ProgramsApiConfigMixin(object): +class ProgramsApiConfigMixin: """Utilities for working with Programs configuration during testing.""" DEFAULTS = { diff --git a/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py b/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py index f371c4f9a7..13fd4861f1 100644 --- a/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py +++ b/openedx/core/djangoapps/programs/tests/test_backpopulate_program_credentials.py @@ -1,12 +1,11 @@ """Tests for the backpopulate_program_credentials management command.""" +from unittest import mock import ddt -import mock from django.core.management import call_command from django.test import TestCase from opaque_keys.edx.keys import CourseKey -from six.moves import range from common.djangoapps.course_modes.models import CourseMode from lms.djangoapps.certificates.api import MODES @@ -42,7 +41,7 @@ class BackpopulateProgramCredentialsTests(CatalogIntegrationMixin, CredentialsAp SAME_COURSE = 'same_course' def setUp(self): - super(BackpopulateProgramCredentialsTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.alice = UserFactory() self.bob = UserFactory() diff --git a/openedx/core/djangoapps/programs/tests/test_signals.py b/openedx/core/djangoapps/programs/tests/test_signals.py index 67ef164e79..fe7c4c5122 100644 --- a/openedx/core/djangoapps/programs/tests/test_signals.py +++ b/openedx/core/djangoapps/programs/tests/test_signals.py @@ -3,7 +3,8 @@ This module contains tests for programs-related signals and signal handlers. """ import datetime -import mock +from unittest import mock + from django.test import TestCase from opaque_keys.edx.keys import CourseKey @@ -102,7 +103,7 @@ class CertChangedReceiverTest(TestCase): """ def setUp(self): - super(CertChangedReceiverTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory.create(username=TEST_USERNAME) @property diff --git a/openedx/core/djangoapps/programs/tests/test_tasks.py b/openedx/core/djangoapps/programs/tests/test_tasks.py index 70256522e6..bcdbc55a88 100644 --- a/openedx/core/djangoapps/programs/tests/test_tasks.py +++ b/openedx/core/djangoapps/programs/tests/test_tasks.py @@ -6,10 +6,11 @@ Tests for programs celery tasks. import json import logging from datetime import datetime, timedelta +from unittest import mock + import pytest import ddt import httpretty -import mock import pytz from celery.exceptions import MaxRetriesExceededError from django.conf import settings @@ -125,7 +126,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo """ def setUp(self): - super(AwardProgramCertificatesTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.create_credentials_config() self.student = UserFactory.create(username='test-student') self.site = SiteFactory() @@ -228,7 +229,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo Checks that the task is aborted if any relevant api configs are disabled. """ - getattr(self, 'create_{}_config'.format(disabled_config_type))(**{disabled_config_attribute: False}) + getattr(self, f'create_{disabled_config_type}_config')(**{disabled_config_attribute: False}) with mock.patch(TASKS_MODULE + '.LOGGER.warning') as mock_warning: with pytest.raises(MaxRetriesExceededError): tasks.award_program_certificates.delay(self.student.username).get() @@ -348,7 +349,7 @@ class AwardProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiCo assert mock_award_program_certificate.call_count == 3 mock_warning.assert_called_once_with( - u'Failed to award certificate for program {uuid} to user {username}.'.format( + 'Failed to award certificate for program {uuid} to user {username}.'.format( uuid=1, username=self.student.username) ) @@ -512,7 +513,7 @@ class AwardCourseCertificatesTestCase(CredentialsApiConfigMixin, TestCase): """ def setUp(self): - super(AwardCourseCertificatesTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.available_date = datetime.now(pytz.UTC) + timedelta(days=1) self.course = CourseOverviewFactory.create( @@ -663,7 +664,7 @@ class RevokeProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiC """ def setUp(self): - super(RevokeProgramCertificatesTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.student = UserFactory.create(username='test-student') self.course_key = 'course-v1:testX+test101+2T2020' @@ -724,7 +725,7 @@ class RevokeProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiC Checks that the task is aborted if any relevant api configs are disabled. """ - getattr(self, 'create_{}_config'.format(disabled_config_type))(**{disabled_config_attribute: False}) + getattr(self, f'create_{disabled_config_type}_config')(**{disabled_config_attribute: False}) with mock.patch(TASKS_MODULE + '.LOGGER.warning') as mock_warning: with pytest.raises(MaxRetriesExceededError): tasks.revoke_program_certificates.delay(self.student.username, self.course_key).get() @@ -802,7 +803,7 @@ class RevokeProgramCertificatesTestCase(CatalogIntegrationMixin, CredentialsApiC assert mock_revoke_program_certificate.call_count == 3 mock_warning.assert_called_once_with( - u'Failed to revoke certificate for program {uuid} of user {username}.'.format( + 'Failed to revoke certificate for program {uuid} of user {username}.'.format( uuid=1, username=self.student.username) ) diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index ea630a4651..67a53411dc 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -6,13 +6,11 @@ import json import uuid from collections import namedtuple from copy import deepcopy +from unittest import mock import ddt from edx_toggles.toggles.testutils import override_waffle_switch import httpretty -import mock -import six -from six.moves import range from django.conf import settings from django.test import TestCase from django.test.utils import override_settings @@ -69,7 +67,7 @@ class TestProgramProgressMeter(TestCase): """Tests of the program progress utility class.""" def setUp(self): - super(TestProgramProgressMeter, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory() self.site = SiteFactory() @@ -651,8 +649,7 @@ class TestProgramProgressMeter(TestCase): self._create_certificates(unknown['key'], status='unknown') meter = ProgramProgressMeter(self.site, self.user) - six.assertCountEqual( - self, + self.assertCountEqual( meter.completed_course_runs, [ {'course_run_id': downloadable['key'], 'type': CourseMode.VERIFIED}, @@ -725,7 +722,7 @@ class TestProgramProgressMeter(TestCase): program_data = meter.engaged_programs[0] detail_fragment_url = reverse('program_details_fragment_view', kwargs={'program_uuid': program_data['uuid']}) path_id = detail_fragment_url.replace('/dashboard/', '') - expected_url = 'edxapp://enrolled_program_info?path_id={}'.format(path_id) + expected_url = f'edxapp://enrolled_program_info?path_id={path_id}' assert program_data['detail_url'] == expected_url @@ -746,7 +743,7 @@ def _create_course(self, course_price, course_run_count=1, make_entitlement=Fals course.instructor_info = self.instructors course = self.update_course(course, self.user.id) - run = CourseRunFactory(key=six.text_type(course.id), seats=[SeatFactory(price=course_price)]) + run = CourseRunFactory(key=str(course.id), seats=[SeatFactory(price=course_price)]) course_runs.append(run) entitlements = [EntitlementFactory()] if make_entitlement else [] @@ -775,14 +772,14 @@ class TestProgramDataExtender(ModuleStoreTestCase): } def setUp(self): - super(TestProgramDataExtender, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course = ModuleStoreCourseFactory() self.course.start = datetime.datetime.now(utc) - datetime.timedelta(days=1) self.course.end = datetime.datetime.now(utc) + datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) - self.course_run = CourseRunFactory(key=six.text_type(self.course.id)) + self.course_run = CourseRunFactory(key=str(self.course.id)) self.catalog_course = CourseFactory(course_runs=[self.course_run]) self.program = ProgramFactory(courses=[self.catalog_course]) self.course_price = 100 @@ -1081,7 +1078,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): """ course1 = _create_course(self, self.course_price, course_run_count=2, make_entitlement=True) course2 = _create_course(self, self.course_price, course_run_count=2, make_entitlement=True) - expected_skus = set([course1['entitlements'][0]['sku'], course2['entitlements'][0]['sku']]) + expected_skus = {course1['entitlements'][0]['sku'], course2['entitlements'][0]['sku']} program = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, @@ -1114,7 +1111,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): course1 = _create_course(self, self.course_price, course_run_count=2, make_entitlement=True) course2 = _create_course(self, self.course_price, course_run_count=2, make_entitlement=True) CourseEntitlementFactory(user=self.user, course_uuid=course1['uuid'], mode=CourseMode.VERIFIED) - expected_skus = set([course2['entitlements'][0]['sku']]) + expected_skus = {course2['entitlements'][0]['sku']} program = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, @@ -1155,7 +1152,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): ProgramDataExtender(program_data, self.user).extend() logger.check( (LOGGER_NAME, - 'WARNING', u'Failed to get course overview for course run key: {}'.format(course_run.get('key'))) + 'WARNING', 'Failed to get course overview for course run key: {}'.format(course_run.get('key'))) ) def test_entitlement_product_wrong_mode(self): @@ -1185,7 +1182,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): # The above statement makes a verified entitlement for the course, which is an applicable seat type # and the statement below makes a professional entitlement for the same course, which is not applicable course2['entitlements'].append(EntitlementFactory(mode=CourseMode.PROFESSIONAL)) - expected_skus = set([course1['course_runs'][0]['seats'][0]['sku'], course2['entitlements'][0]['sku']]) + expected_skus = {course1['course_runs'][0]['seats'][0]['sku'], course2['entitlements'][0]['sku']} program = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, @@ -1202,7 +1199,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): """ course1 = _create_course(self, self.course_price, make_entitlement=True) course2 = _create_course(self, self.course_price) - expected_skus = set([course2['course_runs'][0]['seats'][0]['sku']]) + expected_skus = {course2['course_runs'][0]['seats'][0]['sku']} CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode=CourseMode.VERIFIED) program = ProgramFactory( courses=[course1, course2], @@ -1221,7 +1218,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): course1 = _create_course(self, self.course_price, course_run_count=2) course2 = _create_course(self, self.course_price, course_run_count=2, make_entitlement=True) CourseEnrollmentFactory(user=self.user, course_id=course1['course_runs'][0]['key'], mode=CourseMode.VERIFIED) - expected_skus = set([course2['entitlements'][0]['sku']]) + expected_skus = {course2['entitlements'][0]['sku']} program = ProgramFactory( courses=[course1, course2], is_program_eligible_for_one_click_purchase=True, @@ -1236,7 +1233,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): Verify that correct course url is returned for mobile. """ data = ProgramDataExtender(self.program, self.user, mobile_only=True).extend() - expected_course_url = 'edxapp://enrolled_course_info?course_id={}'.format(self.course.id) + expected_course_url = f'edxapp://enrolled_course_info?course_id={self.course.id}' self._assert_supplemented(data, course_url=expected_course_url) @@ -1247,7 +1244,7 @@ class TestGetCertificates(TestCase): Tests of the function used to get certificates associated with a program. """ def setUp(self): - super(TestGetCertificates, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory() self.program = ProgramFactory() @@ -1351,7 +1348,7 @@ class TestGetCertificates(TestCase): @skip_unless_lms class TestProgramMarketingDataExtender(ModuleStoreTestCase): """Tests of the program data extender utility class.""" - ECOMMERCE_CALCULATE_DISCOUNT_ENDPOINT = '{root}/api/v2/baskets/calculate/'.format(root=ECOMMERCE_URL_ROOT) + ECOMMERCE_CALCULATE_DISCOUNT_ENDPOINT = f'{ECOMMERCE_URL_ROOT}/api/v2/baskets/calculate/' instructors = { 'instructors': [ { @@ -1366,7 +1363,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase): } def setUp(self): - super(TestProgramMarketingDataExtender, 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) @@ -1424,7 +1421,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase): for __ in range(3): course = ModuleStoreCourseFactory() course = self.update_course(course, self.user.id) - course_runs.append(CourseRunFactory(key=six.text_type(course.id), seats=[])) + course_runs.append(CourseRunFactory(key=str(course.id), seats=[])) program = ProgramFactory(courses=[CourseFactory(course_runs=course_runs)]) data = ProgramMarketingDataExtender(program, self.user).extend() @@ -1492,7 +1489,7 @@ class TestProgramMarketingDataExtender(ModuleStoreTestCase): content_type='application/json' ) ProgramMarketingDataExtender(self.program, self.user).extend() - assert httpretty.last_request().querystring.get('is_anonymous')[0] == u'True' # lint-amnesty, pylint: disable=no-member, line-too-long + assert httpretty.last_request().querystring.get('is_anonymous')[0] == 'True' # lint-amnesty, pylint: disable=no-member, line-too-long @httpretty.activate def test_fetching_program_discounted_price_as_anonymous_user(self): diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index 9b1e3560ab..a50ed6a0c4 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """Helper functions for working with Programs.""" @@ -7,8 +6,8 @@ import logging from collections import defaultdict from copy import deepcopy from itertools import chain +from urllib.parse import urljoin, urlparse, urlunparse -import six from dateutil.parser import parse from django.conf import settings from django.contrib.auth import get_user_model @@ -20,7 +19,6 @@ from edx_rest_api_client.exceptions import SlumberBaseException from opaque_keys.edx.keys import CourseKey from pytz import utc from requests.exceptions import ConnectionError, Timeout # lint-amnesty, pylint: disable=redefined-builtin -from six.moves.urllib.parse import urljoin, urlparse, urlunparse # pylint: disable=import-error from common.djangoapps.course_modes.api import get_paid_modes_for_course from common.djangoapps.course_modes.models import CourseMode @@ -77,7 +75,7 @@ def attach_program_detail_url(programs, mobile_only=False): if mobile_only: detail_fragment_url = reverse('program_details_fragment_view', kwargs={'program_uuid': program['uuid']}) path_id = detail_fragment_url.replace('/dashboard/', '') - detail_url = 'edxapp://enrolled_program_info?path_id={path_id}'.format(path_id=path_id) + detail_url = f'edxapp://enrolled_program_info?path_id={path_id}' else: detail_url = reverse('program_details_view', kwargs={'program_uuid': program['uuid']}) @@ -86,7 +84,7 @@ def attach_program_detail_url(programs, mobile_only=False): return programs -class ProgramProgressMeter(object): +class ProgramProgressMeter: """Utility for gauging a user's progress towards program completion. Arguments: @@ -110,7 +108,7 @@ class ProgramProgressMeter(object): self.course_run_ids = [] for enrollment in self.enrollments: # enrollment.course_id is really a CourseKey (╯ಠ_ಠ)╯︵ ┻━┻ - enrollment_id = six.text_type(enrollment.course_id) + enrollment_id = str(enrollment.course_id) mode = enrollment.mode if mode == CourseMode.NO_ID_PROFESSIONAL_MODE: mode = CourseMode.PROFESSIONAL @@ -153,7 +151,7 @@ class ProgramProgressMeter(object): program_list.append(program) # Sort programs by title for consistent presentation. - for program_list in six.itervalues(inverted_programs): + for program_list in inverted_programs.values(): program_list.sort(key=lambda p: p['title']) return inverted_programs @@ -436,7 +434,7 @@ class ProgramProgressMeter(object): completed_runs, failed_runs = [], [] for certificate in course_run_certificates: course_data = { - 'course_run_id': six.text_type(certificate['course_key']), + 'course_run_id': str(certificate['course_key']), 'type': self._certificate_mode_translation(certificate['type']), } @@ -463,7 +461,7 @@ class ProgramProgressMeter(object): # pylint: disable=missing-docstring -class ProgramDataExtender(object): +class ProgramDataExtender: """ Utility for extending program data meant for the program detail page with user-specific (e.g., CourseEnrollment) data. @@ -509,7 +507,7 @@ class ProgramDataExtender(object): try: self.course_overview = CourseOverview.get_from_id(self.course_run_key) except CourseOverview.DoesNotExist: - log.warning(u'Failed to get course overview for course run key: %s', course_run.get('key')) + log.warning('Failed to get course overview for course run key: %s', course_run.get('key')) else: self.enrollment_start = self.course_overview.enrollment_start or DEFAULT_ENROLLMENT_START_DATE @@ -592,7 +590,7 @@ class ProgramDataExtender(object): Returns: A subset of the given list of course dicts """ - course_uuids = set(course['uuid'] for course in courses) + course_uuids = {course['uuid'] for course in courses} # Filter the entitlements' modes with a case-insensitive match against applicable seat_types entitlements = self.user.courseentitlement_set.filter( mode__in=self.data['applicable_seat_types'], @@ -601,7 +599,7 @@ class ProgramDataExtender(object): # Here we check the entitlements' expired_at_datetime property rather than filter by the expired_at attribute # to ensure that the expiration status is as up to date as possible entitlements = [e for e in entitlements if not e.expired_at_datetime] - courses_with_entitlements = set(six.text_type(entitlement.course_uuid) for entitlement in entitlements) + courses_with_entitlements = {str(entitlement.course_uuid) for entitlement in entitlements} return [course for course in courses if course['uuid'] not in courses_with_entitlements] def _filter_out_courses_with_enrollments(self, courses): @@ -619,10 +617,10 @@ class ProgramDataExtender(object): is_active=True, mode__in=self.data['applicable_seat_types'] ) - course_runs_with_enrollments = set(six.text_type(enrollment.course_id) for enrollment in enrollments) + course_runs_with_enrollments = {str(enrollment.course_id) for enrollment in enrollments} courses_without_enrollments = [] for course in courses: - if all(six.text_type(run['key']) not in course_runs_with_enrollments for run in course['course_runs']): + if all(str(run['key']) not in course_runs_with_enrollments for run in course['course_runs']): courses_without_enrollments.append(course) return courses_without_enrollments @@ -634,7 +632,7 @@ class ProgramDataExtender(object): """ if 'professional' in self.data['applicable_seat_types']: self.data['applicable_seat_types'].append('no-id-professional') - applicable_seat_types = set(seat for seat in self.data['applicable_seat_types'] if seat != 'credit') + applicable_seat_types = {seat for seat in self.data['applicable_seat_types'] if seat != 'credit'} is_learner_eligible_for_one_click_purchase = self.data['is_program_eligible_for_one_click_purchase'] bundle_uuid = self.data.get('uuid') @@ -712,7 +710,7 @@ class ProgramDataExtender(object): 'variant': bundle_variant }) except (ConnectionError, SlumberBaseException, Timeout): - log.exception(u'Failed to get discount price for following product SKUs: %s ', ', '.join(skus)) + log.exception('Failed to get discount price for following product SKUs: %s ', ', '.join(skus)) self.data.update({ 'discount_data': {'is_discounted': False} }) @@ -791,7 +789,7 @@ class ProgramMarketingDataExtender(ProgramDataExtender): user (User): The user whose enrollments to inspect. """ def __init__(self, program_data, user): - super(ProgramMarketingDataExtender, self).__init__(program_data, user) # lint-amnesty, pylint: disable=super-with-arguments + super().__init__(program_data, user) # Aggregate list of instructors for the program keyed by name self.instructors = [] @@ -843,7 +841,7 @@ class ProgramMarketingDataExtender(ProgramDataExtender): def extend(self): """Execute extension handlers, returning the extended data.""" - self.data.update(super(ProgramMarketingDataExtender, self).extend()) # lint-amnesty, pylint: disable=super-with-arguments + self.data.update(super().extend()) return self.data @classmethod @@ -869,7 +867,7 @@ class ProgramMarketingDataExtender(ProgramDataExtender): def _attach_course_run_upgrade_url(self, run_mode): if not self.user.is_anonymous: - super(ProgramMarketingDataExtender, self)._attach_course_run_upgrade_url(run_mode) # lint-amnesty, pylint: disable=super-with-arguments + super()._attach_course_run_upgrade_url(run_mode) else: run_mode['upgrade_url'] = None diff --git a/openedx/core/djangoapps/safe_sessions/middleware.py b/openedx/core/djangoapps/safe_sessions/middleware.py index ddd21dfb42..a290745c4c 100644 --- a/openedx/core/djangoapps/safe_sessions/middleware.py +++ b/openedx/core/djangoapps/safe_sessions/middleware.py @@ -66,7 +66,6 @@ from contextlib import contextmanager from hashlib import sha256 from logging import ERROR, getLogger -import six from django.conf import settings from django.contrib.auth import SESSION_KEY from django.contrib.auth.views import redirect_to_login @@ -78,8 +77,6 @@ from django.utils.deprecation import MiddlewareMixin from django.utils.encoding import python_2_unicode_compatible from edx_django_utils.monitoring import set_custom_attribute -from six import text_type # pylint: disable=ungrouped-imports - from openedx.core.lib.mobile_utils import is_request_from_mobile_app log = getLogger(__name__) @@ -90,19 +87,19 @@ class SafeCookieError(Exception): An exception class for safe cookie related errors. """ def __init__(self, error_message): - super(SafeCookieError, self).__init__(error_message) # lint-amnesty, pylint: disable=super-with-arguments + super().__init__(error_message) log.error(error_message) @python_2_unicode_compatible -class SafeCookieData(object): +class SafeCookieData: """ Cookie data that cryptographically binds and timestamps the user to the session id. It verifies the freshness of the cookie by checking its creation date using settings.SESSION_COOKIE_AGE. """ CURRENT_VERSION = '1' - SEPARATOR = u"|" + SEPARATOR = "|" def __init__(self, version, session_id, key_salt, signature): """ @@ -154,16 +151,16 @@ class SafeCookieData(object): safe_cookie_string. """ try: - raw_cookie_components = six.text_type(safe_cookie_string).split(cls.SEPARATOR) + raw_cookie_components = str(safe_cookie_string).split(cls.SEPARATOR) safe_cookie_data = SafeCookieData(*raw_cookie_components) except TypeError: raise SafeCookieError( # lint-amnesty, pylint: disable=raise-missing-from - u"SafeCookieData BWC parse error: {0!r}.".format(safe_cookie_string) + f"SafeCookieData BWC parse error: {safe_cookie_string!r}." ) else: if safe_cookie_data.version != cls.CURRENT_VERSION: raise SafeCookieError( - u"SafeCookieData version {0!r} is not supported. Current version is {1}.".format( + "SafeCookieData version {!r} is not supported. Current version is {}.".format( safe_cookie_data.version, cls.CURRENT_VERSION, )) @@ -194,12 +191,12 @@ class SafeCookieData(object): unsigned_data = signing.loads(self.signature, salt=self.key_salt, max_age=settings.SESSION_COOKIE_AGE) if unsigned_data == self._compute_digest(user_id): return True - log.error(u"SafeCookieData '%r' is not bound to user '%s'.", six.text_type(self), user_id) + log.error("SafeCookieData '%r' is not bound to user '%s'.", str(self), user_id) except signing.BadSignature as sig_error: log.error( - u"SafeCookieData signature error for cookie data {0!r}: {1}".format( # pylint: disable=logging-format-interpolation - six.text_type(self), - text_type(sig_error), + "SafeCookieData signature error for cookie data {!r}: {}".format( # pylint: disable=logging-format-interpolation + str(self), + str(sig_error), ) ) return False @@ -210,8 +207,8 @@ class SafeCookieData(object): """ hash_func = sha256() for data_item in [self.version, self.session_id, user_id]: - hash_func.update(six.b(six.text_type(data_item))) - hash_func.update(six.b('|')) + hash_func.update(str(data_item).encode()) + hash_func.update(b'|') return hash_func.hexdigest() @staticmethod @@ -224,10 +221,10 @@ class SafeCookieData(object): # Compare against unicode(None) as well since the 'value' # property of a cookie automatically serializes None to a # string. - if not session_id or session_id == six.text_type(None): + if not session_id or session_id == str(None): # The session ID should always be valid in the cookie. raise SafeCookieError( - u"SafeCookieData not created due to invalid value for session_id '{}' for user_id '{}'.".format( + "SafeCookieData not created due to invalid value for session_id '{}' for user_id '{}'.".format( session_id, user_id, )) @@ -238,7 +235,7 @@ class SafeCookieData(object): # as some of the session requests are made as # Anonymous users. log.debug( - u"SafeCookieData received empty user_id '%s' for session_id '%s'.", + "SafeCookieData received empty user_id '%s' for session_id '%s'.", user_id, session_id, ) @@ -287,7 +284,7 @@ class SafeSessionMiddleware(SessionMiddleware, MiddlewareMixin): else: request.COOKIES[settings.SESSION_COOKIE_NAME] = safe_cookie_data.session_id # Step 2 - process_request_response = super(SafeSessionMiddleware, self).process_request(request) # Step 3 # lint-amnesty, pylint: disable=assignment-from-no-return, super-with-arguments + process_request_response = super().process_request(request) # Step 3 # lint-amnesty, pylint: disable=assignment-from-no-return, super-with-arguments if process_request_response: # The process_request pipeline has been short circuited so # return the response. @@ -326,7 +323,7 @@ class SafeSessionMiddleware(SessionMiddleware, MiddlewareMixin): Step 4. Delete the cookie, if it's marked for deletion. """ - response = super(SafeSessionMiddleware, self).process_response(request, response) # Step 1 # lint-amnesty, pylint: disable=super-with-arguments + response = super().process_response(request, response) # Step 1 if not _is_cookie_marked_for_deletion(request) and _is_cookie_present(response): try: @@ -441,7 +438,7 @@ class SafeSessionMiddleware(SessionMiddleware, MiddlewareMixin): ) # Update the cookie's value with the safe_cookie_data. - cookies[settings.SESSION_COOKIE_NAME] = six.text_type(safe_cookie_data) + cookies[settings.SESSION_COOKIE_NAME] = str(safe_cookie_data) def _mark_cookie_for_deletion(request): @@ -488,14 +485,14 @@ def _delete_cookie(request, response): # malicious gets directly dumped into the log. cookie_header = request.META.get('HTTP_COOKIE', '')[:4096] log.warning( - u"Malformed Cookie Header? First 4K, in Base64: %s", - b64encode(six.b(cookie_header)) + "Malformed Cookie Header? First 4K, in Base64: %s", + b64encode(str(cookie_header).encode()) ) # Note, there is no request.user attribute at this point. if hasattr(request, 'session') and hasattr(request.session, 'session_key'): log.warning( - u"SafeCookieData deleted session cookie for session %s", + "SafeCookieData deleted session cookie for session %s", request.session.session_key ) diff --git a/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py b/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py index f24259246e..d58f8f3832 100644 --- a/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py +++ b/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py @@ -2,9 +2,9 @@ Unit tests for SafeSessionMiddleware """ +from unittest.mock import patch import ddt -import six from crum import set_current_request from django.conf import settings from django.contrib.auth import SESSION_KEY @@ -12,7 +12,6 @@ from django.contrib.auth.models import AnonymousUser from django.http import HttpResponse, HttpResponseRedirect, SimpleCookie from django.test import TestCase from django.test.utils import override_settings -from mock import patch from openedx.core.djangolib.testing.utils import get_mock_request from common.djangoapps.student.tests.factories import UserFactory @@ -27,7 +26,7 @@ class TestSafeSessionProcessRequest(TestSafeSessionsLogMixin, TestCase): """ def setUp(self): - super(TestSafeSessionProcessRequest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory.create() self.addCleanup(set_current_request, None) self.request = get_mock_request() @@ -44,7 +43,7 @@ class TestSafeSessionProcessRequest(TestSafeSessionsLogMixin, TestCase): Else, verifies a failed response with an HTTP redirect. """ if safe_cookie_data: - self.request.COOKIES[settings.SESSION_COOKIE_NAME] = six.text_type(safe_cookie_data) + self.request.COOKIES[settings.SESSION_COOKIE_NAME] = str(safe_cookie_data) response = SafeSessionMiddleware().process_request(self.request) if success: assert response is None @@ -127,7 +126,7 @@ class TestSafeSessionProcessResponse(TestSafeSessionsLogMixin, TestCase): """ def setUp(self): - super(TestSafeSessionProcessResponse, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory.create() self.addCleanup(set_current_request, None) self.request = get_mock_request() @@ -232,7 +231,7 @@ class TestSafeSessionMiddleware(TestSafeSessionsLogMixin, TestCase): """ def setUp(self): - super(TestSafeSessionMiddleware, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory.create() self.addCleanup(set_current_request, None) self.request = get_mock_request() @@ -258,7 +257,7 @@ class TestSafeSessionMiddleware(TestSafeSessionsLogMixin, TestCase): session_id = self.client.session.session_key safe_cookie_data = SafeCookieData.create(session_id, self.user.id) - self.request.COOKIES[settings.SESSION_COOKIE_NAME] = six.text_type(safe_cookie_data) + self.request.COOKIES[settings.SESSION_COOKIE_NAME] = str(safe_cookie_data) with self.assert_not_logged(): response = SafeSessionMiddleware().process_request(self.request) diff --git a/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py b/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py index ae79bce646..a34fdc71a3 100644 --- a/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py +++ b/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py @@ -6,12 +6,11 @@ Unit tests for SafeCookieData import itertools from time import time +from unittest.mock import patch + import pytest import ddt -import six from django.test import TestCase -from mock import patch -from six.moves import range # pylint: disable=ungrouped-imports from ..middleware import SafeCookieData, SafeCookieError from .test_utils import TestSafeSessionsLogMixin @@ -24,7 +23,7 @@ class TestSafeCookieData(TestSafeSessionsLogMixin, TestCase): """ def setUp(self): - super(TestSafeCookieData, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.session_id = 'test_session_id' self.user_id = 'test_user_id' self.safe_cookie_data = SafeCookieData.create(self.session_id, self.user_id) @@ -51,7 +50,7 @@ class TestSafeCookieData(TestSafeSessionsLogMixin, TestCase): assert safe_cookie_data_1.verify(user_id) # serialize - serialized_value = six.text_type(safe_cookie_data_1) + serialized_value = str(safe_cookie_data_1) # parse and verify safe_cookie_data_2 = SafeCookieData.parse(serialized_value) @@ -64,9 +63,9 @@ class TestSafeCookieData(TestSafeSessionsLogMixin, TestCase): assert self.safe_cookie_data.version == SafeCookieData.CURRENT_VERSION def test_serialize(self): - serialized_value = six.text_type(self.safe_cookie_data) - for field_value in six.itervalues(self.safe_cookie_data.__dict__): - assert six.text_type(field_value) in serialized_value + serialized_value = str(self.safe_cookie_data) + for field_value in self.safe_cookie_data.__dict__.values(): + assert str(field_value) in serialized_value #---- Test Parse ----# @@ -78,7 +77,7 @@ class TestSafeCookieData(TestSafeSessionsLogMixin, TestCase): ) def test_parse_success_serialized(self): - serialized_value = six.text_type(self.safe_cookie_data) + serialized_value = str(self.safe_cookie_data) self.assert_cookie_data_equal( SafeCookieData.parse(serialized_value), self.safe_cookie_data, @@ -92,7 +91,7 @@ class TestSafeCookieData(TestSafeSessionsLogMixin, TestCase): @ddt.data(0, 2, -1, 'invalid_version') def test_parse_invalid_version(self, version): - serialized_value = '{}|session_id|key_salt|signature'.format(version) + serialized_value = f'{version}|session_id|key_salt|signature' with self.assert_logged(r"SafeCookieData version .* is not supported."): with pytest.raises(SafeCookieError): SafeCookieData.parse(serialized_value) diff --git a/openedx/core/djangoapps/safe_sessions/tests/test_utils.py b/openedx/core/djangoapps/safe_sessions/tests/test_utils.py index 529c9efe46..7c722f2a17 100644 --- a/openedx/core/djangoapps/safe_sessions/tests/test_utils.py +++ b/openedx/core/djangoapps/safe_sessions/tests/test_utils.py @@ -4,11 +4,10 @@ Shared test utilities for Safe Sessions tests from contextlib import contextmanager - -from mock import patch +from unittest.mock import patch -class TestSafeSessionsLogMixin(object): +class TestSafeSessionsLogMixin: """ Test Mixin class with helpers for testing log method calls in the safe sessions middleware.