diff --git a/lms/djangoapps/mailing/management/commands/mailchimp_id.py b/lms/djangoapps/mailing/management/commands/mailchimp_id.py index ca30454dd7..f8b195b400 100644 --- a/lms/djangoapps/mailing/management/commands/mailchimp_id.py +++ b/lms/djangoapps/mailing/management/commands/mailchimp_id.py @@ -42,8 +42,8 @@ class Command(BaseCommand): list_with_id = by_web_id.get(web_id, None) if list_with_id: - print(u"id: {} for web_id: {}".format(list_with_id['id'], web_id)) - print(u"list name: {}".format(list_with_id['name'])) + print("id: {} for web_id: {}".format(list_with_id['id'], web_id)) + print("list name: {}".format(list_with_id['name'])) else: - print(u"list with web_id: {} not found.".format(web_id)) + print(f"list with web_id: {web_id} not found.") sys.exit(1) diff --git a/lms/djangoapps/mailing/management/commands/mailchimp_sync_course.py b/lms/djangoapps/mailing/management/commands/mailchimp_sync_course.py index 3157ad62c9..2a33945d06 100644 --- a/lms/djangoapps/mailing/management/commands/mailchimp_sync_course.py +++ b/lms/djangoapps/mailing/management/commands/mailchimp_sync_course.py @@ -10,8 +10,6 @@ import random from collections import namedtuple from itertools import chain -import six -from six.moves import range from django.core.management.base import BaseCommand from mailsnake import MailSnake from opaque_keys.edx.keys import CourseKey @@ -59,7 +57,7 @@ class Command(BaseCommand): course_id = options['course_id'] num_segments = options['num_segments'] - log.info(u'Syncronizing email list for %s', course_id) + log.info('Syncronizing email list for %s', course_id) mailchimp = connect_mailchimp(key) @@ -83,7 +81,7 @@ class Command(BaseCommand): unsubscribe(mailchimp, list_id, non_enrolled_emails) - subscribed = subscribed.union(set(d['EMAIL'] for d in to_subscribe)) + subscribed = subscribed.union({d['EMAIL'] for d in to_subscribe}) make_segments(mailchimp, list_id, num_segments, subscribed) @@ -111,7 +109,7 @@ def verify_list(mailchimp, list_id, course_id): list_name = lists[0]['name'] - log.debug(u'list name: %s', list_name) + log.debug('list name: %s', list_name) # check that we are connecting to the correct list parts = course_id.replace('_', ' ').replace('/', ' ').split() @@ -246,7 +244,7 @@ def update_merge_tags(mailchimp, list_id, tag_names): The purpose of this function is unclear. """ mc_vars = mailchimp.listMergeVars(id=list_id) - mc_names = set(v['name'] for v in mc_vars) + mc_names = {v['name'] for v in mc_vars} mc_merge = mailchimp.listMergeVarAdd @@ -288,7 +286,7 @@ def subscribe_with_data(mailchimp, list_id, user_data): Returns None """ - format_entry = lambda e: {name_to_tag(k): v for k, v in six.iteritems(e)} + format_entry = lambda e: {name_to_tag(k): v for k, v in e.items()} formated_data = list(format_entry(e) for e in user_data) # send the updates in batches of a fixed size @@ -299,7 +297,7 @@ def subscribe_with_data(mailchimp, list_id, user_data): update_existing=True) log.debug( - u"Added: %s Error on: %s", result['add_count'], result['error_count'] + "Added: %s Error on: %s", result['add_count'], result['error_count'] ) @@ -334,7 +332,7 @@ def make_segments(mailchimp, list_id, count, emails): # create segments and add emails for seg in range(count): - name = 'random_{0:002}'.format(seg) + name = f'random_{seg:002}' seg_id = mailchimp.listStaticSegmentAdd(id=list_id, name=name) for batch in chunk(chunks[seg], BATCH_SIZE): mailchimp.listStaticSegmentMembersAdd( diff --git a/lms/djangoapps/mobile_api/admin.py b/lms/djangoapps/mobile_api/admin.py index 841fb4b0c7..fde951f309 100644 --- a/lms/djangoapps/mobile_api/admin.py +++ b/lms/djangoapps/mobile_api/admin.py @@ -17,7 +17,7 @@ class AppVersionConfigAdmin(admin.ModelAdmin): fields = ('platform', 'version', 'expire_at', 'enabled') list_filter = ['platform'] - class Meta(object): + class Meta: ordering = ['-major_version', '-minor_version', '-patch_version'] def get_list_display(self, __): diff --git a/lms/djangoapps/mobile_api/course_info/tests.py b/lms/djangoapps/mobile_api/course_info/tests.py index 4f5556fa89..40a0a3ed28 100644 --- a/lms/djangoapps/mobile_api/course_info/tests.py +++ b/lms/djangoapps/mobile_api/course_info/tests.py @@ -6,7 +6,6 @@ Tests for course_info import ddt from django.conf import settings from milestones.tests.utils import MilestonesTestCaseMixin -from six.moves import range from lms.djangoapps.mobile_api.testutils import MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin from lms.djangoapps.mobile_api.utils import API_V1, API_V05 @@ -24,7 +23,7 @@ class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTest REVERSE_INFO = {'name': 'course-updates-list', 'params': ['course_id', 'api_version']} def verify_success(self, response): - super(TestUpdates, self).verify_success(response) # lint-amnesty, pylint: disable=super-with-arguments + super().verify_success(response) assert response.data == [] @ddt.data( @@ -67,7 +66,7 @@ class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTest # old format stores the updates with the newest first for num in range(num_updates, 0, -1): update_data += "
  • Date" + str(num) + "

    Update" + str(num) + "
  • " - course_updates.data = u"
      " + update_data + "
    " + course_updates.data = "
      " + update_data + "
    " modulestore().update_item(course_updates, self.user.id) # call API diff --git a/lms/djangoapps/mobile_api/course_info/urls.py b/lms/djangoapps/mobile_api/course_info/urls.py index 55c3a3ded3..eebf3fff50 100644 --- a/lms/djangoapps/mobile_api/course_info/urls.py +++ b/lms/djangoapps/mobile_api/course_info/urls.py @@ -10,12 +10,12 @@ from .views import CourseHandoutsList, CourseUpdatesList urlpatterns = [ url( - r'^{}/handouts$'.format(settings.COURSE_ID_PATTERN), + fr'^{settings.COURSE_ID_PATTERN}/handouts$', CourseHandoutsList.as_view(), name='course-handouts-list' ), url( - r'^{}/updates$'.format(settings.COURSE_ID_PATTERN), + fr'^{settings.COURSE_ID_PATTERN}/updates$', CourseUpdatesList.as_view(), name='course-updates-list' ), diff --git a/lms/djangoapps/mobile_api/course_info/views.py b/lms/djangoapps/mobile_api/course_info/views.py index 175889515c..6d78f05918 100644 --- a/lms/djangoapps/mobile_api/course_info/views.py +++ b/lms/djangoapps/mobile_api/course_info/views.py @@ -6,9 +6,9 @@ Views for course info API from rest_framework import generics from rest_framework.response import Response +from common.djangoapps.static_replace import make_static_urls_absolute from lms.djangoapps.courseware.courses import get_course_info_section_module from openedx.core.lib.xblock_utils import get_course_update_items -from common.djangoapps.static_replace import make_static_urls_absolute from ..decorators import mobile_course_access, mobile_view diff --git a/lms/djangoapps/mobile_api/middleware.py b/lms/djangoapps/mobile_api/middleware.py index 88c593d5ec..41af2b688b 100644 --- a/lms/djangoapps/mobile_api/middleware.py +++ b/lms/djangoapps/mobile_api/middleware.py @@ -10,7 +10,6 @@ from django.core.cache import cache from django.http import HttpResponse from django.utils.deprecation import MiddlewareMixin from pytz import UTC -import six from lms.djangoapps.mobile_api.mobile_platform import MobilePlatform from lms.djangoapps.mobile_api.models import AppVersionConfig @@ -79,7 +78,7 @@ class AppVersionUpgrade(MiddlewareMixin): Returns: string: Cache key to be used. """ - return "mobile_api.app_version_upgrade.{}.{}".format(field, key) + return f"mobile_api.app_version_upgrade.{field}.{key}" def _get_version_info(self, request): """ @@ -112,7 +111,7 @@ class AppVersionUpgrade(MiddlewareMixin): request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date latest_version = cached_data.get(latest_version_cache_key) - if not (latest_version and isinstance(latest_version, six.text_type)): + if not (latest_version and isinstance(latest_version, str)): latest_version = self._get_latest_version(platform.NAME) cache.set(latest_version_cache_key, latest_version, self.CACHE_TIMEOUT) request_cache_dict[self.LATEST_VERSION_HEADER] = latest_version diff --git a/lms/djangoapps/mobile_api/migrations/0001_initial.py b/lms/djangoapps/mobile_api/migrations/0001_initial.py index ae604e95bc..4b3bbfa016 100644 --- a/lms/djangoapps/mobile_api/migrations/0001_initial.py +++ b/lms/djangoapps/mobile_api/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 @@ -19,7 +16,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), - ('video_profiles', models.TextField(help_text=u'A comma-separated list of names of profiles to include for videos returned from the mobile API.', blank=True)), + ('video_profiles', models.TextField(help_text='A comma-separated list of names of profiles to include for videos returned from the mobile API.', blank=True)), ('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/mobile_api/migrations/0002_auto_20160406_0904.py b/lms/djangoapps/mobile_api/migrations/0002_auto_20160406_0904.py index 602317c69d..8c4db5bbb7 100644 --- a/lms/djangoapps/mobile_api/migrations/0002_auto_20160406_0904.py +++ b/lms/djangoapps/mobile_api/migrations/0002_auto_20160406_0904.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models @@ -15,12 +12,12 @@ class Migration(migrations.Migration): name='AppVersionConfig', fields=[ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('platform', models.CharField(max_length=50, choices=[(u'Android', u'Android'), (u'iOS', u'iOS')])), - ('version', models.CharField(help_text=u'Version should be in the format X.X.X.Y where X is a number and Y is alphanumeric', max_length=50)), + ('platform', models.CharField(max_length=50, choices=[('Android', 'Android'), ('iOS', 'iOS')])), + ('version', models.CharField(help_text='Version should be in the format X.X.X.Y where X is a number and Y is alphanumeric', max_length=50)), ('major_version', models.IntegerField()), ('minor_version', models.IntegerField()), ('patch_version', models.IntegerField()), - ('expire_at', models.DateTimeField(null=True, verbose_name=u'Expiry date for platform version', blank=True)), + ('expire_at', models.DateTimeField(null=True, verbose_name='Expiry date for platform version', blank=True)), ('enabled', models.BooleanField(default=True)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), @@ -31,6 +28,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='appversionconfig', - unique_together=set([('platform', 'version')]), + unique_together={('platform', 'version')}, ), ] diff --git a/lms/djangoapps/mobile_api/migrations/0003_ignore_mobile_available_flag.py b/lms/djangoapps/mobile_api/migrations/0003_ignore_mobile_available_flag.py index 89816f0bd5..565281a6c6 100644 --- a/lms/djangoapps/mobile_api/migrations/0003_ignore_mobile_available_flag.py +++ b/lms/djangoapps/mobile_api/migrations/0003_ignore_mobile_available_flag.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/lms/djangoapps/mobile_api/mobile_platform.py b/lms/djangoapps/mobile_api/mobile_platform.py index 61ec5edd1e..fee0919e5b 100644 --- a/lms/djangoapps/mobile_api/mobile_platform.py +++ b/lms/djangoapps/mobile_api/mobile_platform.py @@ -6,10 +6,8 @@ Platform related Operations for Mobile APP import abc import re -import six - -class MobilePlatform(six.with_metaclass(abc.ABCMeta)): +class MobilePlatform(metaclass=abc.ABCMeta): """ MobilePlatform class creates an instance of platform based on user agent and supports platform related operations. @@ -58,14 +56,14 @@ class IOS(MobilePlatform): """ iOS platform """ USER_AGENT_REGEX = (r'\((?P[0-9]+.[0-9]+.[0-9]+(\.[0-9a-zA-Z]*)?); OS Version [0-9.]+ ' r'\(Build [0-9a-zA-Z]*\)\)') - NAME = u"iOS" + NAME = "iOS" class Android(MobilePlatform): """ Android platform """ USER_AGENT_REGEX = (r'Dalvik/[.0-9]+ \(Linux; U; Android [.0-9]+; (.*) (Build|MIUI)/[0-9a-zA-Z-.]*\) ' r'(.*)/(?P[0-9]+.[0-9]+.[0-9]+(\.[0-9a-zA-Z]*)?)') - NAME = u"Android" + NAME = "Android" # a list of all supported mobile platforms diff --git a/lms/djangoapps/mobile_api/models.py b/lms/djangoapps/mobile_api/models.py index 4e423cc0db..61c6a815d2 100644 --- a/lms/djangoapps/mobile_api/models.py +++ b/lms/djangoapps/mobile_api/models.py @@ -22,10 +22,10 @@ class MobileApiConfig(ConfigurationModel): """ video_profiles = models.TextField( blank=True, - help_text=u"A comma-separated list of names of profiles to include for videos returned from the mobile API." + help_text="A comma-separated list of names of profiles to include for videos returned from the mobile API." ) - class Meta(object): + class Meta: app_label = "mobile_api" @classmethod @@ -51,12 +51,12 @@ class AppVersionConfig(models.Model): version = models.CharField( max_length=50, blank=False, - help_text=u"Version should be in the format X.X.X.Y where X is a number and Y is alphanumeric" + help_text="Version should be in the format X.X.X.Y where X is a number and Y is alphanumeric" ) major_version = models.IntegerField() minor_version = models.IntegerField() patch_version = models.IntegerField() - expire_at = models.DateTimeField(null=True, blank=True, verbose_name=u"Expiry date for platform version") + expire_at = models.DateTimeField(null=True, blank=True, verbose_name="Expiry date for platform version") enabled = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -67,7 +67,7 @@ class AppVersionConfig(models.Model): ordering = ['-major_version', '-minor_version', '-patch_version'] def __str__(self): - return "{}_{}".format(self.platform, self.version) + return f"{self.platform}_{self.version}" @classmethod def latest_version(cls, platform): @@ -88,7 +88,7 @@ class AppVersionConfig(models.Model): def save(self, *args, **kwargs): # lint-amnesty, pylint: disable=signature-differs """ parses version into major, minor and patch versions before saving """ self.major_version, self.minor_version, self.patch_version = utils.parsed_version(self.version) - super(AppVersionConfig, self).save(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments + super().save(*args, **kwargs) class IgnoreMobileAvailableFlagConfig(ConfigurationModel): @@ -102,5 +102,5 @@ class IgnoreMobileAvailableFlagConfig(ConfigurationModel): .. no_pii: """ - class Meta(object): + class Meta: app_label = "mobile_api" diff --git a/lms/djangoapps/mobile_api/tests/test_decorator.py b/lms/djangoapps/mobile_api/tests/test_decorator.py index 5134268def..4e18d9703d 100644 --- a/lms/djangoapps/mobile_api/tests/test_decorator.py +++ b/lms/djangoapps/mobile_api/tests/test_decorator.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Tests for mobile API utilities. """ diff --git a/lms/djangoapps/mobile_api/tests/test_middleware.py b/lms/djangoapps/mobile_api/tests/test_middleware.py index 8cf3fbc855..3db1be023d 100644 --- a/lms/djangoapps/mobile_api/tests/test_middleware.py +++ b/lms/djangoapps/mobile_api/tests/test_middleware.py @@ -4,9 +4,9 @@ Tests for Version Based App Upgrade Middleware from datetime import datetime +from unittest import mock import ddt -import mock from django.core.cache import caches from django.http import HttpRequest, HttpResponse from pytz import UTC @@ -25,7 +25,7 @@ class TestAppVersionUpgradeMiddleware(CacheIsolationTestCase): ENABLED_CACHES = ['default'] def setUp(self): - super(TestAppVersionUpgradeMiddleware, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.middleware = AppVersionUpgrade() self.set_app_version_config() diff --git a/lms/djangoapps/mobile_api/tests/test_milestones.py b/lms/djangoapps/mobile_api/tests/test_milestones.py index ef97ef3f86..d9c233530b 100644 --- a/lms/djangoapps/mobile_api/tests/test_milestones.py +++ b/lms/djangoapps/mobile_api/tests/test_milestones.py @@ -3,19 +3,19 @@ Milestone related tests for the mobile_api """ -import six +from unittest.mock import patch + from crum import set_current_request from django.conf import settings -from mock import patch +from common.djangoapps.util.milestones_helpers import add_prerequisite_course, fulfill_course_milestone from lms.djangoapps.courseware.access_response import MilestoneAccessError from lms.djangoapps.courseware.tests.test_entrance_exam import add_entrance_exam_milestone, answer_entrance_exam_problem from openedx.core.djangolib.testing.utils import get_mock_request -from common.djangoapps.util.milestones_helpers import add_prerequisite_course, fulfill_course_milestone from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -class MobileAPIMilestonesMixin(object): +class MobileAPIMilestonesMixin: """ Tests the Mobile API decorators for milestones. @@ -114,7 +114,7 @@ class MobileAPIMilestonesMixin(object): add_entrance_exam_milestone(self.course, self.entrance_exam) self.course.entrance_exam_minimum_score_pct = 0.50 - self.course.entrance_exam_id = six.text_type(self.entrance_exam.location) + self.course.entrance_exam_id = str(self.entrance_exam.location) self.store.update_item(self.course, self.user.id) def _add_prerequisite_course(self): diff --git a/lms/djangoapps/mobile_api/tests/test_model.py b/lms/djangoapps/mobile_api/tests/test_model.py index a1e172215e..c63d0f271e 100644 --- a/lms/djangoapps/mobile_api/tests/test_model.py +++ b/lms/djangoapps/mobile_api/tests/test_model.py @@ -96,13 +96,13 @@ class TestMobileApiConfig(TestCase): """Check that video_profiles config is returned in order as a list""" MobileApiConfig(video_profiles="mobile_low,mobile_high,youtube").save() video_profile_list = MobileApiConfig.get_video_profiles() - assert video_profile_list == [u'mobile_low', u'mobile_high', u'youtube'] + assert video_profile_list == ['mobile_low', 'mobile_high', 'youtube'] def test_video_profile_list_with_whitespace(self): """Check video_profiles config with leading and trailing whitespace""" MobileApiConfig(video_profiles=" mobile_low , mobile_high,youtube ").save() video_profile_list = MobileApiConfig.get_video_profiles() - assert video_profile_list == [u'mobile_low', u'mobile_high', u'youtube'] + assert video_profile_list == ['mobile_low', 'mobile_high', 'youtube'] def test_empty_video_profile(self): """Test an empty video_profile""" diff --git a/lms/djangoapps/mobile_api/testutils.py b/lms/djangoapps/mobile_api/testutils.py index 384076fbd3..8eeabd9413 100644 --- a/lms/djangoapps/mobile_api/testutils.py +++ b/lms/djangoapps/mobile_api/testutils.py @@ -13,24 +13,23 @@ Test utilities for mobile API tests: import datetime +from unittest.mock import patch import ddt import pytz -import six from django.conf import settings from django.urls import reverse from django.utils import timezone -from mock import patch from opaque_keys.edx.keys import CourseKey from rest_framework.test import APITestCase +from common.djangoapps.student import auth +from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.courseware.access_response import MobileAvailabilityError, StartDateError, VisibilityError from lms.djangoapps.courseware.tests.factories import UserFactory from lms.djangoapps.mobile_api.models import IgnoreMobileAvailableFlagConfig from lms.djangoapps.mobile_api.tests.test_milestones import MobileAPIMilestonesMixin from lms.djangoapps.mobile_api.utils import API_V1 -from common.djangoapps.student import auth -from common.djangoapps.student.models import CourseEnrollment from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -43,7 +42,7 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase): They may also override any of the methods defined in this class to control the behavior of the TestMixins. """ def setUp(self): - super(MobileAPITestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course = CourseFactory.create( mobile_available=True, static_asset_path="needed_for_split", @@ -57,7 +56,7 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase): IgnoreMobileAvailableFlagConfig(enabled=False).save() def tearDown(self): - super(MobileAPITestCase, self).tearDown() # lint-amnesty, pylint: disable=super-with-arguments + super().tearDown() self.logout() def login(self): @@ -96,7 +95,7 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase): """Base implementation that returns URL for endpoint that's being tested.""" reverse_args = reverse_args or {} if 'course_id' in self.REVERSE_INFO['params']: - reverse_args.update({'course_id': six.text_type(kwargs.get('course_id', self.course.id))}) + reverse_args.update({'course_id': str(kwargs.get('course_id', self.course.id))}) if 'username' in self.REVERSE_INFO['params']: reverse_args.update({'username': kwargs.get('username', self.user.username)}) if 'api_version' in self.REVERSE_INFO['params']: @@ -108,7 +107,7 @@ class MobileAPITestCase(ModuleStoreTestCase, APITestCase): return self.client.get(url, data=data) -class MobileAuthTestMixin(object): +class MobileAuthTestMixin: """ Test Mixin for testing APIs decorated with mobile_view. """ diff --git a/lms/djangoapps/mobile_api/users/serializers.py b/lms/djangoapps/mobile_api/users/serializers.py index b774379975..a4418696fd 100644 --- a/lms/djangoapps/mobile_api/users/serializers.py +++ b/lms/djangoapps/mobile_api/users/serializers.py @@ -3,15 +3,14 @@ Serializer for user API """ -import six from rest_framework import serializers from rest_framework.reverse import reverse -from lms.djangoapps.courseware.access import has_access -from lms.djangoapps.certificates.api import certificate_downloadable_status -from openedx.features.course_duration_limits.access import get_user_course_expiration_date from common.djangoapps.student.models import CourseEnrollment, User from common.djangoapps.util.course import get_encoded_course_sharing_utm_params, get_link_for_about_page +from lms.djangoapps.certificates.api import certificate_downloadable_status +from lms.djangoapps.courseware.access import has_access +from openedx.features.course_duration_limits.access import get_user_course_expiration_date class CourseOverviewField(serializers.RelatedField): # lint-amnesty, pylint: disable=abstract-method @@ -19,7 +18,7 @@ class CourseOverviewField(serializers.RelatedField): # lint-amnesty, pylint: di Custom field to wrap a CourseOverview object. Read-only. """ def to_representation(self, course_overview): # lint-amnesty, pylint: disable=arguments-differ - course_id = six.text_type(course_overview.id) + course_id = str(course_overview.id) request = self.context.get('request') api_version = self.context.get('api_version') enrollment = CourseEnrollment.get_enrollment(user=self.context.get('request').user, course_key=course_id) @@ -111,7 +110,7 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer): else: return {} - class Meta(object): + class Meta: model = CourseEnrollment fields = ('audit_access_expires', 'created', 'mode', 'is_active', 'course', 'certificate') lookup_field = 'username' @@ -122,7 +121,7 @@ class CourseEnrollmentSerializerv05(CourseEnrollmentSerializer): Serializes CourseEnrollment models for v0.5 api Does not include 'audit_access_expires' field that is present in v1 api """ - class Meta(object): + class Meta: model = CourseEnrollment fields = ('created', 'mode', 'is_active', 'course', 'certificate') lookup_field = 'username' @@ -145,7 +144,7 @@ class UserSerializer(serializers.ModelSerializer): request=request ) - class Meta(object): + class Meta: model = User fields = ('id', 'username', 'email', 'name', 'course_enrollments') lookup_field = 'username' diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 68df5a04e9..dc73b85850 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -4,10 +4,11 @@ Tests for users API import datetime +from unittest.mock import patch +from urllib.parse import parse_qs import ddt import pytz -import six from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing from django.conf import settings from django.template import defaultfilters @@ -15,15 +16,16 @@ from django.test import RequestFactory, override_settings from django.utils import timezone from django.utils.timezone import now from milestones.tests.utils import MilestonesTestCaseMixin -from mock import patch -from six.moves import range -from six.moves.urllib.parse import parse_qs from common.djangoapps.course_modes.models import CourseMode -from lms.djangoapps.courseware.access_response import MilestoneAccessError, StartDateError, VisibilityError +from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory +from common.djangoapps.util.milestones_helpers import set_prerequisite_courses +from common.djangoapps.util.testing import UrlResetMixin from lms.djangoapps.certificates.api import generate_user_certificates from lms.djangoapps.certificates.models import CertificateStatuses from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory +from lms.djangoapps.courseware.access_response import MilestoneAccessError, StartDateError, VisibilityError from lms.djangoapps.grades.tests.utils import mock_passing_grade from lms.djangoapps.mobile_api.testutils import ( MobileAPITestCase, @@ -36,10 +38,6 @@ from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory from openedx.core.lib.courses import course_image_url from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_experience.tests.views.helpers import add_course_mode -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory -from common.djangoapps.util.milestones_helpers import set_prerequisite_courses -from common.djangoapps.util.testing import UrlResetMixin from xmodule.course_module import DEFAULT_START_DATE from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -118,13 +116,13 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest @patch.dict(settings.FEATURES, {"ENABLE_DISCUSSION_SERVICE": True}) def setUp(self): - super(TestUserEnrollmentApi, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() def verify_success(self, response): """ Verifies user course enrollment response for success """ - super(TestUserEnrollmentApi, self).verify_success(response) # lint-amnesty, pylint: disable=super-with-arguments + super().verify_success(response) courses = response.data assert len(courses) == 1 @@ -132,7 +130,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest assert 'courses/{}/about'.format(self.course.id) in found_course['course_about'] assert 'course_info/{}/updates'.format(self.course.id) in found_course['course_updates'] assert 'course_info/{}/handouts'.format(self.course.id) in found_course['course_handouts'] - assert found_course['id'] == six.text_type(self.course.id) + assert found_course['id'] == str(self.course.id) assert courses[0]['mode'] == CourseMode.DEFAULT_MODE_SLUG assert courses[0]['course']['subscription_id'] == self.course.clean_id(padding_char='_') @@ -161,7 +159,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest response = self.api_response(api_version=api_version) for course_index in range(num_courses): assert response.data[course_index]['course']['id'] ==\ - six.text_type(courses[((num_courses - course_index) - 1)].id) + str(courses[((num_courses - course_index) - 1)].id) @ddt.data(API_V05, API_V1) @patch.dict(settings.FEATURES, { @@ -174,7 +172,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest course_with_prereq = CourseFactory.create(start=self.LAST_WEEK, mobile_available=True) prerequisite_course = CourseFactory.create() - set_prerequisite_courses(course_with_prereq.id, [six.text_type(prerequisite_course.id)]) + set_prerequisite_courses(course_with_prereq.id, [str(prerequisite_course.id)]) # Create list of courses with various expected courseware_access responses and corresponding expected codes courses = [ @@ -423,7 +421,7 @@ class CourseStatusAPITestCase(MobileAPITestCase): """ Creates a basic course structure for our course """ - super(CourseStatusAPITestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.section = ItemFactory.create( parent=self.course, @@ -456,8 +454,8 @@ class TestCourseStatusGET(CourseStatusAPITestCase, MobileAuthUserTestMixin, self.login_and_enroll() response = self.api_response(api_version=API_V05) - assert response.data['last_visited_module_id'] == six.text_type(self.sub_section.location) - assert response.data['last_visited_module_path'] == [six.text_type(module.location) for module in + assert response.data['last_visited_module_id'] == str(self.sub_section.location) + assert response.data['last_visited_module_path'] == [str(module.location) for module in [self.sub_section, self.section, self.course]] def test_success_v1(self): @@ -465,7 +463,7 @@ class TestCourseStatusGET(CourseStatusAPITestCase, MobileAuthUserTestMixin, self.login_and_enroll() submit_completions_for_testing(self.user, [self.unit.location]) response = self.api_response(api_version=API_V1) - assert response.data['last_visited_block_id'] == six.text_type(self.unit.location) + assert response.data['last_visited_block_id'] == str(self.unit.location) class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin, @@ -479,8 +477,8 @@ class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin, def test_success(self): self.login_and_enroll() - response = self.api_response(data={"last_visited_module_id": six.text_type(self.other_unit.location)}) - assert response.data['last_visited_module_id'] == six.text_type(self.other_sub_section.location) + response = self.api_response(data={"last_visited_module_id": str(self.other_unit.location)}) + assert response.data['last_visited_module_id'] == str(self.other_sub_section.location) def test_invalid_module(self): self.login_and_enroll() @@ -498,7 +496,7 @@ class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin, past_date = datetime.datetime.now() response = self.api_response( data={ - "last_visited_module_id": six.text_type(self.other_unit.location), + "last_visited_module_id": str(self.other_unit.location), "modification_date": past_date.isoformat() }, expected_response_code=400 @@ -513,16 +511,16 @@ class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin, self.login_and_enroll() # save something so we have an initial date - self.api_response(data={"last_visited_module_id": six.text_type(initial_unit.location)}) + self.api_response(data={"last_visited_module_id": str(initial_unit.location)}) # now actually update it response = self.api_response( data={ - "last_visited_module_id": six.text_type(update_unit.location), + "last_visited_module_id": str(update_unit.location), "modification_date": date.isoformat() } ) - assert response.data['last_visited_module_id'] == six.text_type(expected_subsection.location) + assert response.data['last_visited_module_id'] == str(expected_subsection.location) def test_old_date(self): self.login_and_enroll() @@ -538,11 +536,11 @@ class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin, self.login_and_enroll() response = self.api_response( data={ - "last_visited_module_id": six.text_type(self.other_unit.location), + "last_visited_module_id": str(self.other_unit.location), "modification_date": timezone.now().isoformat() } ) - assert response.data['last_visited_module_id'] == six.text_type(self.other_sub_section.location) + assert response.data['last_visited_module_id'] == str(self.other_sub_section.location) def test_invalid_date(self): self.login_and_enroll() @@ -560,7 +558,7 @@ class TestCourseEnrollmentSerializer(MobileAPITestCase, MilestonesTestCaseMixin) ENABLED_SIGNALS = ['course_published'] def setUp(self): - super(TestCourseEnrollmentSerializer, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.login_and_enroll() self.request = RequestFactory().get('/') self.request.user = self.user diff --git a/lms/djangoapps/mobile_api/users/urls.py b/lms/djangoapps/mobile_api/users/urls.py index 4a580693d1..419d9d67f0 100644 --- a/lms/djangoapps/mobile_api/users/urls.py +++ b/lms/djangoapps/mobile_api/users/urls.py @@ -15,7 +15,7 @@ urlpatterns = [ UserCourseEnrollmentsList.as_view(), name='courseenrollment-detail' ), - url('^{}/course_status_info/{}'.format(settings.USERNAME_PATTERN, settings.COURSE_ID_PATTERN), + url(f'^{settings.USERNAME_PATTERN}/course_status_info/{settings.COURSE_ID_PATTERN}', UserCourseStatus.as_view(), name='user-course-status') ] diff --git a/lms/djangoapps/mobile_api/users/views.py b/lms/djangoapps/mobile_api/users/views.py index 7fec2ebc19..3647110d59 100644 --- a/lms/djangoapps/mobile_api/users/views.py +++ b/lms/djangoapps/mobile_api/users/views.py @@ -3,9 +3,9 @@ Views for user API """ -import six -from completion.utilities import get_key_to_last_completed_block from completion.exceptions import UnavailableCompletionData +from completion.utilities import get_key_to_last_completed_block +from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.contrib.auth.signals import user_logged_in from django.shortcuts import redirect from django.utils import dateparse @@ -16,17 +16,16 @@ from rest_framework.decorators import api_view from rest_framework.response import Response from xblock.fields import Scope from xblock.runtime import KeyValueStore -from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user +from common.djangoapps.student.models import CourseEnrollment, User # lint-amnesty, pylint: disable=reimported from lms.djangoapps.courseware.access import is_mobile_available_for_user +from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED from lms.djangoapps.courseware.courses import get_current_child from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.module_render import get_module_for_descriptor from lms.djangoapps.courseware.views.index import save_positions_recursively_up -from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED -from lms.djangoapps.mobile_api.utils import API_V05, API_V1 +from lms.djangoapps.mobile_api.utils import API_V1, API_V05 from openedx.features.course_duration_limits.access import check_course_expired -from common.djangoapps.student.models import CourseEnrollment, User # lint-amnesty, pylint: disable=reimported from xmodule.modulestore.django import modulestore from xmodule.modulestore.exceptions import ItemNotFoundError @@ -72,7 +71,7 @@ class UserDetail(generics.RetrieveAPIView): lookup_field = 'username' def get_serializer_context(self): - context = super(UserDetail, self).get_serializer_context() # lint-amnesty, pylint: disable=super-with-arguments + context = super().get_serializer_context() context['api_version'] = self.kwargs.get('api_version') return context @@ -152,7 +151,7 @@ class UserCourseStatus(views.APIView): Returns the course status """ path = self._last_visited_module_path(request, course) - path_ids = [six.text_type(module.location) for module in path] + path_ids = [str(module.location) for module in path] return Response({ "last_visited_module_id": path_ids[0], "last_visited_module_path": path_ids, @@ -312,7 +311,7 @@ class UserCourseEnrollmentsList(generics.ListAPIView): return check_org is None or (check_org.lower() == course_org.lower()) def get_serializer_context(self): - context = super(UserCourseEnrollmentsList, self).get_serializer_context() # lint-amnesty, pylint: disable=super-with-arguments + context = super().get_serializer_context() context['api_version'] = self.kwargs.get('api_version') return context diff --git a/lms/djangoapps/mobile_api/utils.py b/lms/djangoapps/mobile_api/utils.py index 07b237d206..e674df8ec6 100644 --- a/lms/djangoapps/mobile_api/utils.py +++ b/lms/djangoapps/mobile_api/utils.py @@ -1,7 +1,6 @@ """ Common utility methods for Mobile APIs. """ -from six.moves import map API_V05 = 'v0.5' API_V1 = 'v1' diff --git a/lms/djangoapps/monitoring/scripts/clean_unmapped_view_modules.py b/lms/djangoapps/monitoring/scripts/clean_unmapped_view_modules.py index 2d6233ff5b..77fb5fcf9c 100644 --- a/lms/djangoapps/monitoring/scripts/clean_unmapped_view_modules.py +++ b/lms/djangoapps/monitoring/scripts/clean_unmapped_view_modules.py @@ -18,6 +18,7 @@ Or for more details:: """ import csv + import click @@ -54,7 +55,7 @@ def main(unmapped_csv): Script removes duplicates in addition to providing sorted list of plain app names. """ - with open(unmapped_csv, 'r') as file: + with open(unmapped_csv) as file: csv_data = file.read() reader = csv.DictReader(csv_data.splitlines()) diff --git a/lms/djangoapps/monitoring/scripts/generate_code_owner_mappings.py b/lms/djangoapps/monitoring/scripts/generate_code_owner_mappings.py index 78c308d4a3..2a284e5db6 100644 --- a/lms/djangoapps/monitoring/scripts/generate_code_owner_mappings.py +++ b/lms/djangoapps/monitoring/scripts/generate_code_owner_mappings.py @@ -12,10 +12,11 @@ Or for more details:: """ import csv -import click import os import re +import click + # Maps edx-platform installed Django apps to the edx repo that contains # the app code. EDX_REPO_APPS = { @@ -113,10 +114,10 @@ def main(repo_csv, app_csv, dep_csv): print('# Do not hand edit CODE_OWNER_MAPPINGS. Generated by {}'.format(os.path.basename(__file__))) print('CODE_OWNER_MAPPINGS:') for owner, path_list in sorted(owner_to_paths_map.items()): - print(" {}:".format(owner)) + print(f" {owner}:") path_list.sort() for path in path_list: - print(" - {}".format(path)) + print(f" - {path}") owner_with_mappings_set = set(owner_to_paths_map.keys()) print('# Do not hand edit CODE_OWNER_THEMES. Generated by {}'.format(os.path.basename(__file__))) @@ -126,10 +127,10 @@ def main(repo_csv, app_csv, dep_csv): # only include the theme's list of owners that have mappings theme_owner_with_mappings_list = list(theme_owner_set & owner_with_mappings_set) if theme_owner_with_mappings_list: - print(" {}:".format(theme)) + print(f" {theme}:") theme_owner_with_mappings_list.sort() for owner in theme_owner_with_mappings_list: - print(" - {}".format(owner)) + print(f" - {owner}") def _map_repo_apps(csv_type, repo_csv, app_to_repo_map, owner_map, owner_to_paths_map): @@ -144,7 +145,7 @@ def _map_repo_apps(csv_type, repo_csv, app_to_repo_map, owner_map, owner_to_path owner_to_paths_map (dict): Holds results mapping owner to paths """ - with open(repo_csv, 'r') as file: + with open(repo_csv) as file: csv_data = file.read() reader = csv.DictReader(csv_data.splitlines()) @@ -160,14 +161,14 @@ def _map_repo_apps(csv_type, repo_csv, app_to_repo_map, owner_map, owner_to_path owner_to_paths_map[owner] = [] owner_to_paths_map[owner].append(app) else: - print('WARNING: Repo {} was not found in {} csv. Needed for app {}.'.format(repo_url, csv_type, app)) + print(f'WARNING: Repo {repo_url} was not found in {csv_type} csv. Needed for app {app}.') def _map_edx_platform_apps(app_csv, owner_map, owner_to_paths_map): """ Reads CSV of edx-platform app ownership and updates mappings """ - with open(app_csv, 'r') as file: + with open(app_csv) as file: csv_data = file.read() reader = csv.DictReader(csv_data.splitlines()) for row in reader: @@ -215,7 +216,7 @@ def _get_and_map_code_owner(row, owner_map): if theme: theme = theme.lower() - owner = '{}-{}'.format(theme, squad) if theme else squad + owner = f'{theme}-{squad}' if theme else squad theme = theme or squad if squad not in owner_map['squad_to_theme_map']: