Ran pyupgrade on lms/djangoapps
Ran pyupgrade on lms/djangoapps/mailing Ran pyupgrade on lms/djangoapps/mobile_api Ran pyupgrade on lms/djangoapps/monitoring
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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, __):
|
||||
|
||||
@@ -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 += "<li><h2>Date" + str(num) + "</h2><a href=\"/static/\">Update" + str(num) + "</a></li>"
|
||||
course_updates.data = u"<ol>" + update_data + "</ol>"
|
||||
course_updates.data = "<ol>" + update_data + "</ol>"
|
||||
modulestore().update_item(course_updates, self.user.id)
|
||||
|
||||
# call API
|
||||
|
||||
@@ -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'
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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={
|
||||
|
||||
@@ -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')},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -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<version>[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<version>[0-9]+.[0-9]+.[0-9]+(\.[0-9a-zA-Z]*)?)')
|
||||
NAME = u"Android"
|
||||
NAME = "Android"
|
||||
|
||||
|
||||
# a list of all supported mobile platforms
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests for mobile API utilities.
|
||||
"""
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
Common utility methods for Mobile APIs.
|
||||
"""
|
||||
from six.moves import map
|
||||
|
||||
API_V05 = 'v0.5'
|
||||
API_V1 = 'v1'
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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']:
|
||||
|
||||
Reference in New Issue
Block a user