From e505d992371dd2dc720134c46ffdf0cd6fe4a2d0 Mon Sep 17 00:00:00 2001 From: "M. Zulqarnain" <muhammad.zulqarnain@arbisoft.com> Date: Mon, 22 Feb 2021 12:58:41 +0500 Subject: [PATCH] pyupgrade on lms gating and grades apps (#26532) --- lms/djangoapps/gating/api.py | 4 +- lms/djangoapps/gating/signals.py | 7 +- lms/djangoapps/gating/tasks.py | 11 +- lms/djangoapps/gating/tests/test_api.py | 7 +- .../gating/tests/test_integration.py | 6 +- lms/djangoapps/gating/tests/test_signals.py | 6 +- lms/djangoapps/grades/api.py | 12 +- lms/djangoapps/grades/apps.py | 14 +- lms/djangoapps/grades/config/forms.py | 2 +- lms/djangoapps/grades/config/models.py | 13 +- .../grades/config/tests/test_models.py | 4 +- lms/djangoapps/grades/config/waffle.py | 19 +- lms/djangoapps/grades/constants.py | 8 +- lms/djangoapps/grades/course_data.py | 8 +- lms/djangoapps/grades/course_grade.py | 17 +- lms/djangoapps/grades/course_grade_factory.py | 21 +- lms/djangoapps/grades/events.py | 89 ++++--- .../management/commands/compute_grades.py | 5 +- .../commands/recalculate_subsection_grades.py | 19 +- .../commands/tests/test_compute_grades.py | 20 +- .../tests/test_recalculate_learner_grades.py | 11 +- .../test_recalculate_subsection_grades.py | 19 +- .../grades/migrations/0001_initial.py | 5 +- .../0002_rename_last_edited_field.py | 3 - ...tgradesflag_persistentgradesenabledflag.py | 3 - .../0004_visibleblocks_course_id.py | 3 - .../migrations/0005_multiple_course_flags.py | 3 - .../0006_persistent_course_grades.py | 5 +- .../0007_add_passed_timestamp_column.py | 5 +- ...rsistentsubsectiongrade_first_attempted.py | 3 - .../migrations/0009_auto_20170111_1507.py | 7 +- .../migrations/0010_auto_20170112_1156.py | 5 +- .../migrations/0011_null_edited_time.py | 3 - .../migrations/0012_computegradessetting.py | 5 +- .../0013_persistentsubsectiongradeoverride.py | 3 - ...ersistentsubsectiongradeoverridehistory.py | 5 +- ...oricalpersistentsubsectiongradeoverride.py | 1 - .../migrations/0016_auto_20190703_1446.py | 1 - .../0017_delete_manual_psgoverride_table.py | 1 - .../0018_add_waffle_flag_defaults.py | 3 - lms/djangoapps/grades/models.py | 90 ++++--- lms/djangoapps/grades/rest_api/serializers.py | 2 +- .../grades/rest_api/v1/gradebook_views.py | 59 +++-- .../grades/rest_api/v1/tests/mixins.py | 17 +- .../rest_api/v1/tests/test_gradebook_views.py | 106 ++++----- .../v1/tests/test_grading_policy_view.py | 21 +- .../grades/rest_api/v1/tests/test_views.py | 12 +- lms/djangoapps/grades/rest_api/v1/urls.py | 10 +- lms/djangoapps/grades/rest_api/v1/utils.py | 8 +- lms/djangoapps/grades/scores.py | 15 +- lms/djangoapps/grades/services.py | 2 +- lms/djangoapps/grades/signals/handlers.py | 31 ++- lms/djangoapps/grades/subsection_grade.py | 47 ++-- .../grades/subsection_grade_factory.py | 12 +- lms/djangoapps/grades/tasks.py | 40 ++-- lms/djangoapps/grades/tests/base.py | 8 +- .../grades/tests/integration/test_access.py | 12 +- .../grades/tests/integration/test_events.py | 74 +++--- .../grades/tests/integration/test_problems.py | 41 ++-- lms/djangoapps/grades/tests/test_api.py | 18 +- .../grades/tests/test_course_data.py | 13 +- .../grades/tests/test_course_grade.py | 14 +- .../grades/tests/test_course_grade_factory.py | 34 ++- lms/djangoapps/grades/tests/test_models.py | 99 ++++---- lms/djangoapps/grades/tests/test_scores.py | 12 +- lms/djangoapps/grades/tests/test_services.py | 34 ++- lms/djangoapps/grades/tests/test_signals.py | 8 +- .../tests/test_subsection_grade_factory.py | 5 +- lms/djangoapps/grades/tests/test_tasks.py | 60 +++-- .../grades/tests/test_transformer.py | 220 +++++++++--------- lms/djangoapps/grades/tests/utils.py | 2 +- lms/djangoapps/grades/transformer.py | 20 +- lms/djangoapps/grades/util_services.py | 4 +- 73 files changed, 723 insertions(+), 813 deletions(-) diff --git a/lms/djangoapps/gating/api.py b/lms/djangoapps/gating/api.py index a8d2d898914..d68d9740b5e 100644 --- a/lms/djangoapps/gating/api.py +++ b/lms/djangoapps/gating/api.py @@ -8,9 +8,9 @@ from collections import defaultdict from opaque_keys.edx.keys import UsageKey +from common.djangoapps.util import milestones_helpers from lms.djangoapps.courseware.entrance_exams import get_entrance_exam_content from openedx.core.lib.gating import api as gating_api -from common.djangoapps.util import milestones_helpers from openedx.core.toggles import ENTRANCE_EXAMS log = logging.getLogger(__name__) @@ -82,5 +82,5 @@ def get_entrance_exam_score_ratio(course_grade, exam_chapter_key): entrance_exam_score_ratio = course_grade.chapter_percentage(exam_chapter_key) except KeyError: entrance_exam_score_ratio = 0.0, 0.0 - log.warning(u'Gating: Unexpectedly failed to find chapter_grade for %s.', exam_chapter_key) + log.warning('Gating: Unexpectedly failed to find chapter_grade for %s.', exam_chapter_key) return entrance_exam_score_ratio diff --git a/lms/djangoapps/gating/signals.py b/lms/djangoapps/gating/signals.py index 292cf1c8ecf..b1ea187f075 100644 --- a/lms/djangoapps/gating/signals.py +++ b/lms/djangoapps/gating/signals.py @@ -1,9 +1,6 @@ """ Signal handlers for the gating djangoapp """ - - -import six from completion.models import BlockCompletion from django.db import models from django.dispatch import receiver @@ -37,10 +34,10 @@ def evaluate_subsection_completion_milestones(**kwargs): evaluation of any milestone which can be completed. """ instance = kwargs['instance'] - course_id = six.text_type(instance.context_key) + course_id = str(instance.context_key) if not instance.context_key.is_course: return # Content in a library or some other thing that doesn't support milestones - block_id = six.text_type(instance.block_key) + block_id = str(instance.block_key) user_id = instance.user_id task_evaluate_subsection_completion_milestones.delay(course_id, block_id, user_id) diff --git a/lms/djangoapps/gating/tasks.py b/lms/djangoapps/gating/tasks.py index 8297ea2bed6..0336c4abcfb 100644 --- a/lms/djangoapps/gating/tasks.py +++ b/lms/djangoapps/gating/tasks.py @@ -5,14 +5,13 @@ This file contains celery tasks related to course content gating. import logging -import six from celery import shared_task from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from edx_django_utils.monitoring import set_code_owner_attribute from opaque_keys.edx.keys import CourseKey, UsageKey -from lms.djangoapps.gating import api as gating_api from lms.djangoapps.course_blocks.api import get_course_blocks +from lms.djangoapps.gating import api as gating_api from xmodule.modulestore.django import modulestore log = logging.getLogger(__name__) @@ -34,7 +33,7 @@ def task_evaluate_subsection_completion_milestones(course_id, block_id, user_id) course = store.get_course(course_key) if not course or not course.enable_subsection_gating: log.debug( - u"Gating: ignoring evaluation of completion milestone because it disabled for course [%s]", course_id + "Gating: ignoring evaluation of completion milestone because it disabled for course [%s]", course_id ) else: try: @@ -44,12 +43,12 @@ def task_evaluate_subsection_completion_milestones(course_id, block_id, user_id) subsection_block = _get_subsection_of_block(completed_block_usage_key, course_structure) subsection = course_structure[subsection_block] log.debug( - u"Gating: Evaluating completion milestone for subsection [%s] and user [%s]", - six.text_type(subsection.location), user.id + "Gating: Evaluating completion milestone for subsection [%s] and user [%s]", + str(subsection.location), user.id ) gating_api.evaluate_prerequisite(course, subsection, user) except KeyError: - log.error(u"Gating: Given prerequisite subsection [%s] not found in course structure", block_id) + log.error("Gating: Given prerequisite subsection [%s] not found in course structure", block_id) def _get_subsection_of_block(usage_key, block_structure): diff --git a/lms/djangoapps/gating/tests/test_api.py b/lms/djangoapps/gating/tests/test_api.py index 28aa3803ab2..3b9ed16e8e6 100644 --- a/lms/djangoapps/gating/tests/test_api.py +++ b/lms/djangoapps/gating/tests/test_api.py @@ -3,10 +3,11 @@ Unit tests for gating.signals module """ +from unittest.mock import Mock, patch + from ddt import data, ddt, unpack from milestones import api as milestones_api from milestones.tests.utils import MilestonesTestCaseMixin -from mock import Mock, patch from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase from lms.djangoapps.gating.api import evaluate_prerequisite @@ -25,7 +26,7 @@ class GatingTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase): """ Initial data setup """ - super(GatingTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() # create course self.course = CourseFactory.create( @@ -65,7 +66,7 @@ class TestEvaluatePrerequisite(GatingTestCase, MilestonesTestCaseMixin): """ def setUp(self): - super(TestEvaluatePrerequisite, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user_dict = {'id': self.user.id} self.prereq_milestone = None self.subsection_grade = Mock(location=self.seq1.location, percent_graded=0.5) diff --git a/lms/djangoapps/gating/tests/test_integration.py b/lms/djangoapps/gating/tests/test_integration.py index 4152889c4d4..1afe5c07c5e 100644 --- a/lms/djangoapps/gating/tests/test_integration.py +++ b/lms/djangoapps/gating/tests/test_integration.py @@ -11,12 +11,12 @@ from edx_toggles.toggles.testutils import override_waffle_switch from milestones import api as milestones_api from milestones.tests.utils import MilestonesTestCaseMixin +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.courseware.access import has_access from lms.djangoapps.grades.api import CourseGradeFactory from lms.djangoapps.grades.tests.utils import answer_problem from openedx.core.djangolib.testing.utils import get_mock_request from openedx.core.lib.gating import api as gating_api -from common.djangoapps.student.tests.factories import UserFactory from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -31,11 +31,11 @@ class TestGatedContent(MilestonesTestCaseMixin, SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TestGatedContent, cls).setUpClass() + super().setUpClass() cls.set_up_course() def setUp(self): - super(TestGatedContent, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.setup_gating_milestone(50, 100) self.non_staff_user = UserFactory() self.staff_user = UserFactory(is_staff=True, is_superuser=True) diff --git a/lms/djangoapps/gating/tests/test_signals.py b/lms/djangoapps/gating/tests/test_signals.py index da9ba45a50e..ea4d59440c5 100644 --- a/lms/djangoapps/gating/tests/test_signals.py +++ b/lms/djangoapps/gating/tests/test_signals.py @@ -3,10 +3,10 @@ Unit tests for gating.signals module """ -from mock import Mock, patch +from unittest.mock import Mock, patch -from lms.djangoapps.gating.signals import evaluate_subsection_gated_milestones from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.gating.signals import evaluate_subsection_gated_milestones from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -18,7 +18,7 @@ class TestHandleScoreChanged(ModuleStoreTestCase): """ def setUp(self): - super(TestHandleScoreChanged, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course = CourseFactory.create(org='TestX', number='TS01', run='2016_Q1') self.user = UserFactory.create() self.subsection_grade = Mock() diff --git a/lms/djangoapps/grades/api.py b/lms/djangoapps/grades/api.py index f8ef149e482..ed0a82c08d6 100644 --- a/lms/djangoapps/grades/api.py +++ b/lms/djangoapps/grades/api.py @@ -11,11 +11,12 @@ from django.core.exceptions import ObjectDoesNotExist from opaque_keys.edx.keys import CourseKey, UsageKey from six import text_type +from common.djangoapps.track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type # Public Grades Modules from lms.djangoapps.grades import constants, context, course_data, events # Grades APIs that should NOT belong within the Grades subsystem # TODO move Gradebook to be an external feature outside of core Grades -from lms.djangoapps.grades.config.waffle import is_writable_gradebook_enabled, gradebook_can_see_bulk_management +from lms.djangoapps.grades.config.waffle import gradebook_can_see_bulk_management, is_writable_gradebook_enabled # Public Grades Factories from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.models_api import * @@ -27,7 +28,6 @@ from lms.djangoapps.grades.subsection_grade_factory import SubsectionGradeFactor from lms.djangoapps.grades.tasks import compute_all_grades_for_course as task_compute_all_grades_for_course from lms.djangoapps.grades.util_services import GradesUtilService from lms.djangoapps.utils import _get_key -from common.djangoapps.track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type def graded_subsections_for_course_id(course_id): @@ -80,8 +80,8 @@ def override_subsection_grade( signals.SUBSECTION_OVERRIDE_CHANGED.send( sender=None, user_id=user_id, - course_id=text_type(course_key), - usage_id=text_type(usage_key), + course_id=str(course_key), + usage_id=str(usage_key), only_if_higher=False, modified=override.modified, score_deleted=False, @@ -124,8 +124,8 @@ def undo_override_subsection_grade(user_id, course_key_or_id, usage_key_or_id, f signals.SUBSECTION_OVERRIDE_CHANGED.send( sender=None, user_id=user_id, - course_id=text_type(course_key), - usage_id=text_type(usage_key), + course_id=str(course_key), + usage_id=str(usage_key), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), # Not used when score_deleted=True score_deleted=True, diff --git a/lms/djangoapps/grades/apps.py b/lms/djangoapps/grades/apps.py index 93ed36642dc..c65e8722b6d 100644 --- a/lms/djangoapps/grades/apps.py +++ b/lms/djangoapps/grades/apps.py @@ -17,21 +17,21 @@ class GradesConfig(AppConfig): """ Application Configuration for Grades. """ - name = u'lms.djangoapps.grades' + name = 'lms.djangoapps.grades' plugin_app = { PluginURLs.CONFIG: { ProjectType.LMS: { - PluginURLs.NAMESPACE: u'grades_api', - PluginURLs.REGEX: u'^api/grades/', - PluginURLs.RELATIVE_PATH: u'rest_api.urls', + PluginURLs.NAMESPACE: 'grades_api', + PluginURLs.REGEX: '^api/grades/', + PluginURLs.RELATIVE_PATH: 'rest_api.urls', } }, PluginSettings.CONFIG: { ProjectType.LMS: { - SettingsType.PRODUCTION: {PluginSettings.RELATIVE_PATH: u'settings.production'}, - SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: u'settings.common'}, - SettingsType.TEST: {PluginSettings.RELATIVE_PATH: u'settings.test'}, + SettingsType.PRODUCTION: {PluginSettings.RELATIVE_PATH: 'settings.production'}, + SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: 'settings.common'}, + SettingsType.TEST: {PluginSettings.RELATIVE_PATH: 'settings.test'}, } } } diff --git a/lms/djangoapps/grades/config/forms.py b/lms/djangoapps/grades/config/forms.py index 9be433678e6..fe2f35dd7e2 100644 --- a/lms/djangoapps/grades/config/forms.py +++ b/lms/djangoapps/grades/config/forms.py @@ -16,7 +16,7 @@ log = logging.getLogger(__name__) class CoursePersistentGradesAdminForm(forms.ModelForm): """Input form for subsection grade enabling, allowing us to verify data.""" - class Meta(object): + class Meta: model = CoursePersistentGradesFlag fields = '__all__' diff --git a/lms/djangoapps/grades/config/models.py b/lms/djangoapps/grades/config/models.py index c4113418080..8a6b909181e 100644 --- a/lms/djangoapps/grades/config/models.py +++ b/lms/djangoapps/grades/config/models.py @@ -9,7 +9,6 @@ from django.conf import settings from django.db.models import BooleanField, IntegerField, TextField from django.utils.encoding import python_2_unicode_compatible from opaque_keys.edx.django.models import CourseKeyField - from six import text_type from openedx.core.lib.cache_utils import request_cached @@ -64,12 +63,12 @@ class PersistentGradesEnabledFlag(ConfigurationModel): return effective.enabled if effective is not None else False return True - class Meta(object): + class Meta: app_label = "grades" def __str__(self): current_model = PersistentGradesEnabledFlag.current() - return u"PersistentGradesEnabledFlag: enabled {}".format( + return "PersistentGradesEnabledFlag: enabled {}".format( current_model.is_enabled() ) @@ -85,7 +84,7 @@ class CoursePersistentGradesFlag(ConfigurationModel): """ KEY_FIELDS = ('course_id',) - class Meta(object): + class Meta: app_label = "grades" # The course that these features are attached to. @@ -95,18 +94,18 @@ class CoursePersistentGradesFlag(ConfigurationModel): not_en = "Not " if self.enabled: not_en = "" - return u"Course '{}': Persistent Grades {}Enabled".format(text_type(self.course_id), not_en) + return "Course '{}': Persistent Grades {}Enabled".format(str(self.course_id), not_en) class ComputeGradesSetting(ConfigurationModel): """ .. no_pii: """ - class Meta(object): + class Meta: app_label = "grades" batch_size = IntegerField(default=100) course_ids = TextField( blank=False, - help_text=u"Whitespace-separated list of course keys for which to compute grades." + help_text="Whitespace-separated list of course keys for which to compute grades." ) diff --git a/lms/djangoapps/grades/config/tests/test_models.py b/lms/djangoapps/grades/config/tests/test_models.py index 8d09bbb1090..b4f1105c2b3 100644 --- a/lms/djangoapps/grades/config/tests/test_models.py +++ b/lms/djangoapps/grades/config/tests/test_models.py @@ -5,11 +5,11 @@ persistent grading feature. import itertools +from unittest.mock import patch import ddt from django.conf import settings from django.test import TestCase -from mock import patch from opaque_keys.edx.locator import CourseLocator from lms.djangoapps.grades.config.models import PersistentGradesEnabledFlag @@ -25,7 +25,7 @@ class PersistentGradesFeatureFlagTests(TestCase): """ def setUp(self): - super(PersistentGradesFeatureFlagTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course_id_1 = CourseLocator(org="edx", course="course", run="run") self.course_id_2 = CourseLocator(org="edx", course="course2", run="run") diff --git a/lms/djangoapps/grades/config/waffle.py b/lms/djangoapps/grades/config/waffle.py index 77e2471130e..b3b8e89ec1d 100644 --- a/lms/djangoapps/grades/config/waffle.py +++ b/lms/djangoapps/grades/config/waffle.py @@ -5,10 +5,11 @@ waffle switches for the Grades app. from edx_toggles.toggles import LegacyWaffleFlagNamespace, LegacyWaffleSwitch, LegacyWaffleSwitchNamespace + from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag # Namespace -WAFFLE_NAMESPACE = u'grades' +WAFFLE_NAMESPACE = 'grades' # Switches @@ -23,7 +24,7 @@ WAFFLE_NAMESPACE = u'grades' # .. toggle_target_removal_date: None # .. toggle_tickets: https://github.com/edx/edx-platform/pull/14771 # .. toggle_warnings: This requires the PersistentGradesEnabledFlag to be enabled. -ASSUME_ZERO_GRADE_IF_ABSENT = u'assume_zero_grade_if_absent' +ASSUME_ZERO_GRADE_IF_ABSENT = 'assume_zero_grade_if_absent' # .. toggle_name: grades.disable_regrade_on_policy_change # .. toggle_implementation: WaffleSwitch # .. toggle_default: False @@ -33,7 +34,7 @@ ASSUME_ZERO_GRADE_IF_ABSENT = u'assume_zero_grade_if_absent' # .. toggle_target_removal_date: None # .. toggle_warnings: None # .. toggle_tickets: https://github.com/edx/edx-platform/pull/15733 -DISABLE_REGRADE_ON_POLICY_CHANGE = u'disable_regrade_on_policy_change' +DISABLE_REGRADE_ON_POLICY_CHANGE = 'disable_regrade_on_policy_change' # Course Flags @@ -47,7 +48,7 @@ DISABLE_REGRADE_ON_POLICY_CHANGE = u'disable_regrade_on_policy_change' # .. toggle_target_removal_date: None # .. toggle_warnings: None # .. toggle_tickets: https://github.com/edx/edx-platform/pull/20719 -REJECTED_EXAM_OVERRIDES_GRADE = u'rejected_exam_overrides_grade' +REJECTED_EXAM_OVERRIDES_GRADE = 'rejected_exam_overrides_grade' # .. toggle_name: grades.rejected_exam_overrides_grade # .. toggle_implementation: CourseWaffleFlag # .. toggle_default: False @@ -58,7 +59,7 @@ REJECTED_EXAM_OVERRIDES_GRADE = u'rejected_exam_overrides_grade' # .. toggle_target_removal_date: None # .. toggle_warnings: None # .. toggle_tickets: https://github.com/edx/edx-platform/pull/19026 -ENFORCE_FREEZE_GRADE_AFTER_COURSE_END = u'enforce_freeze_grade_after_course_end' +ENFORCE_FREEZE_GRADE_AFTER_COURSE_END = 'enforce_freeze_grade_after_course_end' # .. toggle_name: grades.writable_gradebook # .. toggle_implementation: CourseWaffleFlag @@ -70,7 +71,7 @@ ENFORCE_FREEZE_GRADE_AFTER_COURSE_END = u'enforce_freeze_grade_after_course_end' # .. toggle_target_removal_date: None # .. toggle_tickets: https://github.com/edx/edx-platform/pull/19054 # .. toggle_warnings: Enabling this requires that the `WRITABLE_GRADEBOOK_URL` setting be properly defined. -WRITABLE_GRADEBOOK = u'writable_gradebook' +WRITABLE_GRADEBOOK = 'writable_gradebook' # .. toggle_name: grades.bulk_management # .. toggle_implementation: CourseWaffleFlag @@ -82,14 +83,14 @@ WRITABLE_GRADEBOOK = u'writable_gradebook' # .. toggle_target_removal_date: None # .. toggle_warnings: None # .. toggle_tickets: https://github.com/edx/edx-platform/pull/21389 -BULK_MANAGEMENT = u'bulk_management' +BULK_MANAGEMENT = 'bulk_management' def waffle(): """ Returns the namespaced, cached, audited Waffle class for Grades. """ - return LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Grades: ') + return LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix='Grades: ') def waffle_switch(name): @@ -109,7 +110,7 @@ def waffle_flags(): WARNING: do not replicate this pattern. Instead of declaring waffle flag names as strings, you should create LegacyWaffleFlag and CourseWaffleFlag objects as top-level constants. """ - namespace = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Grades: ') + namespace = LegacyWaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix='Grades: ') return { # By default, enable rejected exam grade overrides. Can be disabled on a course-by-course basis. # TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment. diff --git a/lms/djangoapps/grades/constants.py b/lms/djangoapps/grades/constants.py index 2d5fb8f9791..ace772f33ad 100644 --- a/lms/djangoapps/grades/constants.py +++ b/lms/djangoapps/grades/constants.py @@ -3,7 +3,7 @@ Constants and Enums used by Grading. """ -class ScoreDatabaseTableEnum(object): +class ScoreDatabaseTableEnum: """ The various database tables that store scores. """ @@ -12,7 +12,7 @@ class ScoreDatabaseTableEnum(object): overrides = 'overrides' -class GradeOverrideFeatureEnum(object): - proctoring = u'PROCTORING' - gradebook = u'GRADEBOOK' +class GradeOverrideFeatureEnum: + proctoring = 'PROCTORING' + gradebook = 'GRADEBOOK' grade_import = 'grade-import' diff --git a/lms/djangoapps/grades/course_data.py b/lms/djangoapps/grades/course_data.py index c79806fa782..791758138c0 100644 --- a/lms/djangoapps/grades/course_data.py +++ b/lms/djangoapps/grades/course_data.py @@ -13,7 +13,7 @@ from .transformer import GradesTransformer @python_2_unicode_compatible -class CourseData(object): +class CourseData: """ Utility access layer to intelligently get and cache the requested course data as long as at least one property is @@ -108,15 +108,15 @@ class CourseData(object): """ Return human-readable string representation. """ - return u'Course: course_key: {}'.format(self.course_key) + return f'Course: course_key: {self.course_key}' def full_string(self): # lint-amnesty, pylint: disable=missing-function-docstring if self.effective_structure: - return u'Course: course_key: {}, version: {}, edited_on: {}, grading_policy: {}'.format( + return 'Course: course_key: {}, version: {}, edited_on: {}, grading_policy: {}'.format( self.course_key, self.version, self.edited_on, self.grading_policy_hash, ) else: - return u'Course: course_key: {}, empty course structure'.format(self.course_key) + return f'Course: course_key: {self.course_key}, empty course structure' @property def effective_structure(self): diff --git a/lms/djangoapps/grades/course_grade.py b/lms/djangoapps/grades/course_grade.py index 711fb6a2239..1f14ceecea7 100644 --- a/lms/djangoapps/grades/course_grade.py +++ b/lms/djangoapps/grades/course_grade.py @@ -6,7 +6,6 @@ CourseGrade Class from abc import abstractmethod from collections import OrderedDict, defaultdict -import six from ccx_keys.locator import CCXLocator from django.conf import settings from django.utils.encoding import python_2_unicode_compatible @@ -22,7 +21,7 @@ from .subsection_grade_factory import SubsectionGradeFactory @python_2_unicode_compatible -class CourseGradeBase(object): +class CourseGradeBase: """ Base class for Course Grades. """ @@ -38,8 +37,8 @@ class CourseGradeBase(object): self.force_update_subsections = force_update_subsections def __str__(self): - return u'Course Grade: percent: {}, letter_grade: {}, passed: {}'.format( - six.text_type(self.percent), + return 'Course Grade: percent: {}, letter_grade: {}, passed: {}'.format( + str(self.percent), self.letter_grade, self.passed, ) @@ -70,7 +69,7 @@ class CourseGradeBase(object): a dict keyed by subsection format types. """ subsections_by_format = defaultdict(OrderedDict) - for chapter in six.itervalues(self.chapter_grades): + for chapter in self.chapter_grades.values(): for subsection_grade in chapter['sections']: if subsection_grade.graded: graded_total = subsection_grade.graded_total @@ -99,7 +98,7 @@ class CourseGradeBase(object): keyed by subsection location. """ subsection_grades = defaultdict(OrderedDict) - for chapter in six.itervalues(self.chapter_grades): + for chapter in self.chapter_grades.values(): for subsection_grade in chapter['sections']: subsection_grades[subsection_grade.location] = subsection_grade return subsection_grades @@ -110,7 +109,7 @@ class CourseGradeBase(object): Returns a dict of problem scores keyed by their locations. """ problem_scores = {} - for chapter in six.itervalues(self.chapter_grades): + for chapter in self.chapter_grades.values(): for subsection_grade in chapter['sections']: problem_scores.update(subsection_grade.problem_scores) return problem_scores @@ -249,7 +248,7 @@ class CourseGrade(CourseGradeBase): Course Grade class when grades are updated or read from storage. """ def __init__(self, user, course_data, *args, **kwargs): - super(CourseGrade, self).__init__(user, course_data, *args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments + super().__init__(user, course_data, *args, **kwargs) self._subsection_grade_factory = SubsectionGradeFactory(user, course_data=course_data) def update(self): @@ -278,7 +277,7 @@ class CourseGrade(CourseGradeBase): if assume_zero_if_absent(self.course_data.course_key): return True - for chapter in six.itervalues(self.chapter_grades): + for chapter in self.chapter_grades.values(): for subsection_grade in chapter['sections']: if subsection_grade.all_total.first_attempted: return True diff --git a/lms/djangoapps/grades/course_grade_factory.py b/lms/djangoapps/grades/course_grade_factory.py index 8a8af4d2d40..3ca0199f5d6 100644 --- a/lms/djangoapps/grades/course_grade_factory.py +++ b/lms/djangoapps/grades/course_grade_factory.py @@ -1,14 +1,9 @@ """ Course Grade Factory Class """ - - from collections import namedtuple from logging import getLogger -import six -from six import text_type - from openedx.core.djangoapps.signals.signals import ( COURSE_GRADE_CHANGED, COURSE_GRADE_NOW_FAILED, @@ -24,7 +19,7 @@ from .models_api import prefetch_grade_overrides_and_visible_blocks log = getLogger(__name__) -class CourseGradeFactory(object): +class CourseGradeFactory: """ Factory class to create Course Grade objects. """ @@ -108,7 +103,7 @@ class CourseGradeFactory(object): course_data = CourseData( user=None, course=course, collected_block_structure=collected_block_structure, course_key=course_key, ) - stats_tags = [u'action:{}'.format(course_data.course_key)] # lint-amnesty, pylint: disable=unused-variable + stats_tags = [f'action:{course_data.course_key}'] # lint-amnesty, pylint: disable=unused-variable for user in users: yield self._iter_grade_result(user, course_data, force_update) @@ -130,10 +125,10 @@ class CourseGradeFactory(object): # Keep marching on even if this student couldn't be graded for # some reason, but log it for future reference. log.exception( - u'Cannot grade student %s in course %s because of exception: %s', + 'Cannot grade student %s in course %s because of exception: %s', user.id, course_data.course_key, - text_type(exc) + str(exc) ) return self.GradeResult(user, None, exc) @@ -142,7 +137,7 @@ class CourseGradeFactory(object): """ Returns a ZeroCourseGrade object for the given user and course. """ - log.debug(u'Grades: CreateZero, %s, User: %s', six.text_type(course_data), user.id) + log.debug('Grades: CreateZero, %s, User: %s', str(course_data), user.id) return ZeroCourseGrade(user, course_data) @staticmethod @@ -155,14 +150,14 @@ class CourseGradeFactory(object): raise PersistentCourseGrade.DoesNotExist persistent_grade = PersistentCourseGrade.read(user.id, course_data.course_key) - log.debug(u'Grades: Read, %s, User: %s, %s', six.text_type(course_data), user.id, persistent_grade) + log.debug('Grades: Read, %s, User: %s, %s', str(course_data), user.id, persistent_grade) return CourseGrade( user, course_data, persistent_grade.percent_grade, persistent_grade.letter_grade, - persistent_grade.letter_grade != u'' + persistent_grade.letter_grade != '' ) @staticmethod @@ -221,7 +216,7 @@ class CourseGradeFactory(object): ) log.info( - u'Grades: Update, %s, User: %s, %s, persisted: %s', + 'Grades: Update, %s, User: %s, %s, persisted: %s', course_data.full_string(), user.id, course_grade, should_persist, ) diff --git a/lms/djangoapps/grades/events.py b/lms/djangoapps/grades/events.py index e4eb6729659..697674ee976 100644 --- a/lms/djangoapps/grades/events.py +++ b/lms/djangoapps/grades/events.py @@ -1,9 +1,6 @@ """ Emits course grade events. """ - - -import six from crum import get_current_user from eventtracking import tracker @@ -15,13 +12,13 @@ from common.djangoapps.track.event_transaction_utils import ( set_event_transaction_type ) -COURSE_GRADE_CALCULATED = u'edx.grades.course.grade_calculated' -GRADES_OVERRIDE_EVENT_TYPE = u'edx.grades.problem.score_overridden' -GRADES_RESCORE_EVENT_TYPE = u'edx.grades.problem.rescored' -PROBLEM_SUBMITTED_EVENT_TYPE = u'edx.grades.problem.submitted' -STATE_DELETED_EVENT_TYPE = u'edx.grades.problem.state_deleted' -SUBSECTION_OVERRIDE_EVENT_TYPE = u'edx.grades.subsection.score_overridden' -SUBSECTION_GRADE_CALCULATED = u'edx.grades.subsection.grade_calculated' +COURSE_GRADE_CALCULATED = 'edx.grades.course.grade_calculated' +GRADES_OVERRIDE_EVENT_TYPE = 'edx.grades.problem.score_overridden' +GRADES_RESCORE_EVENT_TYPE = 'edx.grades.problem.rescored' +PROBLEM_SUBMITTED_EVENT_TYPE = 'edx.grades.problem.submitted' +STATE_DELETED_EVENT_TYPE = 'edx.grades.problem.state_deleted' +SUBSECTION_OVERRIDE_EVENT_TYPE = 'edx.grades.subsection.score_overridden' +SUBSECTION_GRADE_CALCULATED = 'edx.grades.subsection.grade_calculated' def grade_updated(**kwargs): @@ -41,13 +38,13 @@ def grade_updated(**kwargs): root_id = create_new_event_transaction_id() set_event_transaction_type(PROBLEM_SUBMITTED_EVENT_TYPE) tracker.emit( - six.text_type(PROBLEM_SUBMITTED_EVENT_TYPE), + str(PROBLEM_SUBMITTED_EVENT_TYPE), { - 'user_id': six.text_type(kwargs['user_id']), - 'course_id': six.text_type(kwargs['course_id']), - 'problem_id': six.text_type(kwargs['usage_id']), - 'event_transaction_id': six.text_type(root_id), - 'event_transaction_type': six.text_type(PROBLEM_SUBMITTED_EVENT_TYPE), + 'user_id': str(kwargs['user_id']), + 'course_id': str(kwargs['course_id']), + 'problem_id': str(kwargs['usage_id']), + 'event_transaction_id': str(root_id), + 'event_transaction_type': str(PROBLEM_SUBMITTED_EVENT_TYPE), 'weighted_earned': kwargs.get('weighted_earned'), 'weighted_possible': kwargs.get('weighted_possible'), } @@ -57,31 +54,31 @@ def grade_updated(**kwargs): current_user = get_current_user() instructor_id = getattr(current_user, 'id', None) tracker.emit( - six.text_type(root_type), + str(root_type), { - 'course_id': six.text_type(kwargs['course_id']), - 'user_id': six.text_type(kwargs['user_id']), - 'problem_id': six.text_type(kwargs['usage_id']), + 'course_id': str(kwargs['course_id']), + 'user_id': str(kwargs['user_id']), + 'problem_id': str(kwargs['usage_id']), 'new_weighted_earned': kwargs.get('weighted_earned'), 'new_weighted_possible': kwargs.get('weighted_possible'), 'only_if_higher': kwargs.get('only_if_higher'), - 'instructor_id': six.text_type(instructor_id), - 'event_transaction_id': six.text_type(get_event_transaction_id()), - 'event_transaction_type': six.text_type(root_type), + 'instructor_id': str(instructor_id), + 'event_transaction_id': str(get_event_transaction_id()), + 'event_transaction_type': str(root_type), } ) elif root_type in [SUBSECTION_OVERRIDE_EVENT_TYPE]: tracker.emit( - six.text_type(root_type), + str(root_type), { - 'course_id': six.text_type(kwargs['course_id']), - 'user_id': six.text_type(kwargs['user_id']), - 'problem_id': six.text_type(kwargs['usage_id']), + 'course_id': str(kwargs['course_id']), + 'user_id': str(kwargs['user_id']), + 'problem_id': str(kwargs['usage_id']), 'only_if_higher': kwargs.get('only_if_higher'), 'override_deleted': kwargs.get('score_deleted', False), - 'event_transaction_id': six.text_type(get_event_transaction_id()), - 'event_transaction_type': six.text_type(root_type), + 'event_transaction_id': str(get_event_transaction_id()), + 'event_transaction_type': str(root_type), } ) @@ -98,19 +95,19 @@ def subsection_grade_calculated(subsection_grade): tracker.emit( event_name, { - 'user_id': six.text_type(subsection_grade.user_id), - 'course_id': six.text_type(subsection_grade.course_id), - 'block_id': six.text_type(subsection_grade.usage_key), - 'course_version': six.text_type(subsection_grade.course_version), + 'user_id': str(subsection_grade.user_id), + 'course_id': str(subsection_grade.course_id), + 'block_id': str(subsection_grade.usage_key), + 'course_version': str(subsection_grade.course_version), 'weighted_total_earned': subsection_grade.earned_all, 'weighted_total_possible': subsection_grade.possible_all, 'weighted_graded_earned': subsection_grade.earned_graded, 'weighted_graded_possible': subsection_grade.possible_graded, - 'first_attempted': six.text_type(subsection_grade.first_attempted), - 'subtree_edited_timestamp': six.text_type(subsection_grade.subtree_edited_timestamp), - 'event_transaction_id': six.text_type(get_event_transaction_id()), - 'event_transaction_type': six.text_type(get_event_transaction_type()), - 'visible_blocks_hash': six.text_type(subsection_grade.visible_blocks_id), + 'first_attempted': str(subsection_grade.first_attempted), + 'subtree_edited_timestamp': str(subsection_grade.subtree_edited_timestamp), + 'event_transaction_id': str(get_event_transaction_id()), + 'event_transaction_type': str(get_event_transaction_type()), + 'visible_blocks_hash': str(subsection_grade.visible_blocks_id), } ) @@ -127,14 +124,14 @@ def course_grade_calculated(course_grade): tracker.emit( event_name, { - 'user_id': six.text_type(course_grade.user_id), - 'course_id': six.text_type(course_grade.course_id), - 'course_version': six.text_type(course_grade.course_version), + 'user_id': str(course_grade.user_id), + 'course_id': str(course_grade.course_id), + 'course_version': str(course_grade.course_version), 'percent_grade': course_grade.percent_grade, - 'letter_grade': six.text_type(course_grade.letter_grade), - 'course_edited_timestamp': six.text_type(course_grade.course_edited_timestamp), - 'event_transaction_id': six.text_type(get_event_transaction_id()), - 'event_transaction_type': six.text_type(get_event_transaction_type()), - 'grading_policy_hash': six.text_type(course_grade.grading_policy_hash), + 'letter_grade': str(course_grade.letter_grade), + 'course_edited_timestamp': str(course_grade.course_edited_timestamp), + 'event_transaction_id': str(get_event_transaction_id()), + 'event_transaction_type': str(get_event_transaction_type()), + 'grading_policy_hash': str(course_grade.grading_policy_hash), } ) diff --git a/lms/djangoapps/grades/management/commands/compute_grades.py b/lms/djangoapps/grades/management/commands/compute_grades.py index 75cc8826b05..1fdbc45931c 100644 --- a/lms/djangoapps/grades/management/commands/compute_grades.py +++ b/lms/djangoapps/grades/management/commands/compute_grades.py @@ -8,12 +8,11 @@ import logging from django.core.management.base import BaseCommand +from lms.djangoapps.grades import tasks from lms.djangoapps.grades.config.models import ComputeGradesSetting from openedx.core.lib.command_utils import get_mutually_exclusive_required_option, parse_course_keys from xmodule.modulestore.django import modulestore -from lms.djangoapps.grades import tasks - log = logging.getLogger(__name__) @@ -107,7 +106,7 @@ class Command(BaseCommand): for task_arg_tuple in tasks._course_task_args(course_key, **options): # lint-amnesty, pylint: disable=protected-access all_args.append(task_arg_tuple) - all_args.sort(key=lambda x: hashlib.md5('{!r}'.format(x).encode('utf-8')).digest()) + all_args.sort(key=lambda x: hashlib.md5(f'{x!r}'.encode('utf-8')).digest()) for args in all_args: yield { diff --git a/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py b/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py index 3a2bc8de732..4161e668065 100644 --- a/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py +++ b/lms/djangoapps/grades/management/commands/recalculate_subsection_grades.py @@ -7,18 +7,17 @@ in the specified time range. import logging from datetime import datetime -import six from django.core.management.base import BaseCommand, CommandError from pytz import utc from submissions.models import Submission +from common.djangoapps.student.models import user_by_anonymous_id +from common.djangoapps.track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type +from common.djangoapps.util.date_utils import to_timestamp from lms.djangoapps.courseware.models import StudentModule from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum from lms.djangoapps.grades.events import PROBLEM_SUBMITTED_EVENT_TYPE from lms.djangoapps.grades.tasks import recalculate_subsection_grade_v3 -from common.djangoapps.student.models import user_by_anonymous_id -from common.djangoapps.track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type -from common.djangoapps.util.date_utils import to_timestamp log = logging.getLogger(__name__) @@ -67,12 +66,12 @@ class Command(BaseCommand): continue task_args = { "user_id": record.student_id, - "course_id": six.text_type(record.course_id), - "usage_id": six.text_type(record.module_state_key), + "course_id": str(record.course_id), + "usage_id": str(record.module_state_key), "only_if_higher": False, "expected_modified_time": to_timestamp(record.modified), "score_deleted": False, - "event_transaction_id": six.text_type(event_transaction_id), + "event_transaction_id": str(event_transaction_id), "event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE, "score_db_table": ScoreDatabaseTableEnum.courseware_student_module, } @@ -86,12 +85,12 @@ class Command(BaseCommand): task_args = { "user_id": user_by_anonymous_id(record.student_item.student_id).id, "anonymous_user_id": record.student_item.student_id, - "course_id": six.text_type(record.student_item.course_id), - "usage_id": six.text_type(record.student_item.item_id), + "course_id": str(record.student_item.course_id), + "usage_id": str(record.student_item.item_id), "only_if_higher": False, "expected_modified_time": to_timestamp(record.created_at), "score_deleted": False, - "event_transaction_id": six.text_type(event_transaction_id), + "event_transaction_id": str(event_transaction_id), "event_transaction_type": PROBLEM_SUBMITTED_EVENT_TYPE, "score_db_table": ScoreDatabaseTableEnum.submissions, } diff --git a/lms/djangoapps/grades/management/commands/tests/test_compute_grades.py b/lms/djangoapps/grades/management/commands/tests/test_compute_grades.py index edba5e8677d..bb4d6b9220f 100644 --- a/lms/djangoapps/grades/management/commands/tests/test_compute_grades.py +++ b/lms/djangoapps/grades/management/commands/tests/test_compute_grades.py @@ -3,18 +3,16 @@ Tests for compute_grades management command. """ # pylint: disable=protected-access +from unittest.mock import ANY, patch -import pytest import ddt -import six -from six.moves import range +import pytest from django.contrib.auth import get_user_model from django.core.management import CommandError, call_command -from mock import ANY, patch +from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.grades.config.models import ComputeGradesSetting from lms.djangoapps.grades.management.commands import compute_grades -from common.djangoapps.student.models import CourseEnrollment from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -29,12 +27,12 @@ class TestComputeGrades(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TestComputeGrades, cls).setUpClass() + super().setUpClass() cls.command = compute_grades.Command() cls.courses = [CourseFactory.create() for _ in range(cls.num_courses)] - cls.course_keys = [six.text_type(course.id) for course in cls.courses] - cls.users = [get_user_model().objects.create(username='user{}'.format(idx)) for idx in range(cls.num_users)] + cls.course_keys = [str(course.id) for course in cls.courses] + cls.users = [get_user_model().objects.create(username=f'user{idx}') for idx in range(cls.num_users)] for user in cls.users: for course in cls.courses: @@ -42,11 +40,11 @@ class TestComputeGrades(SharedModuleStoreTestCase): def test_select_all_courses(self): courses = self.command._get_course_keys({'all_courses': True}) - assert set(six.text_type(course) for course in courses) == set(self.course_keys) + assert {str(course) for course in courses} == set(self.course_keys) def test_specify_courses(self): courses = self.command._get_course_keys({'courses': [self.course_keys[0], self.course_keys[1], 'd/n/e']}) - assert [six.text_type(course) for course in courses] == [self.course_keys[0], self.course_keys[1], 'd/n/e'] + assert [str(course) for course in courses] == [self.course_keys[0], self.course_keys[1], 'd/n/e'] def test_selecting_invalid_course(self): with pytest.raises(CommandError): @@ -55,7 +53,7 @@ class TestComputeGrades(SharedModuleStoreTestCase): def test_from_settings(self): ComputeGradesSetting.objects.create(course_ids=" ".join(self.course_keys)) courses = self.command._get_course_keys({'from_settings': True}) - assert set(six.text_type(course) for course in courses) == set(self.course_keys) + assert {str(course) for course in courses} == set(self.course_keys) # test that --from_settings always uses the latest setting ComputeGradesSetting.objects.create(course_ids='badcoursekey') with pytest.raises(CommandError): diff --git a/lms/djangoapps/grades/management/commands/tests/test_recalculate_learner_grades.py b/lms/djangoapps/grades/management/commands/tests/test_recalculate_learner_grades.py index df257a91550..6bbe8d24874 100644 --- a/lms/djangoapps/grades/management/commands/tests/test_recalculate_learner_grades.py +++ b/lms/djangoapps/grades/management/commands/tests/test_recalculate_learner_grades.py @@ -4,16 +4,15 @@ Tests for recalculate_learner_grades management command. from tempfile import NamedTemporaryFile +from unittest import mock -import mock - +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from lms.djangoapps.grades.management.commands import recalculate_learner_grades from lms.djangoapps.grades.tests.test_tasks import HasCourseWithProblemsMixin -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory -DATE_FORMAT = u"%Y-%m-%d %H:%M" +DATE_FORMAT = "%Y-%m-%d %H:%M" class TestRecalculateLearnerGrades(HasCourseWithProblemsMixin, ModuleStoreTestCase): @@ -22,7 +21,7 @@ class TestRecalculateLearnerGrades(HasCourseWithProblemsMixin, ModuleStoreTestCa """ def setUp(self): - super(TestRecalculateLearnerGrades, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.command = recalculate_learner_grades.Command() self.course1 = CourseFactory.create() @@ -50,7 +49,7 @@ class TestRecalculateLearnerGrades(HasCourseWithProblemsMixin, ModuleStoreTestCa with NamedTemporaryFile() as csv: csv.write(b"course_id,user_id\n") csv.writelines( - "{},{}\n".format(course, user).encode() + f"{course},{user}\n".encode() for user, course in self.user_course_pairs ) csv.seek(0) diff --git a/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py b/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py index c93cd4659e1..f835bcc2f0a 100644 --- a/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py +++ b/lms/djangoapps/grades/management/commands/tests/test_recalculate_subsection_grades.py @@ -4,22 +4,21 @@ Tests for reset_grades management command. from datetime import datetime +from unittest.mock import MagicMock, patch import ddt -import six from django.conf import settings -from mock import MagicMock, patch from opaque_keys.edx.keys import CourseKey from pytz import utc +from common.djangoapps.track.event_transaction_utils import get_event_transaction_id +from common.djangoapps.util.date_utils import to_timestamp from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum from lms.djangoapps.grades.management.commands import recalculate_subsection_grades from lms.djangoapps.grades.tests.test_tasks import HasCourseWithProblemsMixin -from common.djangoapps.track.event_transaction_utils import get_event_transaction_id -from common.djangoapps.util.date_utils import to_timestamp from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -DATE_FORMAT = u"%Y-%m-%d %H:%M" +DATE_FORMAT = "%Y-%m-%d %H:%M" @patch.dict(settings.FEATURES, {'PERSISTENT_GRADES_ENABLED_FOR_ALL_TESTS': False}) @@ -30,7 +29,7 @@ class TestRecalculateSubsectionGrades(HasCourseWithProblemsMixin, ModuleStoreTes """ def setUp(self): - super(TestRecalculateSubsectionGrades, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.command = recalculate_subsection_grades.Command() @patch('lms.djangoapps.grades.management.commands.recalculate_subsection_grades.Submission') @@ -67,13 +66,13 @@ class TestRecalculateSubsectionGrades(HasCourseWithProblemsMixin, ModuleStoreTes self.command.handle(modified_start='2016-08-25 16:42', modified_end='2018-08-25 16:44') kwargs = { "user_id": "ID", - "course_id": u'course-v1:x+y+z', - "usage_id": u'abc', + "course_id": 'course-v1:x+y+z', + "usage_id": 'abc', "only_if_higher": False, "expected_modified_time": to_timestamp(utc.localize(datetime.strptime('2016-08-23 16:43', DATE_FORMAT))), "score_deleted": False, - "event_transaction_id": six.text_type(get_event_transaction_id()), - "event_transaction_type": u'edx.grades.problem.submitted', + "event_transaction_id": str(get_event_transaction_id()), + "event_transaction_type": 'edx.grades.problem.submitted', "score_db_table": score_db_table, } diff --git a/lms/djangoapps/grades/migrations/0001_initial.py b/lms/djangoapps/grades/migrations/0001_initial.py index 313c647f0f2..aac99bb659a 100644 --- a/lms/djangoapps/grades/migrations/0001_initial.py +++ b/lms/djangoapps/grades/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.utils.timezone import model_utils.fields from django.db import migrations, models @@ -47,6 +44,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='persistentsubsectiongrade', - unique_together=set([('course_id', 'user_id', 'usage_key')]), + unique_together={('course_id', 'user_id', 'usage_key')}, ), ] diff --git a/lms/djangoapps/grades/migrations/0002_rename_last_edited_field.py b/lms/djangoapps/grades/migrations/0002_rename_last_edited_field.py index 01693ec5079..1ececcb2206 100644 --- a/lms/djangoapps/grades/migrations/0002_rename_last_edited_field.py +++ b/lms/djangoapps/grades/migrations/0002_rename_last_edited_field.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/lms/djangoapps/grades/migrations/0003_coursepersistentgradesflag_persistentgradesenabledflag.py b/lms/djangoapps/grades/migrations/0003_coursepersistentgradesflag_persistentgradesenabledflag.py index 419e388ac52..06478b9081f 100644 --- a/lms/djangoapps/grades/migrations/0003_coursepersistentgradesflag_persistentgradesenabledflag.py +++ b/lms/djangoapps/grades/migrations/0003_coursepersistentgradesflag_persistentgradesenabledflag.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/grades/migrations/0004_visibleblocks_course_id.py b/lms/djangoapps/grades/migrations/0004_visibleblocks_course_id.py index 3119eaacb92..63a2d88ba51 100644 --- a/lms/djangoapps/grades/migrations/0004_visibleblocks_course_id.py +++ b/lms/djangoapps/grades/migrations/0004_visibleblocks_course_id.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models from opaque_keys.edx.django.models import CourseKeyField from opaque_keys.edx.keys import CourseKey diff --git a/lms/djangoapps/grades/migrations/0005_multiple_course_flags.py b/lms/djangoapps/grades/migrations/0005_multiple_course_flags.py index 4928ff6c78e..8eaa297eb3e 100644 --- a/lms/djangoapps/grades/migrations/0005_multiple_course_flags.py +++ b/lms/djangoapps/grades/migrations/0005_multiple_course_flags.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models from opaque_keys.edx.django.models import CourseKeyField diff --git a/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py b/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py index 232b8013344..182e4e6e9f8 100644 --- a/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py +++ b/lms/djangoapps/grades/migrations/0006_persistent_course_grades.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.utils.timezone import model_utils.fields from django.db import migrations, models @@ -33,6 +30,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='persistentcoursegrade', - unique_together=set([('course_id', 'user_id')]), + unique_together={('course_id', 'user_id')}, ), ] diff --git a/lms/djangoapps/grades/migrations/0007_add_passed_timestamp_column.py b/lms/djangoapps/grades/migrations/0007_add_passed_timestamp_column.py index 9fba12b146e..e7b833bea05 100644 --- a/lms/djangoapps/grades/migrations/0007_add_passed_timestamp_column.py +++ b/lms/djangoapps/grades/migrations/0007_add_passed_timestamp_column.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models @@ -18,6 +15,6 @@ class Migration(migrations.Migration): ), migrations.AlterIndexTogether( name='persistentcoursegrade', - index_together=set([('passed_timestamp', 'course_id')]), + index_together={('passed_timestamp', 'course_id')}, ), ] diff --git a/lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py b/lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py index ca83336310e..cbef211b24c 100644 --- a/lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py +++ b/lms/djangoapps/grades/migrations/0008_persistentsubsectiongrade_first_attempted.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/lms/djangoapps/grades/migrations/0009_auto_20170111_1507.py b/lms/djangoapps/grades/migrations/0009_auto_20170111_1507.py index adfdabad60d..36999513dee 100644 --- a/lms/djangoapps/grades/migrations/0009_auto_20170111_1507.py +++ b/lms/djangoapps/grades/migrations/0009_auto_20170111_1507.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models @@ -13,10 +10,10 @@ class Migration(migrations.Migration): operations = [ migrations.AlterIndexTogether( name='persistentcoursegrade', - index_together=set([('passed_timestamp', 'course_id'), ('modified', 'course_id')]), + index_together={('passed_timestamp', 'course_id'), ('modified', 'course_id')}, ), migrations.AlterIndexTogether( name='persistentsubsectiongrade', - index_together=set([('modified', 'course_id', 'usage_key')]), + index_together={('modified', 'course_id', 'usage_key')}, ), ] diff --git a/lms/djangoapps/grades/migrations/0010_auto_20170112_1156.py b/lms/djangoapps/grades/migrations/0010_auto_20170112_1156.py index 22315052a27..bcc89a3962f 100644 --- a/lms/djangoapps/grades/migrations/0010_auto_20170112_1156.py +++ b/lms/djangoapps/grades/migrations/0010_auto_20170112_1156.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models @@ -13,6 +10,6 @@ class Migration(migrations.Migration): operations = [ migrations.AlterIndexTogether( name='persistentsubsectiongrade', - index_together=set([('modified', 'course_id', 'usage_key'), ('first_attempted', 'course_id', 'user_id')]), + index_together={('modified', 'course_id', 'usage_key'), ('first_attempted', 'course_id', 'user_id')}, ), ] diff --git a/lms/djangoapps/grades/migrations/0011_null_edited_time.py b/lms/djangoapps/grades/migrations/0011_null_edited_time.py index 3ee67cb2755..f60d82ad610 100644 --- a/lms/djangoapps/grades/migrations/0011_null_edited_time.py +++ b/lms/djangoapps/grades/migrations/0011_null_edited_time.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/lms/djangoapps/grades/migrations/0012_computegradessetting.py b/lms/djangoapps/grades/migrations/0012_computegradessetting.py index ceb7a7486a4..b3bc1bd8e92 100644 --- a/lms/djangoapps/grades/migrations/0012_computegradessetting.py +++ b/lms/djangoapps/grades/migrations/0012_computegradessetting.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - import django.db.models.deletion from django.conf import settings from django.db import migrations, models @@ -21,7 +18,7 @@ class Migration(migrations.Migration): ('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')), ('enabled', models.BooleanField(default=False, verbose_name='Enabled')), ('batch_size', models.IntegerField(default=100)), - ('course_ids', models.TextField(help_text=u'Whitespace-separated list of course keys for which to compute grades.')), + ('course_ids', models.TextField(help_text='Whitespace-separated list of course keys for which to compute grades.')), ('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')), ], ), diff --git a/lms/djangoapps/grades/migrations/0013_persistentsubsectiongradeoverride.py b/lms/djangoapps/grades/migrations/0013_persistentsubsectiongradeoverride.py index ce0f9cdefdf..08343a2a544 100644 --- a/lms/djangoapps/grades/migrations/0013_persistentsubsectiongradeoverride.py +++ b/lms/djangoapps/grades/migrations/0013_persistentsubsectiongradeoverride.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from django.db import migrations, models diff --git a/lms/djangoapps/grades/migrations/0014_persistentsubsectiongradeoverridehistory.py b/lms/djangoapps/grades/migrations/0014_persistentsubsectiongradeoverridehistory.py index 4a0554277cb..bc7f12093f2 100644 --- a/lms/djangoapps/grades/migrations/0014_persistentsubsectiongradeoverridehistory.py +++ b/lms/djangoapps/grades/migrations/0014_persistentsubsectiongradeoverridehistory.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.16 on 2018-11-27 20:53 @@ -20,8 +19,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('override_id', models.IntegerField(db_index=True)), - ('feature', models.CharField(choices=[(u'PROCTORING', u'proctoring'), (u'GRADEBOOK', u'gradebook')], default=u'PROCTORING', max_length=32)), - ('action', models.CharField(choices=[(u'CREATEORUPDATE', u'create_or_update'), (u'DELETE', u'delete')], default=u'CREATEORUPDATE', max_length=32)), + ('feature', models.CharField(choices=[('PROCTORING', 'proctoring'), ('GRADEBOOK', 'gradebook')], default='PROCTORING', max_length=32)), + ('action', models.CharField(choices=[('CREATEORUPDATE', 'create_or_update'), ('DELETE', 'delete')], default='CREATEORUPDATE', max_length=32)), ('comments', models.CharField(blank=True, max_length=300, null=True)), ('created', models.DateTimeField(auto_now_add=True, db_index=True)), ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), diff --git a/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py b/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py index 3def73d57ff..75522833ae5 100644 --- a/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py +++ b/lms/djangoapps/grades/migrations/0015_historicalpersistentsubsectiongradeoverride.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.20 on 2019-06-05 13:59 diff --git a/lms/djangoapps/grades/migrations/0016_auto_20190703_1446.py b/lms/djangoapps/grades/migrations/0016_auto_20190703_1446.py index 2e31eda302a..b0ea0422ba2 100644 --- a/lms/djangoapps/grades/migrations/0016_auto_20190703_1446.py +++ b/lms/djangoapps/grades/migrations/0016_auto_20190703_1446.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.21 on 2019-07-03 14:46 diff --git a/lms/djangoapps/grades/migrations/0017_delete_manual_psgoverride_table.py b/lms/djangoapps/grades/migrations/0017_delete_manual_psgoverride_table.py index 32671eae73e..60550736ea7 100644 --- a/lms/djangoapps/grades/migrations/0017_delete_manual_psgoverride_table.py +++ b/lms/djangoapps/grades/migrations/0017_delete_manual_psgoverride_table.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Generated by Django 1.11.27 on 2020-01-07 16:52 diff --git a/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py b/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py index 56436fdfc37..c64149216b5 100644 --- a/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py +++ b/lms/djangoapps/grades/migrations/0018_add_waffle_flag_defaults.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import absolute_import, unicode_literals - from django.db import migrations from lms.djangoapps.grades.config.waffle import ( diff --git a/lms/djangoapps/grades/models.py b/lms/djangoapps/grades/models.py index 27ef33fa586..db5f1dc4518 100644 --- a/lms/djangoapps/grades/models.py +++ b/lms/djangoapps/grades/models.py @@ -15,7 +15,6 @@ from base64 import b64encode from collections import defaultdict, namedtuple from hashlib import sha1 -import six from django.apps import apps from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user, unused-import from django.db import models @@ -26,7 +25,6 @@ from model_utils.models import TimeStampedModel from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField from opaque_keys.edx.keys import CourseKey, UsageKey from simple_history.models import HistoricalRecords -from six.moves import map from lms.djangoapps.courseware.fields import UnsignedBigIntAutoField from lms.djangoapps.grades import constants, events # lint-amnesty, pylint: disable=unused-import @@ -42,7 +40,7 @@ BLOCK_RECORD_LIST_VERSION = 1 BlockRecord = namedtuple('BlockRecord', ['locator', 'weight', 'raw_possible', 'graded']) -class BlockRecordList(object): +class BlockRecordList: """ An immutable ordered list of BlockRecord objects. """ @@ -89,11 +87,11 @@ class BlockRecordList(object): """ list_of_block_dicts = [block._asdict() for block in self.blocks] for block_dict in list_of_block_dicts: - block_dict['locator'] = six.text_type(block_dict['locator']) # BlockUsageLocator is not json-serializable + block_dict['locator'] = str(block_dict['locator']) # BlockUsageLocator is not json-serializable data = { - u'blocks': list_of_block_dicts, - u'course_key': six.text_type(self.course_key), - u'version': self.version, + 'blocks': list_of_block_dicts, + 'course_key': str(self.course_key), + 'version': self.version, } return json.dumps( data, @@ -144,16 +142,16 @@ class VisibleBlocks(models.Model): hashed = models.CharField(max_length=100, unique=True) course_id = CourseKeyField(blank=False, max_length=255, db_index=True) - _CACHE_NAMESPACE = u"grades.models.VisibleBlocks" + _CACHE_NAMESPACE = "grades.models.VisibleBlocks" - class Meta(object): + class Meta: app_label = "grades" def __str__(self): """ String representation of this model. """ - return u"VisibleBlocks object - hash:{}, raw json:'{}'".format(self.hashed, self.blocks_json) + return f"VisibleBlocks object - hash:{self.hashed}, raw json:'{self.blocks_json}'" @property def blocks(self): @@ -200,7 +198,7 @@ class VisibleBlocks(models.Model): else: model, _ = cls.objects.get_or_create( hashed=blocks.hash_value, - defaults={u'blocks_json': blocks.json_value, u'course_id': blocks.course_key}, + defaults={'blocks_json': blocks.json_value, 'course_id': blocks.course_key}, ) return model @@ -261,7 +259,7 @@ class VisibleBlocks(models.Model): @classmethod def _cache_key(cls, user_id, course_key): - return u"visible_blocks_cache.{}.{}".format(course_key, user_id) + return f"visible_blocks_cache.{course_key}.{user_id}" @python_2_unicode_compatible @@ -272,7 +270,7 @@ class PersistentSubsectionGrade(TimeStampedModel): .. no_pii: """ - class Meta(object): + class Meta: app_label = "grades" unique_together = [ # * Specific grades can be pulled using all three columns, @@ -304,8 +302,8 @@ class PersistentSubsectionGrade(TimeStampedModel): usage_key = UsageKeyField(blank=False, max_length=255) # Information relating to the state of content when grade was calculated - subtree_edited_timestamp = models.DateTimeField(u'Last content edit timestamp', blank=True, null=True) - course_version = models.CharField(u'Guid of latest course version', blank=True, max_length=255) + subtree_edited_timestamp = models.DateTimeField('Last content edit timestamp', blank=True, null=True) + course_version = models.CharField('Guid of latest course version', blank=True, max_length=255) # earned/possible refers to the number of points achieved and available to achieve. # graded refers to the subset of all problems that are marked as being graded. @@ -323,7 +321,7 @@ class PersistentSubsectionGrade(TimeStampedModel): visible_blocks = models.ForeignKey(VisibleBlocks, db_column='visible_blocks_hash', to_field='hashed', on_delete=models.CASCADE) - _CACHE_NAMESPACE = u'grades.models.PersistentSubsectionGrade' + _CACHE_NAMESPACE = 'grades.models.PersistentSubsectionGrade' @property def full_usage_key(self): @@ -341,7 +339,7 @@ class PersistentSubsectionGrade(TimeStampedModel): Returns a string representation of this model. """ return ( - u"{} user: {}, course version: {}, subsection: {} ({}). {}/{} graded, {}/{} all, first_attempted: {}" + "{} user: {}, course version: {}, subsection: {} ({}). {}/{} graded, {}/{} all, first_attempted: {}" ).format( type(self).__name__, self.user_id, @@ -441,8 +439,8 @@ class PersistentSubsectionGrade(TimeStampedModel): # TODO: Remove as part of EDUCATOR-4602. if str(usage_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Created/updated grade ***{}*** for user ***{}*** in course ***{}***' - u'for subsection ***{}*** with default params ***{}***' + log.info('Created/updated grade ***{}*** for user ***{}*** in course ***{}***' + 'for subsection ***{}*** with default params ***{}***' .format(grade, user_id, usage_key.course_key, usage_key, params)) grade.override = PersistentSubsectionGradeOverride.get_override(user_id, usage_key) @@ -504,7 +502,7 @@ class PersistentSubsectionGrade(TimeStampedModel): @classmethod def _cache_key(cls, course_id): - return u"subsection_grades_cache.{}".format(course_id) + return f"subsection_grades_cache.{course_id}" @python_2_unicode_compatible @@ -515,7 +513,7 @@ class PersistentCourseGrade(TimeStampedModel): .. no_pii: """ - class Meta(object): + class Meta: app_label = "grades" # Indices: # (course_id, user_id) for individual grades @@ -538,30 +536,30 @@ class PersistentCourseGrade(TimeStampedModel): course_id = CourseKeyField(blank=False, max_length=255) # Information relating to the state of content when grade was calculated - course_edited_timestamp = models.DateTimeField(u'Last content edit timestamp', blank=True, null=True) - course_version = models.CharField(u'Course content version identifier', blank=True, max_length=255) - grading_policy_hash = models.CharField(u'Hash of grading policy', blank=False, max_length=255) + course_edited_timestamp = models.DateTimeField('Last content edit timestamp', blank=True, null=True) + course_version = models.CharField('Course content version identifier', blank=True, max_length=255) + grading_policy_hash = models.CharField('Hash of grading policy', blank=False, max_length=255) # Information about the course grade itself percent_grade = models.FloatField(blank=False) - letter_grade = models.CharField(u'Letter grade for course', blank=False, max_length=255) + letter_grade = models.CharField('Letter grade for course', blank=False, max_length=255) # Information related to course completion - passed_timestamp = models.DateTimeField(u'Date learner earned a passing grade', blank=True, null=True) + passed_timestamp = models.DateTimeField('Date learner earned a passing grade', blank=True, null=True) - _CACHE_NAMESPACE = u"grades.models.PersistentCourseGrade" + _CACHE_NAMESPACE = "grades.models.PersistentCourseGrade" def __str__(self): """ Returns a string representation of this model. """ - return u', '.join([ - u"{} user: {}".format(type(self).__name__, self.user_id), - u"course version: {}".format(self.course_version), - u"grading policy: {}".format(self.grading_policy_hash), - u"percent grade: {}%".format(self.percent_grade), - u"letter grade: {}".format(self.letter_grade), - u"passed timestamp: {}".format(self.passed_timestamp), + return ', '.join([ + "{} user: {}".format(type(self).__name__, self.user_id), + f"course version: {self.course_version}", + f"grading policy: {self.grading_policy_hash}", + f"percent grade: {self.percent_grade}%", + f"letter grade: {self.letter_grade}", + f"passed timestamp: {self.passed_timestamp}", ]) @classmethod @@ -637,7 +635,7 @@ class PersistentCourseGrade(TimeStampedModel): @classmethod def _cache_key(cls, course_id): - return u"grades_cache.{}".format(course_id) + return f"grades_cache.{course_id}" @staticmethod def _emit_grade_calculated_event(grade): @@ -651,7 +649,7 @@ class PersistentSubsectionGradeOverride(models.Model): .. no_pii: """ - class Meta(object): + class Meta: app_label = "grades" grade = models.OneToOneField(PersistentSubsectionGrade, related_name='override', on_delete=models.CASCADE) @@ -671,7 +669,7 @@ class PersistentSubsectionGradeOverride(models.Model): # store the reason for the override override_reason = models.CharField(max_length=300, blank=True, null=True) - _CACHE_NAMESPACE = u"grades.models.PersistentSubsectionGradeOverride" + _CACHE_NAMESPACE = "grades.models.PersistentSubsectionGradeOverride" # This is necessary because CMS does not install the grades app, but it # imports this models code. Simple History will attempt to connect to the installed @@ -681,12 +679,12 @@ class PersistentSubsectionGradeOverride(models.Model): _history_user = None def __str__(self): - return u', '.join([ - u"{}".format(type(self).__name__), - u"earned_all_override: {}".format(self.earned_all_override), - u"possible_all_override: {}".format(self.possible_all_override), - u"earned_graded_override: {}".format(self.earned_graded_override), - u"possible_graded_override: {}".format(self.possible_graded_override), + return ', '.join([ + "{}".format(type(self).__name__), + f"earned_all_override: {self.earned_all_override}", + f"possible_all_override: {self.possible_all_override}", + f"earned_graded_override: {self.earned_graded_override}", + f"possible_graded_override: {self.possible_graded_override}", ]) def get_history(self): @@ -731,12 +729,12 @@ class PersistentSubsectionGradeOverride(models.Model): # TODO: Remove as part of EDUCATOR-4602. if str(subsection_grade_model.course_id) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Creating override for user ***{}*** for PersistentSubsectionGrade' - u'***{}*** with override data ***{}*** and derived grade_defaults ***{}***.' + log.info('Creating override for user ***{}*** for PersistentSubsectionGrade' + '***{}*** with override data ***{}*** and derived grade_defaults ***{}***.' .format(requesting_user, subsection_grade_model, override_data, grade_defaults)) try: override = PersistentSubsectionGradeOverride.objects.get(grade=subsection_grade_model) - for key, value in six.iteritems(grade_defaults): + for key, value in grade_defaults.items(): setattr(override, key, value) except PersistentSubsectionGradeOverride.DoesNotExist: override = PersistentSubsectionGradeOverride(grade=subsection_grade_model, **grade_defaults) diff --git a/lms/djangoapps/grades/rest_api/serializers.py b/lms/djangoapps/grades/rest_api/serializers.py index 63abc9b27a4..c370788a67f 100644 --- a/lms/djangoapps/grades/rest_api/serializers.py +++ b/lms/djangoapps/grades/rest_api/serializers.py @@ -26,7 +26,7 @@ class GradingPolicySerializer(serializers.Serializer): # When the grader dictionary was missing keys, DRF v2 would default to None; # DRF v3 unhelpfully raises an exception. return dict( - super(GradingPolicySerializer, self).to_representation( # lint-amnesty, pylint: disable=super-with-arguments + super().to_representation( defaultdict(lambda: None, instance) ) ) diff --git a/lms/djangoapps/grades/rest_api/v1/gradebook_views.py b/lms/djangoapps/grades/rest_api/v1/gradebook_views.py index 23c3197b38f..e8a4224eb6e 100644 --- a/lms/djangoapps/grades/rest_api/v1/gradebook_views.py +++ b/lms/djangoapps/grades/rest_api/v1/gradebook_views.py @@ -8,10 +8,9 @@ from collections import namedtuple from contextlib import contextmanager from functools import wraps -import six from django.contrib.auth import get_user_model from django.core.cache import cache -from django.db.models import Case, Exists, F, OuterRef, When, Q +from django.db.models import Case, Exists, F, OuterRef, Q, When from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from opaque_keys import InvalidKeyError @@ -22,21 +21,32 @@ from rest_framework.response import Response from rest_framework.views import APIView from six import text_type +from common.djangoapps.student.auth import has_course_author_access +from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.student.roles import BulkRoleCache +from common.djangoapps.track.event_transaction_utils import ( + create_new_event_transaction_id, + get_event_transaction_id, + get_event_transaction_type, + set_event_transaction_type +) +from common.djangoapps.util.date_utils import to_timestamp +from lms.djangoapps.course_blocks.api import get_course_blocks from lms.djangoapps.courseware.courses import get_course_by_id from lms.djangoapps.grades.api import CourseGradeFactory, clear_prefetched_course_and_subsection_grades from lms.djangoapps.grades.api import constants as grades_constants from lms.djangoapps.grades.api import context as grades_context from lms.djangoapps.grades.api import events as grades_events -from lms.djangoapps.grades.api import is_writable_gradebook_enabled, prefetch_course_and_subsection_grades from lms.djangoapps.grades.api import gradebook_can_see_bulk_management as can_see_bulk_management +from lms.djangoapps.grades.api import is_writable_gradebook_enabled, prefetch_course_and_subsection_grades from lms.djangoapps.grades.course_data import CourseData from lms.djangoapps.grades.grade_utils import are_grades_frozen # TODO these imports break abstraction of the core Grades layer. This code needs # to be refactored so Gradebook views only access public Grades APIs. from lms.djangoapps.grades.models import ( - PersistentSubsectionGrade, - PersistentSubsectionGradeOverride, PersistentCourseGrade, + PersistentSubsectionGrade, + PersistentSubsectionGradeOverride ) from lms.djangoapps.grades.rest_api.serializers import ( StudentGradebookEntrySerializer, @@ -46,7 +56,6 @@ from lms.djangoapps.grades.rest_api.v1.utils import USER_MODEL, CourseEnrollment from lms.djangoapps.grades.subsection_grade import CreateSubsectionGrade from lms.djangoapps.grades.subsection_grade_factory import SubsectionGradeFactory from lms.djangoapps.grades.tasks import recalculate_subsection_grade_v3 -from lms.djangoapps.course_blocks.api import get_course_blocks from lms.djangoapps.program_enrollments.api import get_external_key_by_user_and_course from openedx.core.djangoapps.course_groups import cohorts from openedx.core.djangoapps.util.forms import to_bool @@ -58,16 +67,6 @@ from openedx.core.lib.api.view_utils import ( view_auth_classes ) from openedx.core.lib.cache_utils import request_cached -from common.djangoapps.student.auth import has_course_author_access -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.roles import BulkRoleCache -from common.djangoapps.track.event_transaction_utils import ( - create_new_event_transaction_id, - get_event_transaction_id, - get_event_transaction_type, - set_event_transaction_type -) -from common.djangoapps.util.date_utils import to_timestamp from xmodule.modulestore.django import modulestore from xmodule.util.misc import get_default_short_labeler @@ -326,7 +325,7 @@ class CourseGradingView(BaseCourseView): 'assignment_type': subsection.format, 'graded': subsection.graded, 'short_label': short_label, - 'module_id': text_type(subsection.location), + 'module_id': str(subsection.location), 'display_name': subsection.display_name, }) return subsections @@ -471,7 +470,7 @@ class GradebookView(GradeViewMixin, PaginatedAPIView): 'attempted': attempted, 'category': subsection_grade.format, 'label': short_label, - 'module_id': text_type(subsection_grade.location), + 'module_id': str(subsection_grade.location), 'percent': subsection_grade.percent_graded, 'score_earned': score_earned, 'score_possible': score_possible, @@ -496,7 +495,7 @@ class GradebookView(GradeViewMixin, PaginatedAPIView): user_entry['section_breakdown'] = breakdown user_entry['progress_page_url'] = reverse( 'student_progress', - kwargs=dict(course_id=text_type(course.id), student_id=user.id) + kwargs=dict(course_id=str(course.id), student_id=user.id) ) user_entry['user_id'] = user.id user_entry['full_name'] = user.profile.name @@ -813,7 +812,7 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView): user_id=requested_user_id, usage_id=requested_usage_id, success=False, - reason=text_type(exc) + reason=str(exc) )) continue @@ -829,8 +828,8 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView): subsection_grade_model = self._create_subsection_grade(user, course, subsection) # TODO: Remove as part of EDUCATOR-4602. if str(course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'PersistentSubsectionGrade ***{}*** created for' - u' subsection ***{}*** in course ***{}*** for user ***{}***.' + log.info('PersistentSubsectionGrade ***{}*** created for' + ' subsection ***{}*** in course ***{}*** for user ***{}***.' .format(subsection_grade_model, subsection.location, course, user.id)) else: self._log_update_result(request.user, requested_user_id, requested_usage_id, success=False) @@ -838,7 +837,7 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView): user_id=requested_user_id, usage_id=requested_usage_id, success=False, - reason=u'usage_key {} does not exist in this course.'.format(usage_key) + reason=f'usage_key {usage_key} does not exist in this course.' )) continue @@ -850,13 +849,13 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView): ) result.append(GradebookUpdateResponseItem( user_id=user.id, - usage_id=text_type(usage_key), + usage_id=str(usage_key), success=True, reason=None )) status_code = status.HTTP_422_UNPROCESSABLE_ENTITY - if all((item.success for item in result)): + if all(item.success for item in result): status_code = status.HTTP_202_ACCEPTED return Response( @@ -890,13 +889,13 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView): kwargs=dict( user_id=subsection_grade_model.user_id, anonymous_user_id=None, - course_id=text_type(subsection_grade_model.course_id), - usage_id=text_type(subsection_grade_model.usage_key), + course_id=str(subsection_grade_model.course_id), + usage_id=str(subsection_grade_model.usage_key), only_if_higher=False, expected_modified_time=to_timestamp(override.modified), score_deleted=False, - event_transaction_id=six.text_type(get_event_transaction_id()), - event_transaction_type=six.text_type(get_event_transaction_type()), + event_transaction_id=str(get_event_transaction_id()), + event_transaction_type=str(get_event_transaction_type()), score_db_table=grades_constants.ScoreDatabaseTableEnum.overrides, force_update_subsections=True, ) @@ -915,7 +914,7 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView): ): log.info( - u'Grades: Bulk_Update, UpdatedByUser: %s, User: %s, Usage: %s, Grade: %s, GradeOverride: %s, Success: %s', + 'Grades: Bulk_Update, UpdatedByUser: %s, User: %s, Usage: %s, Grade: %s, GradeOverride: %s, Success: %s', request_user.id, user_id, usage_id, diff --git a/lms/djangoapps/grades/rest_api/v1/tests/mixins.py b/lms/djangoapps/grades/rest_api/v1/tests/mixins.py index ed15c091913..799b99dada5 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/mixins.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/mixins.py @@ -6,12 +6,11 @@ Mixins classes being used by all test classes within this folder from datetime import datetime from pytz import UTC -from six.moves import range +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory -from lms.djangoapps.program_enrollments.tests.factories import ProgramEnrollmentFactory, ProgramCourseEnrollmentFactory +from lms.djangoapps.program_enrollments.tests.factories import ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -59,7 +58,7 @@ class GradeViewTestMixin(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(GradeViewTestMixin, cls).setUpClass() + super().setUpClass() cls.date = datetime(2013, 1, 22, tzinfo=UTC) cls.course = cls._create_test_course_with_default_grading_policy( display_name='test course', run="Testing_course" @@ -89,7 +88,7 @@ class GradeViewTestMixin(SharedModuleStoreTestCase): program_enrollment = ProgramEnrollmentFactory( user=user, - external_user_key='program_user_key_{}'.format(index), + external_user_key=f'program_user_key_{index}', ) ProgramCourseEnrollmentFactory( @@ -99,7 +98,7 @@ class GradeViewTestMixin(SharedModuleStoreTestCase): ) def setUp(self): - super(GradeViewTestMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.password = 'test' self.global_staff = GlobalStaffFactory.create() self.student = UserFactory(password=self.password, username='student', email='student@example.com') @@ -143,19 +142,19 @@ class GradeViewTestMixin(SharedModuleStoreTestCase): category='sequential', parent_location=chapter.location, due=datetime(2017, 12, 18, 11, 30, 00), - display_name=u'Sequential {} {}'.format(grading_type, num), + display_name=f'Sequential {grading_type} {num}', format=grading_type, graded=True, ) vertical = ItemFactory.create( category='vertical', parent_location=section.location, - display_name=u'Vertical {} {}'.format(grading_type, num), + display_name=f'Vertical {grading_type} {num}', ) ItemFactory.create( category='problem', parent_location=vertical.location, - display_name=u'Problem {} {}'.format(grading_type, num), + display_name=f'Problem {grading_type} {num}', ) return course diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py index f6e084c3fd2..f861e60fc75 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_gradebook_views.py @@ -6,12 +6,13 @@ Tests for the course grading API view import json from collections import OrderedDict, namedtuple from datetime import datetime -import pytest +from unittest.mock import MagicMock, patch import ddt +import pytest from django.urls import reverse +from edx_toggles.toggles.testutils import override_waffle_flag from freezegun import freeze_time -from mock import MagicMock, patch from opaque_keys.edx.locator import BlockUsageLocator from pytz import UTC from rest_framework import status @@ -19,7 +20,7 @@ from rest_framework.test import APITestCase from six import text_type from common.djangoapps.course_modes.models import CourseMode -from edx_toggles.toggles.testutils import override_waffle_flag # lint-amnesty, pylint: disable=wrong-import-order +from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate from lms.djangoapps.courseware.tests.factories import InstructorFactory, StaffFactory from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK, waffle_flags @@ -38,7 +39,6 @@ from lms.djangoapps.grades.rest_api.v1.views import CourseEnrollmentPagination from lms.djangoapps.grades.subsection_grade import ReadSubsectionGrade from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory -from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -53,7 +53,7 @@ class CourseGradingViewTest(SharedModuleStoreTestCase, APITestCase): @classmethod def setUpClass(cls): - super(CourseGradingViewTest, cls).setUpClass() + super().setUpClass() cls.course = CourseFactory.create(display_name='test course', run="Testing_course") cls.course_key = cls.course.id @@ -177,28 +177,28 @@ class CourseGradingViewTest(SharedModuleStoreTestCase, APITestCase): 'assignment_type': None, 'display_name': self.subsection1.display_name, 'graded': False, - 'module_id': text_type(self.subsection1.location), + 'module_id': str(self.subsection1.location), 'short_label': None }, { 'assignment_type': None, 'display_name': self.subsection2.display_name, 'graded': False, - 'module_id': text_type(self.subsection2.location), + 'module_id': str(self.subsection2.location), 'short_label': None }, { 'assignment_type': 'Homework', 'display_name': self.homework.display_name, 'graded': True, - 'module_id': text_type(self.homework.location), + 'module_id': str(self.homework.location), 'short_label': 'HW 01', }, { 'assignment_type': 'Midterm Exam', 'display_name': self.midterm.display_name, 'graded': True, - 'module_id': text_type(self.midterm.location), + 'module_id': str(self.midterm.location), 'short_label': 'Midterm 01', }, ], @@ -243,7 +243,7 @@ class GradebookViewTestBase(GradeViewTestMixin, APITestCase): """ @classmethod def setUpClass(cls): - super(GradebookViewTestBase, cls).setUpClass() + super().setUpClass() cls.namespaced_url = 'grades_api:v1:course_gradebook' cls.waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] @@ -362,7 +362,7 @@ class GradebookViewTest(GradebookViewTestBase): """ @classmethod def setUpClass(cls): - super(GradebookViewTest, cls).setUpClass() + super().setUpClass() cls.mock_subsection_grades = { cls.subsections[cls.chapter_1.location][0].location: cls.mock_subsection_grade( cls.subsections[cls.chapter_1.location][0], @@ -398,11 +398,11 @@ class GradebookViewTest(GradebookViewTestBase): """ Helper function to create the course gradebook API read url. """ - base_url = super(GradebookViewTest, self).get_url(course_key) # lint-amnesty, pylint: disable=super-with-arguments + base_url = super().get_url(course_key) if username: - return "{0}?username={1}".format(base_url, username) + return f"{base_url}?username={username}" if user_contains: - return "{0}?user_contains={1}".format(base_url, user_contains) + return f"{base_url}?user_contains={user_contains}" return base_url @staticmethod @@ -433,7 +433,7 @@ class GradebookViewTest(GradebookViewTestBase): ('attempted', True), ('category', 'Homework'), ('label', 'HW 01'), - ('module_id', text_type(self.subsections[self.chapter_1.location][0].location)), + ('module_id', str(self.subsections[self.chapter_1.location][0].location)), ('percent', 0.5), ('score_earned', 1.0), ('score_possible', 2.0), @@ -443,7 +443,7 @@ class GradebookViewTest(GradebookViewTestBase): ('attempted', True), ('category', 'Lab'), ('label', 'Lab 01'), - ('module_id', text_type(self.subsections[self.chapter_1.location][1].location)), + ('module_id', str(self.subsections[self.chapter_1.location][1].location)), ('percent', 0.5), ('score_earned', 1.0), ('score_possible', 2.0), @@ -453,7 +453,7 @@ class GradebookViewTest(GradebookViewTestBase): ('attempted', True), ('category', 'Homework'), ('label', 'HW 02'), - ('module_id', text_type(self.subsections[self.chapter_2.location][0].location)), + ('module_id', str(self.subsections[self.chapter_2.location][0].location)), ('percent', 0.5), ('score_earned', 1.0), ('score_possible', 2.0), @@ -463,7 +463,7 @@ class GradebookViewTest(GradebookViewTestBase): ('attempted', True), ('category', 'Lab'), ('label', 'Lab 02'), - ('module_id', text_type(self.subsections[self.chapter_2.location][1].location)), + ('module_id', str(self.subsections[self.chapter_2.location][1].location)), ('percent', 0.5), ('score_earned', 1.0), ('score_possible', 2.0), @@ -916,7 +916,7 @@ class GradebookViewTest(GradebookViewTestBase): getattr(self, login_method)() # both of our test users are in the audit track, so this is functionally equivalent # to just `?cohort_id=cohort.id`. - query = '?cohort_id={}&enrollment_mode={}'.format(cohort.id, CourseMode.AUDIT) + query = f'?cohort_id={cohort.id}&enrollment_mode={CourseMode.AUDIT}' resp = self.client.get( self.get_url(course_key=self.course.id) + query ) @@ -952,7 +952,7 @@ class GradebookViewTest(GradebookViewTestBase): with override_waffle_flag(self.waffle_flag, active=True): getattr(self, login_method)() resp = self.client.get( - self.get_url(course_key=self.course.id) + '?cohort_id={}'.format(empty_cohort.id) + self.get_url(course_key=self.course.id) + f'?cohort_id={empty_cohort.id}' ) self._assert_empty_response(resp) @@ -981,7 +981,7 @@ class GradebookViewTest(GradebookViewTestBase): with override_waffle_flag(self.waffle_flag, active=True): getattr(self, login_method)() resp = self.client.get( - self.get_url(course_key=self.course.id) + '?enrollment_mode={}'.format(CourseMode.AUDIT) + self.get_url(course_key=self.course.id) + f'?enrollment_mode={CourseMode.AUDIT}' ) self._assert_data_all_users(resp) @@ -1006,7 +1006,7 @@ class GradebookViewTest(GradebookViewTestBase): with override_waffle_flag(self.waffle_flag, active=True): getattr(self, login_method)() resp = self.client.get( - self.get_url(course_key=self.course.id) + '?enrollment_mode={}'.format(CourseMode.VERIFIED) + self.get_url(course_key=self.course.id) + f'?enrollment_mode={CourseMode.VERIFIED}' ) self._assert_empty_response(resp) @@ -1028,7 +1028,7 @@ class GradebookViewTest(GradebookViewTestBase): self.login_staff() query = '' if page_size: - query = '?page_size={}'.format(page_size) + query = f'?page_size={page_size}' resp = self.client.get( self.get_url(course_key=self.course.id) + query ) @@ -1289,7 +1289,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): """ @classmethod def setUpClass(cls): - super(GradebookBulkUpdateViewTest, cls).setUpClass() + super().setUpClass() cls.namespaced_url = 'grades_api:v1:course_gradebook_bulk_update' def test_feature_not_enabled(self): @@ -1334,7 +1334,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): post_data = [ { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'grade': {}, # doesn't matter what we put here. } ] @@ -1358,7 +1358,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): post_data = [ { 'user_id': unenrolled_student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'grade': {}, # doesn't matter what we put here. } ] @@ -1372,7 +1372,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): expected_data = [ { 'user_id': unenrolled_student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'success': False, 'reason': 'CourseEnrollment matching query does not exist.', }, @@ -1391,7 +1391,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): post_data = [ { 'user_id': -123, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'grade': {}, # doesn't matter what we put here. } ] @@ -1405,7 +1405,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): expected_data = [ { 'user_id': -123, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'success': False, 'reason': 'User matching query does not exist.', }, @@ -1478,7 +1478,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): 'user_id': self.student.id, 'usage_id': usage_id, 'success': False, - 'reason': 'usage_key {} does not exist in this course.'.format(usage_id), + 'reason': f'usage_key {usage_id} does not exist in this course.', }, ] assert status.HTTP_422_UNPROCESSABLE_ENTITY == resp.status_code @@ -1495,7 +1495,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): post_data = [ { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'grade': { 'earned_all_override': 3, 'possible_all_override': 3, @@ -1505,7 +1505,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): }, { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][1].location), + 'usage_id': str(self.subsections[self.chapter_1.location][1].location), 'grade': { 'earned_all_override': 1, 'possible_all_override': 4, @@ -1524,13 +1524,13 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): expected_data = [ { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'success': True, 'reason': None, }, { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][1].location), + 'usage_id': str(self.subsections[self.chapter_1.location][1].location), 'success': True, 'reason': None, }, @@ -1541,7 +1541,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): second_post_data = [ { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][1].location), + 'usage_id': str(self.subsections[self.chapter_1.location][1].location), 'grade': { 'earned_all_override': 3, 'possible_all_override': 4, @@ -1601,7 +1601,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): post_data = [ { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][0].location), + 'usage_id': str(self.subsections[self.chapter_1.location][0].location), 'grade': { 'earned_all_override': 0, 'possible_all_override': 3, @@ -1611,7 +1611,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase): }, { 'user_id': self.student.id, - 'usage_id': text_type(self.subsections[self.chapter_1.location][1].location), + 'usage_id': str(self.subsections[self.chapter_1.location][1].location), 'grade': { 'earned_all_override': 0, 'possible_all_override': 4, @@ -1635,7 +1635,7 @@ class SubsectionGradeViewTest(GradebookViewTestBase): """ Test for the audit api call """ @classmethod def setUpClass(cls): - super(SubsectionGradeViewTest, cls).setUpClass() + super().setUpClass() cls.namespaced_url = 'grades_api:v1:course_grade_overrides' cls.locator_a = BlockUsageLocator( course_key=cls.course_key, @@ -1677,7 +1677,7 @@ class SubsectionGradeViewTest(GradebookViewTestBase): 'subsection_id': subsection_id or self.subsection_id, } ) - return "{0}?user_id={1}".format(base_url, user_id or self.user_id) + return "{}?user_id={}".format(base_url, user_id or self.user_id) @patch('lms.djangoapps.grades.subsection_grade_factory.SubsectionGradeFactory.create') @ddt.data( @@ -1722,8 +1722,8 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ]), 'user_id': user_no_grade.id, 'override': None, - 'course_id': text_type(self.usage_key.course_key), - 'subsection_id': text_type(self.usage_key), + 'course_id': str(self.usage_key.course_key), + 'subsection_id': str(self.usage_key), 'history': [] } assert expected_data == resp.data @@ -1750,8 +1750,8 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ]), 'user_id': self.user_id, 'override': None, - 'course_id': text_type(self.course_key), - 'subsection_id': text_type(self.usage_key), + 'course_id': str(self.course_key), + 'subsection_id': str(self.usage_key), 'history': [] } @@ -1793,8 +1793,8 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ('earned_graded_override', 0.0), ('possible_graded_override', 8.0) ]), - 'course_id': text_type(self.course_key), - 'subsection_id': text_type(self.usage_key), + 'course_id': str(self.course_key), + 'subsection_id': str(self.usage_key), 'history': [OrderedDict([ ('created', '2019-01-01T00:00:00Z'), ('grade_id', 1), @@ -1804,7 +1804,7 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ('override_reason', None), ('system', None), ('history_date', '2019-01-01T00:00:00Z'), - ('history_type', u'+'), + ('history_type', '+'), ('history_user', None), ('history_user_id', None), ('id', 1), @@ -1851,8 +1851,8 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ('earned_graded_override', 0.0), ('possible_graded_override', 8.0) ]), - 'course_id': text_type(self.course_key), - 'subsection_id': text_type(self.usage_key), + 'course_id': str(self.course_key), + 'subsection_id': str(self.usage_key), 'history': [OrderedDict([ ('created', '2019-01-01T00:00:00Z'), ('grade_id', 1), @@ -1862,7 +1862,7 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ('override_reason', None), ('system', None), ('history_date', '2019-01-01T00:00:00Z'), - ('history_type', u'+'), + ('history_type', '+'), ('history_user', self.global_staff.username), ('history_user_id', self.global_staff.id), ('id', 1), @@ -1948,8 +1948,8 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ]), 'user_id': other_user.id, 'override': None, - 'course_id': text_type(self.usage_key.course_key), - 'subsection_id': text_type(self.usage_key), + 'course_id': str(self.usage_key.course_key), + 'subsection_id': str(self.usage_key), 'history': [] } @@ -1991,8 +1991,8 @@ class SubsectionGradeViewTest(GradebookViewTestBase): ]), 'user_id': self.user_id, 'override': None, - 'course_id': text_type(self.usage_key.course_key), - 'subsection_id': text_type(unreleased_subsection.location), + 'course_id': str(self.usage_key.course_key), + 'subsection_id': str(unreleased_subsection.location), 'history': [] } assert expected_data == resp.data diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py b/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py index 5fe947eec00..6c19b263c5b 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_grading_policy_view.py @@ -6,28 +6,27 @@ Tests for the views from datetime import datetime import ddt -import six from django.urls import reverse from pytz import UTC -from openedx.core.djangoapps.oauth_dispatch.tests.factories import ApplicationFactory, AccessTokenFactory from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory -from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory, StaffFactory from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory, StaffFactory +from openedx.core.djangoapps.oauth_dispatch.tests.factories import AccessTokenFactory, ApplicationFactory from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @ddt.ddt -class GradingPolicyTestMixin(object): +class GradingPolicyTestMixin: """ Mixin class for Grading Policy tests """ view_name = None def setUp(self): - super(GradingPolicyTestMixin, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.create_user_and_access_token() def create_user_and_access_token(self): @@ -39,7 +38,7 @@ class GradingPolicyTestMixin(object): def create_course_data(cls): # lint-amnesty, pylint: disable=missing-function-docstring cls.invalid_course_id = 'foo/bar/baz' cls.course = CourseFactory.create(display_name='An Introduction to API Testing', raw_grader=cls.raw_grader) - cls.course_id = six.text_type(cls.course.id) + cls.course_id = str(cls.course.id) with cls.store.bulk_operations(cls.course.id, emit_signals=False): cls.sequential = ItemFactory.create( category="sequential", @@ -153,7 +152,7 @@ class GradingPolicyTestMixin(object): org="MTD", default_store=modulestore_type, ) - self.assert_get_for_course(course_id=six.text_type(course.id)) + self.assert_get_for_course(course_id=str(course.id)) class CourseGradingPolicyTests(GradingPolicyTestMixin, SharedModuleStoreTestCase): @@ -181,14 +180,14 @@ class CourseGradingPolicyTests(GradingPolicyTestMixin, SharedModuleStoreTestCase @classmethod def setUpClass(cls): - super(CourseGradingPolicyTests, cls).setUpClass() + super().setUpClass() cls.create_course_data() def test_get(self): """ The view should return grading policy for a course. """ - response = super(CourseGradingPolicyTests, self).test_get() # lint-amnesty, pylint: disable=super-with-arguments + response = super().test_get() expected = [ { @@ -233,14 +232,14 @@ class CourseGradingPolicyMissingFieldsTests(GradingPolicyTestMixin, SharedModule @classmethod def setUpClass(cls): - super(CourseGradingPolicyMissingFieldsTests, cls).setUpClass() + super().setUpClass() cls.create_course_data() def test_get(self): """ The view should return grading policy for a course. """ - response = super(CourseGradingPolicyMissingFieldsTests, self).test_get() # lint-amnesty, pylint: disable=super-with-arguments + response = super().test_get() expected = [ { diff --git a/lms/djangoapps/grades/rest_api/v1/tests/test_views.py b/lms/djangoapps/grades/rest_api/v1/tests/test_views.py index ed6d4bda054..7793d9571d9 100644 --- a/lms/djangoapps/grades/rest_api/v1/tests/test_views.py +++ b/lms/djangoapps/grades/rest_api/v1/tests/test_views.py @@ -4,18 +4,18 @@ Tests for v1 views from collections import OrderedDict +from unittest.mock import MagicMock, patch import ddt from django.urls import reverse -from mock import MagicMock, patch from opaque_keys import InvalidKeyError from rest_framework import status from rest_framework.test import APITestCase +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.grades.rest_api.v1.tests.mixins import GradeViewTestMixin from lms.djangoapps.grades.rest_api.v1.views import CourseGradesView from openedx.core.djangoapps.user_authn.tests.utils import AuthAndScopesTestMixin -from common.djangoapps.student.tests.factories import UserFactory @ddt.ddt @@ -29,7 +29,7 @@ class SingleUserGradesTests(GradeViewTestMixin, AuthAndScopesTestMixin, APITestC @classmethod def setUpClass(cls): - super(SingleUserGradesTests, cls).setUpClass() + super().setUpClass() cls.namespaced_url = 'grades_api:v1:course_grades' def get_url(self, username): @@ -40,7 +40,7 @@ class SingleUserGradesTests(GradeViewTestMixin, AuthAndScopesTestMixin, APITestC 'course_id': self.course_key, } ) - return "{0}?username={1}".format(base_url, username) + return f"{base_url}?username={username}" def assert_success_response_for_student(self, response): """ This method is required by AuthAndScopesTestMixin. """ @@ -120,7 +120,7 @@ class SingleUserGradesTests(GradeViewTestMixin, AuthAndScopesTestMixin, APITestC 'course_id': 'course-v1:MITx+8.MechCX+2014_T1', } ) - url = "{0}?username={1}".format(base_url, self.student.username) + url = f"{base_url}?username={self.student.username}" resp = self.client.get(url) assert resp.status_code == status.HTTP_404_NOT_FOUND assert 'error_code' in resp.data @@ -166,7 +166,7 @@ class CourseGradesViewTest(GradeViewTestMixin, APITestCase): @classmethod def setUpClass(cls): - super(CourseGradesViewTest, cls).setUpClass() + super().setUpClass() cls.namespaced_url = 'grades_api:v1:course_grades' def get_url(self, course_key=None): diff --git a/lms/djangoapps/grades/rest_api/v1/urls.py b/lms/djangoapps/grades/rest_api/v1/urls.py index effefc7e5bf..081e62b4199 100644 --- a/lms/djangoapps/grades/rest_api/v1/urls.py +++ b/lms/djangoapps/grades/rest_api/v1/urls.py @@ -15,27 +15,27 @@ urlpatterns = [ name='course_grades' ), url( - r'^courses/{course_id}/$'.format(course_id=settings.COURSE_ID_PATTERN), + fr'^courses/{settings.COURSE_ID_PATTERN}/$', views.CourseGradesView.as_view(), name='course_grades' ), url( - r'^policy/courses/{course_id}/$'.format(course_id=settings.COURSE_ID_PATTERN), + fr'^policy/courses/{settings.COURSE_ID_PATTERN}/$', views.CourseGradingPolicy.as_view(), name='course_grading_policy' ), url( - r'^gradebook/{course_id}/$'.format(course_id=settings.COURSE_ID_PATTERN), + fr'^gradebook/{settings.COURSE_ID_PATTERN}/$', gradebook_views.GradebookView.as_view(), name='course_gradebook' ), url( - r'^gradebook/{course_id}/bulk-update$'.format(course_id=settings.COURSE_ID_PATTERN), + fr'^gradebook/{settings.COURSE_ID_PATTERN}/bulk-update$', gradebook_views.GradebookBulkUpdateView.as_view(), name='course_gradebook_bulk_update' ), url( - r'^gradebook/{course_id}/grading-info$'.format(course_id=settings.COURSE_ID_PATTERN), + fr'^gradebook/{settings.COURSE_ID_PATTERN}/grading-info$', gradebook_views.CourseGradingView.as_view(), name='course_gradebook_grading_info' ), diff --git a/lms/djangoapps/grades/rest_api/v1/utils.py b/lms/djangoapps/grades/rest_api/v1/utils.py index 95c6b74f020..0a13db8572c 100644 --- a/lms/djangoapps/grades/rest_api/v1/utils.py +++ b/lms/djangoapps/grades/rest_api/v1/utils.py @@ -12,10 +12,10 @@ from rest_framework.exceptions import AuthenticationFailed from rest_framework.pagination import CursorPagination from rest_framework.response import Response -from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory -from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.util.query import use_read_replica_if_available +from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory +from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin USER_MODEL = get_user_model() @@ -45,7 +45,7 @@ class CourseEnrollmentPagination(CursorPagination): Return a response given serialized page data, optional status_code (defaults to 200), and kwargs. Each key-value pair of kwargs is added to the response data. """ - resp = super(CourseEnrollmentPagination, self).get_paginated_response(data) # lint-amnesty, pylint: disable=super-with-arguments + resp = super().get_paginated_response(data) for (key, value) in kwargs.items(): resp.data[key] = value @@ -181,6 +181,6 @@ class GradeViewMixin(DeveloperErrorViewMixin): """ Ensures that the user is authenticated (e.g. not an AnonymousUser). """ - super(GradeViewMixin, self).perform_authentication(request) # lint-amnesty, pylint: disable=super-with-arguments + super().perform_authentication(request) if request.user.is_anonymous: raise AuthenticationFailed diff --git a/lms/djangoapps/grades/scores.py b/lms/djangoapps/grades/scores.py index 7ce6016eca6..813bc664d91 100644 --- a/lms/djangoapps/grades/scores.py +++ b/lms/djangoapps/grades/scores.py @@ -5,7 +5,6 @@ Functionality for problem scores. from logging import getLogger -import six from numpy import around from xblock.core import XBlock @@ -105,7 +104,7 @@ def get_score(submissions_scores, csm_scores, persisted_block, block): weight = _get_weight_from_block(persisted_block, block) # TODO: Remove as part of EDUCATOR-4602. if str(block.location.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Weight for block: ***{}*** is {}' + log.info('Weight for block: ***{}*** is {}' .format(str(block.location), weight)) # Priority order for retrieving the scores: @@ -118,8 +117,8 @@ def get_score(submissions_scores, csm_scores, persisted_block, block): # TODO: Remove as part of EDUCATOR-4602. if str(block.location.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Calculated raw-earned: {}, raw_possible: {}, weighted_earned: ' - u'{}, weighted_possible: {}, first_attempted: {} for block: ***{}***.' + log.info('Calculated raw-earned: {}, raw_possible: {}, weighted_earned: ' + '{}, weighted_possible: {}, first_attempted: {} for block: ***{}***.' .format(raw_earned, raw_possible, weighted_earned, weighted_possible, first_attempted, str(block.location))) @@ -174,7 +173,7 @@ def _get_score_from_submissions(submissions_scores, block): Returns the score values from the submissions API if found. """ if submissions_scores: - submission_value = submissions_scores.get(six.text_type(block.location)) + submission_value = submissions_scores.get(str(block.location)) if submission_value: first_attempted = submission_value['created_at'] weighted_earned = submission_value['points_earned'] @@ -222,7 +221,7 @@ def _get_score_from_persisted_or_latest_block(persisted_block, block, weight): """ # TODO: Remove as part of EDUCATOR-4602. if str(block.location.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Using _get_score_from_persisted_or_latest_block to calculate score for block: ***{}***.'.format( + log.info('Using _get_score_from_persisted_or_latest_block to calculate score for block: ***{}***.'.format( str(block.location) )) raw_earned = 0.0 @@ -234,8 +233,8 @@ def _get_score_from_persisted_or_latest_block(persisted_block, block, weight): raw_possible = block.transformer_data[GradesTransformer].max_score # TODO: Remove as part of EDUCATOR-4602. if str(block.location.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Using latest block content to calculate score for block: ***{}***.') - log.info(u'weight for block: ***{}*** is {}.'.format(str(block.location), raw_possible)) + log.info('Using latest block content to calculate score for block: ***{}***.') + log.info('weight for block: ***{}*** is {}.'.format(str(block.location), raw_possible)) # TODO TNL-5982 remove defensive code for scorables without max_score if raw_possible is None: diff --git a/lms/djangoapps/grades/services.py b/lms/djangoapps/grades/services.py index 6d03e8b56d1..1ca83b9f85e 100644 --- a/lms/djangoapps/grades/services.py +++ b/lms/djangoapps/grades/services.py @@ -6,7 +6,7 @@ Grade service from . import api -class GradesService(object): +class GradesService: """ Course grade service diff --git a/lms/djangoapps/grades/signals/handlers.py b/lms/djangoapps/grades/signals/handlers.py index 8e860d5de3b..7bebf3af785 100644 --- a/lms/djangoapps/grades/signals/handlers.py +++ b/lms/djangoapps/grades/signals/handlers.py @@ -6,29 +6,28 @@ Grades related signals. from contextlib import contextmanager from logging import getLogger -import six from django.dispatch import receiver from opaque_keys.edx.keys import LearningContextKey from submissions.models import score_reset, score_set from xblock.scorable import ScorableXBlockMixin, Score -from lms.djangoapps.courseware.model_data import get_score, set_score -from openedx.core.djangoapps.course_groups.signals.signals import COHORT_MEMBERSHIP_UPDATED -from openedx.core.lib.grade_utils import is_score_higher_or_equal from common.djangoapps.student.models import user_by_anonymous_id from common.djangoapps.student.signals import ENROLLMENT_TRACK_UPDATED from common.djangoapps.track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type from common.djangoapps.util.date_utils import to_timestamp +from lms.djangoapps.courseware.model_data import get_score, set_score +from lms.djangoapps.grades.tasks import ( + RECALCULATE_GRADE_DELAY_SECONDS, + recalculate_course_and_subsection_grades_for_user, + recalculate_subsection_grade_v3 +) +from openedx.core.djangoapps.course_groups.signals.signals import COHORT_MEMBERSHIP_UPDATED +from openedx.core.lib.grade_utils import is_score_higher_or_equal from .. import events from ..constants import ScoreDatabaseTableEnum from ..course_grade_factory import CourseGradeFactory from ..scores import weighted_score -from lms.djangoapps.grades.tasks import ( # lint-amnesty, pylint: disable=wrong-import-order - RECALCULATE_GRADE_DELAY_SECONDS, - recalculate_course_and_subsection_grades_for_user, - recalculate_subsection_grade_v3 -) from .signals import ( PROBLEM_RAW_SCORE_CHANGED, PROBLEM_WEIGHTED_SCORE_CHANGED, @@ -151,8 +150,8 @@ def score_published_handler(sender, block, user, raw_earned, raw_possible, only_ if not is_score_higher_or_equal(prev_raw_earned, prev_raw_possible, raw_earned, raw_possible): update_score = False log.warning( - u"Grades: Rescore is not higher than previous: " - u"user: {}, block: {}, previous: {}/{}, new: {}/{} ".format( + "Grades: Rescore is not higher than previous: " + "user: {}, block: {}, previous: {}/{}, new: {}/{} ".format( user, block.location, prev_raw_earned, prev_raw_possible, raw_earned, raw_possible, ) ) @@ -172,8 +171,8 @@ def score_published_handler(sender, block, user, raw_earned, raw_possible, only_ raw_possible=raw_possible, weight=getattr(block, 'weight', None), user_id=user.id, - course_id=six.text_type(block.location.course_key), - usage_id=six.text_type(block.location), + course_id=str(block.location.course_key), + usage_id=str(block.location), only_if_higher=only_if_higher, modified=score_modified_time, score_db_table=ScoreDatabaseTableEnum.courseware_student_module, @@ -233,8 +232,8 @@ def enqueue_subsection_update(sender, **kwargs): # pylint: disable=unused-argum only_if_higher=kwargs.get('only_if_higher'), expected_modified_time=to_timestamp(kwargs['modified']), score_deleted=kwargs.get('score_deleted', False), - event_transaction_id=six.text_type(get_event_transaction_id()), - event_transaction_type=six.text_type(get_event_transaction_type()), + event_transaction_id=str(get_event_transaction_id()), + event_transaction_type=str(get_event_transaction_type()), score_db_table=kwargs['score_db_table'], force_update_subsections=kwargs.get('force_update_subsections', False), ), @@ -262,6 +261,6 @@ def recalculate_course_and_subsection_grades(sender, user, course_key, countdown countdown=countdown, kwargs=dict( user_id=user.id, - course_key=six.text_type(course_key) + course_key=str(course_key) ) ) diff --git a/lms/djangoapps/grades/subsection_grade.py b/lms/djangoapps/grades/subsection_grade.py index 755db2f3e11..c7c963c5b23 100644 --- a/lms/djangoapps/grades/subsection_grade.py +++ b/lms/djangoapps/grades/subsection_grade.py @@ -7,7 +7,6 @@ from abc import ABCMeta from collections import OrderedDict from logging import getLogger -import six from django.utils.html import escape from lazy import lazy @@ -19,7 +18,7 @@ from xmodule.graders import AggregatedScore, ShowCorrectness log = getLogger(__name__) -class SubsectionGradeBase(six.with_metaclass(ABCMeta, object)): +class SubsectionGradeBase(metaclass=ABCMeta): """ Abstract base class for Subsection Grades. """ @@ -79,7 +78,7 @@ class ZeroSubsectionGrade(SubsectionGradeBase): """ def __init__(self, subsection, course_data): - super(ZeroSubsectionGrade, self).__init__(subsection) # lint-amnesty, pylint: disable=super-with-arguments + super().__init__(subsection) self.course_data = course_data @property @@ -139,14 +138,14 @@ class ZeroSubsectionGrade(SubsectionGradeBase): return locations -class NonZeroSubsectionGrade(six.with_metaclass(ABCMeta, SubsectionGradeBase)): +class NonZeroSubsectionGrade(SubsectionGradeBase, metaclass=ABCMeta): """ Abstract base class for Subsection Grades with possibly NonZero values. """ def __init__(self, subsection, all_total, graded_total, override=None): - super(NonZeroSubsectionGrade, self).__init__(subsection) # lint-amnesty, pylint: disable=super-with-arguments + super().__init__(subsection) self.all_total = all_total self.graded_total = graded_total self.override = override @@ -169,7 +168,7 @@ class NonZeroSubsectionGrade(six.with_metaclass(ABCMeta, SubsectionGradeBase)): ): # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Computing block score for block: ***{}*** in course: ***{}***.'.format( + log.info('Computing block score for block: ***{}*** in course: ***{}***.'.format( str(block_key), str(block_key.course_key), )) @@ -178,8 +177,8 @@ class NonZeroSubsectionGrade(six.with_metaclass(ABCMeta, SubsectionGradeBase)): except KeyError: # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'User\'s access to block: ***{}*** in course: ***{}*** has changed. ' - u'No block score calculated.'.format(str(block_key), str(block_key.course_key))) + log.info('User\'s access to block: ***{}*** in course: ***{}*** has changed. ' + 'No block score calculated.'.format(str(block_key), str(block_key.course_key))) # It's possible that the user's access to that # block has changed since the subsection grade # was last persisted. @@ -187,7 +186,7 @@ class NonZeroSubsectionGrade(six.with_metaclass(ABCMeta, SubsectionGradeBase)): if getattr(block, 'has_score', False): # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Block: ***{}*** in course: ***{}*** HAS has_score attribute. Continuing.' + log.info('Block: ***{}*** in course: ***{}*** HAS has_score attribute. Continuing.' .format(str(block_key), str(block_key.course_key))) return get_score( submissions_scores, @@ -197,8 +196,8 @@ class NonZeroSubsectionGrade(six.with_metaclass(ABCMeta, SubsectionGradeBase)): ) # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Block: ***{}*** in course: ***{}*** DOES NOT HAVE has_score attribute. ' - u'No block score calculated.' + log.info('Block: ***{}*** in course: ***{}*** DOES NOT HAVE has_score attribute. ' + 'No block score calculated.' .format(str(block_key), str(block_key.course_key))) @staticmethod @@ -210,16 +209,16 @@ class NonZeroSubsectionGrade(six.with_metaclass(ABCMeta, SubsectionGradeBase)): used instead. """ score_type = 'graded' if is_graded else 'all' - earned_value = getattr(grade_model, 'earned_{}'.format(score_type)) - possible_value = getattr(grade_model, 'possible_{}'.format(score_type)) + earned_value = getattr(grade_model, f'earned_{score_type}') + possible_value = getattr(grade_model, f'possible_{score_type}') if hasattr(grade_model, 'override'): score_type = 'graded_override' if is_graded else 'all_override' - earned_override = getattr(grade_model.override, 'earned_{}'.format(score_type)) + earned_override = getattr(grade_model.override, f'earned_{score_type}') if earned_override is not None: earned_value = earned_override - possible_override = getattr(grade_model.override, 'possible_{}'.format(score_type)) + possible_override = getattr(grade_model.override, f'possible_{score_type}') if possible_override is not None: possible_value = possible_override @@ -244,7 +243,7 @@ class ReadSubsectionGrade(NonZeroSubsectionGrade): self.model = model self.factory = factory - super(ReadSubsectionGrade, self).__init__(subsection, all_total, graded_total, override) # lint-amnesty, pylint: disable=super-with-arguments + super().__init__(subsection, all_total, graded_total, override) @lazy def problem_scores(self): @@ -283,8 +282,8 @@ class CreateSubsectionGrade(NonZeroSubsectionGrade): # TODO: Remove as part of EDUCATOR-4602. if str(block_key.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Calculated problem score ***{}*** for block ***{!s}***' - u' in subsection ***{}***.' + log.info('Calculated problem score ***{}*** for block ***{!s}***' + ' in subsection ***{}***.' .format(problem_score, block_key, subsection.location)) if problem_score: self.problem_scores[block_key] = problem_score @@ -293,11 +292,11 @@ class CreateSubsectionGrade(NonZeroSubsectionGrade): # TODO: Remove as part of EDUCATOR-4602. if str(subsection.location.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Calculated aggregate all_total ***{}***' - u' and grade_total ***{}*** for subsection ***{}***' + log.info('Calculated aggregate all_total ***{}***' + ' and grade_total ***{}*** for subsection ***{}***' .format(all_total, graded_total, subsection.location)) - super(CreateSubsectionGrade, self).__init__(subsection, all_total, graded_total) # lint-amnesty, pylint: disable=super-with-arguments + super().__init__(subsection, all_total, graded_total) def update_or_create_model(self, student, score_deleted=False, force_update_subsections=False): """ @@ -306,8 +305,8 @@ class CreateSubsectionGrade(NonZeroSubsectionGrade): if self._should_persist_per_attempted(score_deleted, force_update_subsections): # TODO: Remove as part of EDUCATOR-4602. if str(self.location.course_key) == 'course-v1:UQx+BUSLEAD5x+2T2019': - log.info(u'Updating PersistentSubsectionGrade for student ***{}*** in' - u' subsection ***{}*** with params ***{}***.' + log.info('Updating PersistentSubsectionGrade for student ***{}*** in' + ' subsection ***{}*** with params ***{}***.' .format(student.id, self.location, self._persisted_model_params(student))) model = PersistentSubsectionGrade.update_or_create_grade(**self._persisted_model_params(student)) @@ -379,5 +378,5 @@ class CreateSubsectionGrade(NonZeroSubsectionGrade): return [ BlockRecord(location, score.weight, score.raw_possible, score.graded) for location, score in - six.iteritems(self.problem_scores) + self.problem_scores.items() ] diff --git a/lms/djangoapps/grades/subsection_grade_factory.py b/lms/djangoapps/grades/subsection_grade_factory.py index 5f24c8535d8..a2ebe2e934e 100644 --- a/lms/djangoapps/grades/subsection_grade_factory.py +++ b/lms/djangoapps/grades/subsection_grade_factory.py @@ -10,13 +10,13 @@ from django.conf import settings from lazy import lazy from submissions import api as submissions_api -from openedx.core.djangoapps.signals.signals import COURSE_ASSESSMENT_GRADE_CHANGED +from common.djangoapps.student.models import anonymous_id_for_user from lms.djangoapps.courseware.model_data import ScoresClient from lms.djangoapps.grades.config import assume_zero_if_absent, should_persist_grades from lms.djangoapps.grades.models import PersistentSubsectionGrade from lms.djangoapps.grades.scores import possibly_scored +from openedx.core.djangoapps.signals.signals import COURSE_ASSESSMENT_GRADE_CHANGED from openedx.core.lib.grade_utils import is_score_higher_or_equal -from common.djangoapps.student.models import anonymous_id_for_user from .course_data import CourseData from .subsection_grade import CreateSubsectionGrade, ReadSubsectionGrade, ZeroSubsectionGrade @@ -24,7 +24,7 @@ from .subsection_grade import CreateSubsectionGrade, ReadSubsectionGrade, ZeroSu log = getLogger(__name__) -class SubsectionGradeFactory(object): +class SubsectionGradeFactory: """ Factory for Subsection Grades. """ @@ -44,7 +44,7 @@ class SubsectionGradeFactory(object): grade currently exists, even if the assume_zero_if_absent flag is enabled for the course. """ self._log_event( - log.debug, u"create, read_only: {0}, subsection: {1}".format(read_only, subsection.location), subsection, + log.debug, f"create, read_only: {read_only}, subsection: {subsection.location}", subsection, ) subsection_grade = self._get_bulk_cached_grade(subsection) @@ -76,7 +76,7 @@ class SubsectionGradeFactory(object): """ Updates the SubsectionGrade object for the student and subsection. """ - self._log_event(log.debug, u"update, subsection: {}".format(subsection.location), subsection) + self._log_event(log.debug, f"update, subsection: {subsection.location}", subsection) calculated_grade = CreateSubsectionGrade( subsection, self.course_data.structure, self._submissions_scores, self._csm_scores, @@ -173,7 +173,7 @@ class SubsectionGradeFactory(object): """ Logs the given statement, for this instance. """ - log_func(u"Grades: SGF.{}, course: {}, version: {}, edit: {}, user: {}".format( + log_func("Grades: SGF.{}, course: {}, version: {}, edit: {}, user: {}".format( log_statement, self.course_data.course_key, getattr(subsection, 'course_version', None), diff --git a/lms/djangoapps/grades/tasks.py b/lms/djangoapps/grades/tasks.py index cf1eb111668..0e7833fff01 100644 --- a/lms/djangoapps/grades/tasks.py +++ b/lms/djangoapps/grades/tasks.py @@ -1,11 +1,8 @@ """ This module contains tasks for asynchronous execution of grade updates. """ - - from logging import getLogger -import six from celery import shared_task from celery_utils.persist_on_failure import LoggedPersistOnFailureTask from django.conf import settings @@ -21,13 +18,14 @@ from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.locator import CourseLocator from submissions import api as sub_api -from lms.djangoapps.courseware.model_data import get_score -from lms.djangoapps.course_blocks.api import get_course_blocks -from lms.djangoapps.grades.config.models import ComputeGradesSetting -from openedx.core.djangoapps.content.course_overviews.models import CourseOverview # lint-amnesty, pylint: disable=unused-import from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.track.event_transaction_utils import set_event_transaction_id, set_event_transaction_type from common.djangoapps.util.date_utils import from_timestamp +from lms.djangoapps.course_blocks.api import get_course_blocks +from lms.djangoapps.courseware.model_data import get_score +from lms.djangoapps.grades.config.models import ComputeGradesSetting +from openedx.core.djangoapps.content.course_overviews.models import \ + CourseOverview # lint-amnesty, pylint: disable=unused-import from xmodule.modulestore.django import modulestore from .config.waffle import DISABLE_REGRADE_ON_POLICY_CHANGE, waffle @@ -65,7 +63,7 @@ def compute_all_grades_for_course(**kwargs): else: course_key = CourseKey.from_string(kwargs.pop('course_key')) if are_grades_frozen(course_key): - log.info(u"Attempted compute_all_grades_for_course for course '%s', but grades are frozen.", course_key) + log.info("Attempted compute_all_grades_for_course for course '%s', but grades are frozen.", course_key) return for course_key_string, offset, batch_size in _course_task_args(course_key=course_key, **kwargs): kwargs.update({ @@ -122,7 +120,7 @@ def compute_grades_for_course(course_key, offset, batch_size, **kwargs): # pyli """ course_key = CourseKey.from_string(course_key) if are_grades_frozen(course_key): - log.info(u"Attempted compute_grades_for_course for course '%s', but grades are frozen.", course_key) + log.info("Attempted compute_grades_for_course for course '%s', but grades are frozen.", course_key) return enrollments = CourseEnrollment.objects.filter(course_id=course_key).order_by('created') @@ -152,14 +150,14 @@ def recalculate_course_and_subsection_grades_for_user(self, **kwargs): # pylint course_key_str = kwargs.get('course_key') if not (user_id or course_key_str): - message = u'recalculate_course_and_subsection_grades_for_user missing "user" or "course_key" kwargs from {}' + message = 'recalculate_course_and_subsection_grades_for_user missing "user" or "course_key" kwargs from {}' raise Exception(message.format(kwargs)) user = User.objects.get(id=user_id) course_key = CourseKey.from_string(course_key_str) if are_grades_frozen(course_key): log.info( - u"Attempted recalculate_course_and_subsection_grades_for_user for course '%s', but grades are frozen.", + "Attempted recalculate_course_and_subsection_grades_for_user for course '%s', but grades are frozen.", course_key, ) return @@ -215,13 +213,13 @@ def _recalculate_subsection_grade(self, **kwargs): try: course_key = CourseLocator.from_string(kwargs['course_id']) if are_grades_frozen(course_key): - log.info(u"Attempted _recalculate_subsection_grade for course '%s', but grades are frozen.", course_key) + log.info("Attempted _recalculate_subsection_grade for course '%s', but grades are frozen.", course_key) return scored_block_usage_key = UsageKey.from_string(kwargs['usage_id']).replace(course_key=course_key) set_custom_attributes_for_course_key(course_key) - set_custom_attribute('usage_id', six.text_type(scored_block_usage_key)) + set_custom_attribute('usage_id', str(scored_block_usage_key)) # The request cache is not maintained on celery workers, # where this code runs. So we take the values from the @@ -250,7 +248,7 @@ def _recalculate_subsection_grade(self, **kwargs): ) except Exception as exc: if not isinstance(exc, KNOWN_RETRY_ERRORS): - log.info(u"tnl-6244 grades unexpected failure: {}. task id: {}. kwargs={}".format( + log.info("tnl-6244 grades unexpected failure: {}. task id: {}. kwargs={}".format( repr(exc), self.request.id, kwargs, @@ -271,8 +269,8 @@ def _has_db_updated_with_new_score(self, scored_block_usage_key, **kwargs): score = sub_api.get_score( { "student_id": kwargs['anonymous_user_id'], - "course_id": six.text_type(scored_block_usage_key.course_key), - "item_id": six.text_type(scored_block_usage_key), + "course_id": str(scored_block_usage_key.course_key), + "item_id": str(scored_block_usage_key), "item_type": scored_block_usage_key.block_type, } ) @@ -296,8 +294,8 @@ def _has_db_updated_with_new_score(self, scored_block_usage_key, **kwargs): if not db_is_updated: log.info( - u"Grades: tasks._has_database_updated_with_new_score is False. Task ID: {}. Kwargs: {}. Found " - u"modified time: {}".format( + "Grades: tasks._has_database_updated_with_new_score is False. Task ID: {}. Kwargs: {}. Found " + "modified time: {}".format( self.request.id, kwargs, found_modified_time, @@ -353,12 +351,12 @@ def _course_task_args(course_key, **kwargs): from_settings = kwargs.pop('from_settings', True) enrollment_count = CourseEnrollment.objects.filter(course_id=course_key).count() if enrollment_count == 0: - log.warning(u"No enrollments found for {}".format(course_key)) + log.warning(f"No enrollments found for {course_key}") if from_settings is False: batch_size = kwargs.pop('batch_size', 100) else: batch_size = ComputeGradesSetting.current().batch_size - for offset in six.moves.range(0, enrollment_count, batch_size): - yield (six.text_type(course_key), offset, batch_size) + for offset in range(0, enrollment_count, batch_size): + yield (str(course_key), offset, batch_size) diff --git a/lms/djangoapps/grades/tests/base.py b/lms/djangoapps/grades/tests/base.py index 2d32bae9ba8..f9a049bc34a 100644 --- a/lms/djangoapps/grades/tests/base.py +++ b/lms/djangoapps/grades/tests/base.py @@ -6,10 +6,10 @@ Base file for Grades tests from crum import set_current_request from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory -from lms.djangoapps.course_blocks.api import get_course_blocks -from openedx.core.djangolib.testing.utils import get_mock_request from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.course_blocks.api import get_course_blocks +from openedx.core.djangolib.testing.utils import get_mock_request from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -23,7 +23,7 @@ class GradeTestBase(SharedModuleStoreTestCase): """ @classmethod def setUpClass(cls): - super(GradeTestBase, cls).setUpClass() + super().setUpClass() cls.course = CourseFactory.create() with cls.store.bulk_operations(cls.course.id): cls.chapter = ItemFactory.create( @@ -78,7 +78,7 @@ class GradeTestBase(SharedModuleStoreTestCase): cls.store.update_item(cls.chapter_2, UserFactory().id) def setUp(self): - super(GradeTestBase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.addCleanup(set_current_request, None) self.request = get_mock_request(UserFactory()) self.client.login(username=self.request.user.username, password="test") diff --git a/lms/djangoapps/grades/tests/integration/test_access.py b/lms/djangoapps/grades/tests/integration/test_access.py index 81f1175f5f9..94a6b3d6e92 100644 --- a/lms/djangoapps/grades/tests/integration/test_access.py +++ b/lms/djangoapps/grades/tests/integration/test_access.py @@ -6,11 +6,11 @@ Test grading with access changes. from crum import set_current_request from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory -from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin -from lms.djangoapps.course_blocks.api import get_course_blocks -from openedx.core.djangolib.testing.utils import get_mock_request from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.course_blocks.api import get_course_blocks +from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin +from openedx.core.djangolib.testing.utils import get_mock_request from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -27,7 +27,7 @@ class GradesAccessIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreT @classmethod def setUpClass(cls): - super(GradesAccessIntegrationTest, cls).setUpClass() + super().setUpClass() cls.store = modulestore() cls.course = CourseFactory.create() cls.chapter = ItemFactory.create( @@ -69,13 +69,13 @@ class GradesAccessIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreT ) def setUp(self): - super(GradesAccessIntegrationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.addCleanup(set_current_request, None) self.request = get_mock_request(UserFactory()) self.student = self.request.user self.client.login(username=self.student.username, password="test") CourseEnrollment.enroll(self.student, self.course.id) - self.instructor = UserFactory.create(is_staff=True, username=u'test_instructor', password=u'test') + self.instructor = UserFactory.create(is_staff=True, username='test_instructor', password='test') self.refresh_course() def test_subsection_access_changed(self): diff --git a/lms/djangoapps/grades/tests/integration/test_events.py b/lms/djangoapps/grades/tests/integration/test_events.py index 6ca7424cb9c..139778ad681 100644 --- a/lms/djangoapps/grades/tests/integration/test_events.py +++ b/lms/djangoapps/grades/tests/integration/test_events.py @@ -1,20 +1,18 @@ """ Test grading events across apps. """ +from unittest.mock import call as mock_call +from unittest.mock import patch - -import six from crum import set_current_request -from mock import call as mock_call -from mock import patch from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory +from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin from lms.djangoapps.instructor.enrollment import reset_student_attempts from lms.djangoapps.instructor_task.api import submit_rescore_problem_for_student from openedx.core.djangolib.testing.utils import get_mock_request -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.tests.factories import UserFactory from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -68,13 +66,13 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe def setUp(self): self.reset_course() - super(GradesEventIntegrationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.addCleanup(set_current_request, None) self.request = get_mock_request(UserFactory()) self.student = self.request.user self.client.login(username=self.student.username, password="test") CourseEnrollment.enroll(self.student, self.course.id) - self.instructor = UserFactory.create(is_staff=True, username=u'test_instructor', password=u'test') + self.instructor = UserFactory.create(is_staff=True, username='test_instructor', password='test') self.refresh_course() @patch('lms.djangoapps.grades.events.tracker') @@ -88,11 +86,11 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe mock_call( events.PROBLEM_SUBMITTED_EVENT_TYPE, { - 'user_id': six.text_type(self.student.id), + 'user_id': str(self.student.id), 'event_transaction_id': event_transaction_id, 'event_transaction_type': events.PROBLEM_SUBMITTED_EVENT_TYPE, - 'course_id': six.text_type(self.course.id), - 'problem_id': six.text_type(self.problem.location), + 'course_id': str(self.course.id), + 'problem_id': str(self.problem.location), 'weighted_earned': 2.0, 'weighted_possible': 2.0, }, @@ -100,15 +98,15 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe mock_call( events.COURSE_GRADE_CALCULATED, { - 'course_version': six.text_type(course.course_version), + 'course_version': str(course.course_version), 'percent_grade': 0.02, - 'grading_policy_hash': u'ChVp0lHGQGCevD0t4njna/C44zQ=', - 'user_id': six.text_type(self.student.id), - 'letter_grade': u'', + 'grading_policy_hash': 'ChVp0lHGQGCevD0t4njna/C44zQ=', + 'user_id': str(self.student.id), + 'letter_grade': '', 'event_transaction_id': event_transaction_id, 'event_transaction_type': events.PROBLEM_SUBMITTED_EVENT_TYPE, - 'course_id': six.text_type(self.course.id), - 'course_edited_timestamp': six.text_type(course.subtree_edited_on), + 'course_id': str(self.course.id), + 'course_edited_timestamp': str(course.subtree_edited_on), } ), ], @@ -129,10 +127,10 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe enrollment_tracker.emit.assert_called_with( events.STATE_DELETED_EVENT_TYPE, { - 'user_id': six.text_type(self.student.id), - 'course_id': six.text_type(self.course.id), - 'problem_id': six.text_type(self.problem.location), - 'instructor_id': six.text_type(self.instructor.id), + 'user_id': str(self.student.id), + 'course_id': str(self.course.id), + 'problem_id': str(self.problem.location), + 'instructor_id': str(self.instructor.id), 'event_transaction_id': event_transaction_id, 'event_transaction_type': events.STATE_DELETED_EVENT_TYPE, } @@ -142,14 +140,14 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe events.COURSE_GRADE_CALCULATED, { 'percent_grade': 0.0, - 'grading_policy_hash': u'ChVp0lHGQGCevD0t4njna/C44zQ=', - 'user_id': six.text_type(self.student.id), - 'letter_grade': u'', + 'grading_policy_hash': 'ChVp0lHGQGCevD0t4njna/C44zQ=', + 'user_id': str(self.student.id), + 'letter_grade': '', 'event_transaction_id': event_transaction_id, 'event_transaction_type': events.STATE_DELETED_EVENT_TYPE, - 'course_id': six.text_type(self.course.id), - 'course_edited_timestamp': six.text_type(course.subtree_edited_on), - 'course_version': six.text_type(course.course_version), + 'course_id': str(self.course.id), + 'course_edited_timestamp': str(course.subtree_edited_on), + 'course_version': str(course.course_version), } ) @@ -176,7 +174,7 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe # make sure the tracker's context is updated with course info for args in events_tracker.get_tracker().context.call_args_list: - assert args[0][1] == {'course_id': six.text_type(self.course.id), 'org_id': six.text_type(self.course.org)} + assert args[0][1] == {'course_id': str(self.course.id), 'org_id': str(self.course.org)} event_transaction_id = events_tracker.emit.mock_calls[0][1][1]['event_transaction_id'] events_tracker.emit.assert_has_calls( @@ -184,13 +182,13 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe mock_call( events.GRADES_RESCORE_EVENT_TYPE, { - 'course_id': six.text_type(self.course.id), - 'user_id': six.text_type(self.student.id), - 'problem_id': six.text_type(self.problem.location), + 'course_id': str(self.course.id), + 'user_id': str(self.student.id), + 'problem_id': str(self.problem.location), 'new_weighted_earned': 2, 'new_weighted_possible': 2, 'only_if_higher': False, - 'instructor_id': six.text_type(self.instructor.id), + 'instructor_id': str(self.instructor.id), 'event_transaction_id': event_transaction_id, 'event_transaction_type': events.GRADES_RESCORE_EVENT_TYPE, }, @@ -198,15 +196,15 @@ class GradesEventIntegrationTest(ProblemSubmissionTestMixin, SharedModuleStoreTe mock_call( events.COURSE_GRADE_CALCULATED, { - 'course_version': six.text_type(course.course_version), + 'course_version': str(course.course_version), 'percent_grade': 0.02, - 'grading_policy_hash': u'ChVp0lHGQGCevD0t4njna/C44zQ=', - 'user_id': six.text_type(self.student.id), - 'letter_grade': u'', + 'grading_policy_hash': 'ChVp0lHGQGCevD0t4njna/C44zQ=', + 'user_id': str(self.student.id), + 'letter_grade': '', 'event_transaction_id': event_transaction_id, 'event_transaction_type': events.GRADES_RESCORE_EVENT_TYPE, - 'course_id': six.text_type(self.course.id), - 'course_edited_timestamp': six.text_type(course.subtree_edited_on), + 'course_id': str(self.course.id), + 'course_edited_timestamp': str(course.subtree_edited_on), }, ), ], diff --git a/lms/djangoapps/grades/tests/integration/test_problems.py b/lms/djangoapps/grades/tests/integration/test_problems.py index 1fc3e6dbefb..1b0727619fb 100644 --- a/lms/djangoapps/grades/tests/integration/test_problems.py +++ b/lms/djangoapps/grades/tests/integration/test_problems.py @@ -5,14 +5,13 @@ import itertools import ddt import pytz from crum import set_current_request -from six.moves import range from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory -from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin -from lms.djangoapps.course_blocks.api import get_course_blocks -from openedx.core.djangolib.testing.utils import get_mock_request from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.course_blocks.api import get_course_blocks +from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin +from openedx.core.djangolib.testing.utils import get_mock_request from xmodule.graders import ProblemScore from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase @@ -35,14 +34,14 @@ class TestMultipleProblemTypesSubsectionScores(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TestMultipleProblemTypesSubsectionScores, cls).setUpClass() + super().setUpClass() cls.load_scoreable_course() chapter1 = cls.course.get_children()[0] cls.seq1 = chapter1.get_children()[0] def setUp(self): super().setUp() - password = u'test' + password = 'test' self.student = UserFactory.create(is_staff=False, username=u'test_student', password=password) self.client.login(username=self.student.username, password=password) self.addCleanup(set_current_request, None) @@ -104,13 +103,13 @@ class TestVariedMetadata(ProblemSubmissionTestMixin, ModuleStoreTestCase): persisted score. """ default_problem_metadata = { - u'graded': True, - u'weight': 2.5, - u'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'graded': True, + 'weight': 2.5, + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), } def setUp(self): - super(TestVariedMetadata, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course = CourseFactory.create() with self.store.bulk_operations(self.course.id): self.chapter = ItemFactory.create( @@ -129,7 +128,7 @@ class TestVariedMetadata(ProblemSubmissionTestMixin, ModuleStoreTestCase): category='vertical', display_name='Test Vertical 1' ) - self.problem_xml = u''' + self.problem_xml = ''' <problem url_name="capa-optionresponse"> <optionresponse> <optioninput options="('Correct', 'Incorrect')" correct="Correct"></optioninput> @@ -171,7 +170,7 @@ class TestVariedMetadata(ProblemSubmissionTestMixin, ModuleStoreTestCase): two) is submitted. """ - self.submit_question_answer(u'problem', {u'2_1': u'Correct'}) + self.submit_question_answer('problem', {'2_1': 'Correct'}) course_structure = get_course_blocks(self.request.user, self.course.location) subsection_factory = SubsectionGradeFactory( self.request.user, @@ -182,10 +181,10 @@ class TestVariedMetadata(ProblemSubmissionTestMixin, ModuleStoreTestCase): @ddt.data( ({}, 1.25, 2.5), - ({u'weight': 27}, 13.5, 27), - ({u'weight': 1.0}, 0.5, 1.0), - ({u'weight': 0.0}, 0.0, 0.0), - ({u'weight': None}, 1.0, 2.0), + ({'weight': 27}, 13.5, 27), + ({'weight': 1.0}, 0.5, 1.0), + ({'weight': 0.0}, 0.0, 0.0), + ({'weight': None}, 1.0, 2.0), ) @ddt.unpack def test_weight_metadata_alterations(self, alterations, expected_earned, expected_possible): @@ -195,8 +194,8 @@ class TestVariedMetadata(ProblemSubmissionTestMixin, ModuleStoreTestCase): assert score.all_total.possible == expected_possible @ddt.data( - ({u'graded': True}, 1.25, 2.5), - ({u'graded': False}, 0.0, 0.0), + ({'graded': True}, 1.25, 2.5), + ({'graded': False}, 0.0, 0.0), ) @ddt.unpack def test_graded_metadata_alterations(self, alterations, expected_earned, expected_possible): @@ -214,7 +213,7 @@ class TestWeightedProblems(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TestWeightedProblems, cls).setUpClass() + super().setUpClass() cls.course = CourseFactory.create() with cls.store.bulk_operations(cls.course.id): cls.chapter = ItemFactory.create(parent=cls.course, category="chapter", display_name="chapter") @@ -227,13 +226,13 @@ class TestWeightedProblems(SharedModuleStoreTestCase): ItemFactory.create( parent=cls.vertical, category="problem", - display_name="problem_{}".format(i), + display_name=f"problem_{i}", data=problem_xml, ) ) def setUp(self): - super(TestWeightedProblems, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory() self.addCleanup(set_current_request, None) self.request = get_mock_request(self.user) diff --git a/lms/djangoapps/grades/tests/test_api.py b/lms/djangoapps/grades/tests/test_api.py index 3c53397fb17..82f290b1cd0 100644 --- a/lms/djangoapps/grades/tests/test_api.py +++ b/lms/djangoapps/grades/tests/test_api.py @@ -1,15 +1,13 @@ """ Tests calling the grades api directly """ +from unittest.mock import patch + import ddt -from mock import patch -from lms.djangoapps.grades import api -from lms.djangoapps.grades.models import ( - PersistentSubsectionGrade, - PersistentSubsectionGradeOverride, -) from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.grades import api +from lms.djangoapps.grades.models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -22,7 +20,7 @@ class OverrideSubsectionGradeTests(ModuleStoreTestCase): @classmethod def setUpTestData(cls): - super(OverrideSubsectionGradeTests, cls).setUpTestData() + super().setUpTestData() cls.user = UserFactory() cls.overriding_user = UserFactory() cls.signal_patcher = patch('lms.djangoapps.grades.signals.signals.SUBSECTION_OVERRIDE_CHANGED.send') @@ -35,13 +33,13 @@ class OverrideSubsectionGradeTests(ModuleStoreTestCase): @classmethod def tearDownClass(cls): - super(OverrideSubsectionGradeTests, cls).tearDownClass() + super().tearDownClass() cls.signal_patcher.stop() cls.id_patcher.stop() cls.type_patcher.stop() def setUp(self): - super(OverrideSubsectionGradeTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course', run='Spring2019') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") self.grade = PersistentSubsectionGrade.update_or_create_grade( @@ -57,7 +55,7 @@ class OverrideSubsectionGradeTests(ModuleStoreTestCase): ) def tearDown(self): - super(OverrideSubsectionGradeTests, self).tearDown() # lint-amnesty, pylint: disable=super-with-arguments + super().tearDown() PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides @ddt.data(0.0, None, 3.0) diff --git a/lms/djangoapps/grades/tests/test_course_data.py b/lms/djangoapps/grades/tests/test_course_data.py index ccbe5b252e6..6345854ef6d 100644 --- a/lms/djangoapps/grades/tests/test_course_data.py +++ b/lms/djangoapps/grades/tests/test_course_data.py @@ -1,14 +1,13 @@ """ Tests for CourseData utility class. """ +from unittest.mock import patch import pytest -import six -from mock import patch +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.course_blocks.api import get_course_blocks from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache -from common.djangoapps.student.tests.factories import UserFactory from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -22,7 +21,7 @@ class CourseDataTest(ModuleStoreTestCase): """ def setUp(self): - super(CourseDataTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() with self.store.default_store(ModuleStoreEnum.Type.split): self.course = CourseFactory.create() # need to re-retrieve the course since the version on the original course isn't accurate. @@ -77,7 +76,7 @@ class CourseDataTest(ModuleStoreTestCase): assert course_data.course.id == self.course.id assert course_data.version == self.course.course_version assert course_data.edited_on == expected_edited_on - assert u'Course: course_key' in six.text_type(course_data) + assert u'Course: course_key' in str(course_data) assert u'Course: course_key' in course_data.full_string() def test_no_data(self): @@ -93,8 +92,8 @@ class CourseDataTest(ModuleStoreTestCase): course_data = CourseData( self.user, structure=empty_structure, collected_block_structure=self.collected_structure, ) - assert u'Course: course_key: {}, version:'.format(self.course.id) in course_data.full_string() + assert 'Course: course_key: {}, version:'.format(self.course.id) in course_data.full_string() # full_string returns minimal value when structures aren't readily available. course_data = CourseData(self.user, course_key=self.course.id) - assert u'empty course structure' in course_data.full_string() + assert 'empty course structure' in course_data.full_string() diff --git a/lms/djangoapps/grades/tests/test_course_grade.py b/lms/djangoapps/grades/tests/test_course_grade.py index b4313d9de6a..ec933f8501d 100644 --- a/lms/djangoapps/grades/tests/test_course_grade.py +++ b/lms/djangoapps/grades/tests/test_course_grade.py @@ -1,14 +1,14 @@ # lint-amnesty, pylint: disable=missing-module-docstring +from unittest.mock import patch + import ddt -import six from crum import set_current_request from django.conf import settings -from mock import patch - from edx_toggles.toggles.testutils import override_waffle_switch -from openedx.core.djangolib.testing.utils import get_mock_request + from common.djangoapps.student.models import CourseEnrollment from common.djangoapps.student.tests.factories import UserFactory +from openedx.core.djangolib.testing.utils import get_mock_request from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -38,7 +38,7 @@ class ZeroGradeTest(GradeTestBase): chapter_grades = ZeroCourseGrade(self.request.user, course_data).chapter_grades for chapter in chapter_grades: for section in chapter_grades[chapter]['sections']: - for score in six.itervalues(section.problem_scores): + for score in section.problem_scores.values(): assert score.earned == 0 assert score.first_attempted is None assert section.all_total.earned == 0 @@ -76,7 +76,7 @@ class TestScoreForModule(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TestScoreForModule, cls).setUpClass() + super().setUpClass() cls.course = CourseFactory.create() with cls.store.bulk_operations(cls.course.id): cls.a = ItemFactory.create(parent=cls.course, category="chapter", display_name="a") @@ -107,7 +107,7 @@ class TestScoreForModule(SharedModuleStoreTestCase): @classmethod def tearDownClass(cls): - super(TestScoreForModule, cls).tearDownClass() + super().tearDownClass() set_current_request(None) def test_score_chapter(self): diff --git a/lms/djangoapps/grades/tests/test_course_grade_factory.py b/lms/djangoapps/grades/tests/test_course_grade_factory.py index c6145131c36..acc2030bff0 100644 --- a/lms/djangoapps/grades/tests/test_course_grade_factory.py +++ b/lms/djangoapps/grades/tests/test_course_grade_factory.py @@ -1,19 +1,17 @@ """ Tests for the CourseGradeFactory class. """ - - import itertools +from unittest.mock import patch import ddt from django.conf import settings -from mock import patch -from six import text_type from edx_toggles.toggles.testutils import override_waffle_switch + +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.courseware.access import has_access from lms.djangoapps.grades.config.tests.utils import persistent_grades_feature_flags from openedx.core.djangoapps.content.block_structure.factory import BlockStructureFactory -from common.djangoapps.student.tests.factories import UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -87,7 +85,7 @@ class TestCourseGradeFactory(GradeTestBase): _assert_section_order(course_grade) def _assert_grade_values(course_grade, expected_pass, expected_percent): - assert course_grade.letter_grade == (u'Pass' if expected_pass else None) + assert course_grade.letter_grade == ('Pass' if expected_pass else None) assert course_grade.percent == expected_percent def _assert_section_order(course_grade): @@ -217,27 +215,27 @@ class TestCourseGradeFactory(GradeTestBase): 'section_breakdown': [ { 'category': 'Homework', - 'detail': u'Homework 1 - Test Sequential X - 50% (1/2)', - 'label': u'HW 01', + 'detail': 'Homework 1 - Test Sequential X - 50% (1/2)', + 'label': 'HW 01', 'percent': 0.5 }, { 'category': 'Homework', - 'detail': u'Homework 2 - Test Sequential A - 0% (0/1)', - 'label': u'HW 02', + 'detail': 'Homework 2 - Test Sequential A - 0% (0/1)', + 'label': 'HW 02', 'percent': 0.0 }, { 'category': 'Homework', - 'detail': u'Homework Average = 25%', - 'label': u'HW Avg', + 'detail': 'Homework Average = 25%', + 'label': 'HW Avg', 'percent': 0.25, 'prominent': True }, { 'category': 'NoCredit', - 'detail': u'NoCredit Average = 0%', - 'label': u'NC Avg', + 'detail': 'NoCredit Average = 0%', + 'label': 'NC Avg', 'percent': 0, 'prominent': True }, @@ -255,7 +253,7 @@ class TestGradeIteration(SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TestGradeIteration, cls).setUpClass() + super().setUpClass() cls.course = CourseFactory.create( display_name=cls.COURSE_NAME, number=cls.COURSE_NUM @@ -265,7 +263,7 @@ class TestGradeIteration(SharedModuleStoreTestCase): """ Create a course and a handful of users to assign grades """ - super(TestGradeIteration, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.students = [ UserFactory.create(username='student1'), @@ -313,14 +311,14 @@ class TestGradeIteration(SharedModuleStoreTestCase): student1, student2, student3, student4, student5 = self.students mock_course_grade.side_effect = [ - Exception(u"Error for {}.".format(student.username)) + Exception(f"Error for {student.username}.") if student.username in ['student3', 'student4'] else mock_course_grade.return_value for student in self.students ] with self.assertNumQueries(8): all_course_grades, all_errors = self._course_grades_and_errors_for(self.course, self.students) - assert {student: text_type(all_errors[student]) for student in all_errors} == { + assert {student: str(all_errors[student]) for student in all_errors} == { student3: 'Error for student3.', student4: 'Error for student4.' } diff --git a/lms/djangoapps/grades/tests/test_models.py b/lms/djangoapps/grades/tests/test_models.py index fe351f3d086..cdcccd751aa 100644 --- a/lms/djangoapps/grades/tests/test_models.py +++ b/lms/djangoapps/grades/tests/test_models.py @@ -8,19 +8,20 @@ from base64 import b64encode from collections import OrderedDict from datetime import datetime from hashlib import sha1 -import pytest +from unittest.mock import patch import ddt +import pytest import pytz -import six from django.db.utils import IntegrityError from django.test import TestCase from django.utils.timezone import now from freezegun import freeze_time -from mock import patch from opaque_keys import InvalidKeyError from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator +from common.djangoapps.student.tests.factories import UserFactory +from common.djangoapps.track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum from lms.djangoapps.grades.models import ( BLOCK_RECORD_LIST_VERSION, @@ -31,8 +32,6 @@ from lms.djangoapps.grades.models import ( PersistentSubsectionGradeOverride, VisibleBlocks ) -from common.djangoapps.student.tests.factories import UserFactory -from common.djangoapps.track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type class BlockRecordListTestCase(TestCase): @@ -41,7 +40,7 @@ class BlockRecordListTestCase(TestCase): """ def setUp(self): - super(BlockRecordListTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course_key = CourseLocator( org='some_org', course='some_course', @@ -49,8 +48,8 @@ class BlockRecordListTestCase(TestCase): ) def test_empty_block_record_set(self): - empty_json = u'{"blocks":[],"course_key":"%s","version":%s}' % ( - six.text_type(self.course_key), + empty_json = '{{"blocks":[],"course_key":"{}","version":{}}}'.format( + str(self.course_key), BLOCK_RECORD_LIST_VERSION, ) @@ -65,7 +64,7 @@ class GradesModelTestCase(TestCase): Base class for common setup of grades model tests. """ def setUp(self): - super(GradesModelTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.course_key = CourseLocator( org='some_org', course='some_course', @@ -131,7 +130,7 @@ class VisibleBlocksTest(GradesModelTestCase): """ def setUp(self): - super(VisibleBlocksTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user_id = 12345 def _create_block_record_list(self, blocks, user_id=None): @@ -148,15 +147,15 @@ class VisibleBlocksTest(GradesModelTestCase): vblocks = self._create_block_record_list([self.record_a]) list_of_block_dicts = [self.record_a._asdict()] for block_dict in list_of_block_dicts: - block_dict['locator'] = six.text_type(block_dict['locator']) # BlockUsageLocator is not json-serializable + block_dict['locator'] = str(block_dict['locator']) # BlockUsageLocator is not json-serializable expected_data = { 'blocks': [{ - 'locator': six.text_type(self.record_a.locator), + 'locator': str(self.record_a.locator), 'raw_possible': 10, 'weight': 1, 'graded': self.record_a.graded, }], - 'course_key': six.text_type(self.record_a.locator.course_key), + 'course_key': str(self.record_a.locator.course_key), 'version': BLOCK_RECORD_LIST_VERSION, } expected_json = json.dumps(expected_data, separators=(',', ':'), sort_keys=True) @@ -204,7 +203,7 @@ class PersistentSubsectionGradeTest(GradesModelTestCase): """ def setUp(self): - super(PersistentSubsectionGradeTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.usage_key = BlockUsageLocator( course_key=self.course_key, block_type='subsection', @@ -329,21 +328,21 @@ class PersistentSubsectionGradeTest(GradesModelTestCase): was called with the expected info based on the passed grade. """ tracker_mock.emit.assert_called_with( - u'edx.grades.subsection.grade_calculated', + 'edx.grades.subsection.grade_calculated', { - 'user_id': six.text_type(grade.user_id), - 'course_id': six.text_type(grade.course_id), - 'block_id': six.text_type(grade.usage_key), - 'course_version': six.text_type(grade.course_version), + 'user_id': str(grade.user_id), + 'course_id': str(grade.course_id), + 'block_id': str(grade.usage_key), + 'course_version': str(grade.course_version), 'weighted_total_earned': grade.earned_all, 'weighted_total_possible': grade.possible_all, 'weighted_graded_earned': grade.earned_graded, 'weighted_graded_possible': grade.possible_graded, - 'first_attempted': six.text_type(grade.first_attempted), - 'subtree_edited_timestamp': six.text_type(grade.subtree_edited_timestamp), - 'event_transaction_id': six.text_type(get_event_transaction_id()), - 'event_transaction_type': six.text_type(get_event_transaction_type()), - 'visible_blocks_hash': six.text_type(grade.visible_blocks_id), + 'first_attempted': str(grade.first_attempted), + 'subtree_edited_timestamp': str(grade.subtree_edited_timestamp), + 'event_transaction_id': str(get_event_transaction_id()), + 'event_transaction_type': str(get_event_transaction_type()), + 'visible_blocks_hash': str(grade.visible_blocks_id), } ) @@ -355,7 +354,7 @@ class PersistentCourseGradesTest(GradesModelTestCase): """ def setUp(self): - super(PersistentCourseGradesTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.params = { "user_id": 12345, "course_id": self.course_key, @@ -387,43 +386,43 @@ class PersistentCourseGradesTest(GradesModelTestCase): def test_passed_timestamp(self): # When the user has not passed, passed_timestamp is None self.params.update({ - u'percent_grade': 25.0, - u'letter_grade': u'', - u'passed': False, + 'percent_grade': 25.0, + 'letter_grade': '', + 'passed': False, }) grade = PersistentCourseGrade.update_or_create(**self.params) assert grade.passed_timestamp is None # After the user earns a passing grade, the passed_timestamp is set self.params.update({ - u'percent_grade': 75.0, - u'letter_grade': u'C', - u'passed': True, + 'percent_grade': 75.0, + 'letter_grade': 'C', + 'passed': True, }) grade = PersistentCourseGrade.update_or_create(**self.params) passed_timestamp = grade.passed_timestamp - assert grade.letter_grade == u'C' + assert grade.letter_grade == 'C' assert isinstance(passed_timestamp, datetime) # After the user improves their score, the new grade is reflected, but # the passed_timestamp remains the same. self.params.update({ - u'percent_grade': 95.0, - u'letter_grade': u'A', - u'passed': True, + 'percent_grade': 95.0, + 'letter_grade': 'A', + 'passed': True, }) grade = PersistentCourseGrade.update_or_create(**self.params) - assert grade.letter_grade == u'A' + assert grade.letter_grade == 'A' assert grade.passed_timestamp == passed_timestamp # If the grade later reverts to a failing grade, passed_timestamp remains the same. self.params.update({ - u'percent_grade': 20.0, - u'letter_grade': u'', - u'passed': False, + 'percent_grade': 20.0, + 'letter_grade': '', + 'passed': False, }) grade = PersistentCourseGrade.update_or_create(**self.params) - assert grade.letter_grade == u'' + assert grade.letter_grade == '' assert grade.passed_timestamp == passed_timestamp def test_passed_timestamp_is_now(self): @@ -435,7 +434,7 @@ class PersistentCourseGradesTest(GradesModelTestCase): created_grade = PersistentCourseGrade.update_or_create(**self.params) read_grade = PersistentCourseGrade.read(self.params["user_id"], self.params["course_id"]) for param in self.params: - if param == u'passed': + if param == 'passed': continue # passed/passed_timestamp takes special handling, and is tested separately assert self.params[param] == getattr(created_grade, param) assert isinstance(created_grade.passed_timestamp, datetime) @@ -476,16 +475,16 @@ class PersistentCourseGradesTest(GradesModelTestCase): was called with the expected info based on the passed grade. """ tracker_mock.emit.assert_called_with( - u'edx.grades.course.grade_calculated', + 'edx.grades.course.grade_calculated', { - 'user_id': six.text_type(grade.user_id), - 'course_id': six.text_type(grade.course_id), - 'course_version': six.text_type(grade.course_version), + 'user_id': str(grade.user_id), + 'course_id': str(grade.course_id), + 'course_version': str(grade.course_version), 'percent_grade': grade.percent_grade, - 'letter_grade': six.text_type(grade.letter_grade), - 'course_edited_timestamp': six.text_type(grade.course_edited_timestamp), - 'event_transaction_id': six.text_type(get_event_transaction_id()), - 'event_transaction_type': six.text_type(get_event_transaction_type()), - 'grading_policy_hash': six.text_type(grade.grading_policy_hash), + 'letter_grade': str(grade.letter_grade), + 'course_edited_timestamp': str(grade.course_edited_timestamp), + 'event_transaction_id': str(get_event_transaction_id()), + 'event_transaction_type': str(get_event_transaction_type()), + 'grading_policy_hash': str(grade.grading_policy_hash), } ) diff --git a/lms/djangoapps/grades/tests/test_scores.py b/lms/djangoapps/grades/tests/test_scores.py index 32dbdf626d5..212258cab06 100644 --- a/lms/djangoapps/grades/tests/test_scores.py +++ b/lms/djangoapps/grades/tests/test_scores.py @@ -27,7 +27,7 @@ def submission_value_repr(self): the "created_at" attribute that changes with each execution. Needed for consistency of ddt-generated test methods across pytest-xdist workers. """ - return u'<SubmissionValue exists={}>'.format(self.exists) + return f'<SubmissionValue exists={self.exists}>' def csm_value_repr(self): @@ -36,7 +36,7 @@ def csm_value_repr(self): the "created" attribute that changes with each execution. Needed for consistency of ddt-generated test methods across pytest-xdist workers. """ - return u'<CSMValue exists={} raw_earned={}>'.format(self.exists, self.raw_earned) + return f'<CSMValue exists={self.exists} raw_earned={self.raw_earned}>' def expected_result_repr(self): @@ -47,7 +47,7 @@ def expected_result_repr(self): """ included = ('raw_earned', 'raw_possible', 'weighted_earned', 'weighted_possible', 'weight', 'graded') attributes = ['{}={}'.format(name, getattr(self, name)) for name in included] - return u'<ExpectedResult {}>'.format(' '.join(attributes)) + return '<ExpectedResult {}>'.format(' '.join(attributes)) class TestScoredBlockTypes(TestCase): @@ -65,7 +65,7 @@ class TestScoredBlockTypes(TestCase): assert self.possibly_scored_block_types.issubset(scores._block_types_possibly_scored()) def test_possibly_scored(self): - course_key = CourseLocator(u'org', u'course', u'run') + course_key = CourseLocator('org', 'course', 'run') for block_type in self.possibly_scored_block_types: usage_key = BlockUsageLocator(course_key, block_type, 'mock_block_id') assert scores.possibly_scored(usage_key) @@ -77,7 +77,7 @@ class TestGetScore(TestCase): Tests for get_score """ display_name = 'test_name' - course_key = CourseLocator(u'org', u'course', u'run') + course_key = CourseLocator('org', 'course', 'run') location = BlockUsageLocator(course_key, 'problem', 'mock_block_id') SubmissionValue = namedtuple('SubmissionValue', 'exists, points_earned, points_possible, created_at') @@ -298,7 +298,7 @@ class TestInternalGetScoreFromBlock(TestCase): """ Tests the internal helper method: _get_score_from_persisted_or_latest_block """ - course_key = CourseLocator(u'org', u'course', u'run') + course_key = CourseLocator('org', 'course', 'run') location = BlockUsageLocator(course_key, 'problem', 'mock_block_id') def _create_block(self, raw_possible): diff --git a/lms/djangoapps/grades/tests/test_services.py b/lms/djangoapps/grades/tests/test_services.py index eb3edc1f50c..5e1b8b31aec 100644 --- a/lms/djangoapps/grades/tests/test_services.py +++ b/lms/djangoapps/grades/tests/test_services.py @@ -4,20 +4,16 @@ Grades Service Tests from datetime import datetime +from unittest.mock import call, patch import ddt import pytz -import six from freezegun import freeze_time -from mock import call, patch +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum -from lms.djangoapps.grades.models import ( - PersistentSubsectionGrade, - PersistentSubsectionGradeOverride -) +from lms.djangoapps.grades.models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride from lms.djangoapps.grades.services import GradesService -from common.djangoapps.student.tests.factories import UserFactory from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -25,7 +21,7 @@ from ..config.waffle import REJECTED_EXAM_OVERRIDES_GRADE from ..constants import ScoreDatabaseTableEnum -class MockWaffleFlag(object): +class MockWaffleFlag: """ A Mock WaffleFlag object. """ @@ -44,7 +40,7 @@ class GradesServiceTests(ModuleStoreTestCase): """ def setUp(self): - super(GradesServiceTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.service = GradesService() self.course = CourseFactory.create(org='edX', number='DemoX', display_name='Demo_Course', run='Spring2019') self.subsection = ItemFactory.create(parent=self.course, category="subsection", display_name="Subsection") @@ -79,7 +75,7 @@ class GradesServiceTests(ModuleStoreTestCase): } def tearDown(self): - super(GradesServiceTests, self).tearDown() # lint-amnesty, pylint: disable=super-with-arguments + super().tearDown() PersistentSubsectionGradeOverride.objects.all().delete() # clear out all previous overrides self.signal_patcher.stop() self.id_patcher.stop() @@ -111,8 +107,8 @@ class GradesServiceTests(ModuleStoreTestCase): # test with id strings as parameters instead self.assertDictEqual(self.subsection_grade_to_dict(self.service.get_subsection_grade( user_id=self.user.id, - course_key_or_id=six.text_type(self.course.id), - usage_key_or_id=six.text_type(self.subsection.location) + course_key_or_id=str(self.course.id), + usage_key_or_id=str(self.subsection.location) )), { 'earned_all': 6.0, 'earned_graded': 5.0 @@ -140,7 +136,7 @@ class GradesServiceTests(ModuleStoreTestCase): # test with course key parameter as string instead self.assertDictEqual(self.subsection_grade_override_to_dict(self.service.get_subsection_grade_override( user_id=self.user.id, - course_key_or_id=six.text_type(self.course.id), + course_key_or_id=str(self.course.id), usage_key_or_id=self.subsection.location )), { 'earned_all_override': override.earned_all_override, @@ -186,8 +182,8 @@ class GradesServiceTests(ModuleStoreTestCase): assert self.mock_signal.call_args == call( sender=None, user_id=self.user.id, - course_id=six.text_type(self.course.id), - usage_id=six.text_type(self.subsection.location), + course_id=str(self.course.id), + usage_id=str(self.subsection.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, @@ -232,8 +228,8 @@ class GradesServiceTests(ModuleStoreTestCase): assert self.mock_signal.call_args == call( sender=None, user_id=self.user.id, - course_id=six.text_type(self.course.id), - usage_id=six.text_type(self.subsection_without_grade.location), + course_id=str(self.course.id), + usage_id=str(self.subsection_without_grade.location), only_if_higher=False, modified=override_obj.modified, score_deleted=False, @@ -259,8 +255,8 @@ class GradesServiceTests(ModuleStoreTestCase): assert self.mock_signal.call_args == call( sender=None, user_id=self.user.id, - course_id=six.text_type(self.course.id), - usage_id=six.text_type(self.subsection.location), + course_id=str(self.course.id), + usage_id=str(self.subsection.location), only_if_higher=False, modified=datetime.now().replace(tzinfo=pytz.UTC), score_deleted=True, diff --git a/lms/djangoapps/grades/tests/test_signals.py b/lms/djangoapps/grades/tests/test_signals.py index ec445a0b508..f3f079caede 100644 --- a/lms/djangoapps/grades/tests/test_signals.py +++ b/lms/djangoapps/grades/tests/test_signals.py @@ -5,12 +5,12 @@ Tests for the score change signals defined in the courseware models module. import re from datetime import datetime -import pytest +from unittest.mock import MagicMock, patch import ddt +import pytest import pytz from django.test import TestCase -from mock import MagicMock, patch from submissions.models import score_reset, score_set from common.djangoapps.util.date_utils import to_timestamp @@ -24,7 +24,7 @@ from ..signals.handlers import ( ) from ..signals.signals import PROBLEM_RAW_SCORE_CHANGED -UUID_REGEX = re.compile(u'%(hex)s{8}-%(hex)s{4}-%(hex)s{4}-%(hex)s{4}-%(hex)s{12}' % {'hex': u'[0-9a-f]'}) +UUID_REGEX = re.compile('{hex}{{8}}-{hex}{{4}}-{hex}{{4}}-{hex}{{4}}-{hex}{{12}}'.format(hex='[0-9a-f]')) FROZEN_NOW_DATETIME = datetime.now().replace(tzinfo=pytz.UTC) FROZEN_NOW_TIMESTAMP = to_timestamp(FROZEN_NOW_DATETIME) @@ -104,7 +104,7 @@ class ScoreChangedSignalRelayTest(TestCase): """ Configure mocks for all the dependencies of the render method """ - super(ScoreChangedSignalRelayTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.signal_mock = self.setup_patch( 'lms.djangoapps.grades.signals.signals.PROBLEM_WEIGHTED_SCORE_CHANGED.send', None, diff --git a/lms/djangoapps/grades/tests/test_subsection_grade_factory.py b/lms/djangoapps/grades/tests/test_subsection_grade_factory.py index 79453783c11..f3d37b3b0f7 100644 --- a/lms/djangoapps/grades/tests/test_subsection_grade_factory.py +++ b/lms/djangoapps/grades/tests/test_subsection_grade_factory.py @@ -3,13 +3,14 @@ Tests for the SubsectionGradeFactory class. """ +from unittest.mock import patch + import ddt from django.conf import settings -from mock import patch +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin from lms.djangoapps.grades.config.tests.utils import persistent_grades_feature_flags -from common.djangoapps.student.tests.factories import UserFactory from ..constants import GradeOverrideFeatureEnum from ..models import PersistentSubsectionGrade, PersistentSubsectionGradeOverride diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index 488e7627a9c..45928c95d63 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -7,18 +7,20 @@ import itertools from collections import OrderedDict from contextlib import contextmanager from datetime import datetime, timedelta -import pytest +from unittest.mock import MagicMock, patch import ddt +import pytest import pytz -import six from django.conf import settings from django.db.utils import IntegrityError from django.utils import timezone -from mock import MagicMock, patch -from six.moves import range - from edx_toggles.toggles.testutils import override_waffle_flag + +from common.djangoapps.student.models import CourseEnrollment, anonymous_id_for_user +from common.djangoapps.student.tests.factories import UserFactory +from common.djangoapps.track.event_transaction_utils import create_new_event_transaction_id, get_event_transaction_id +from common.djangoapps.util.date_utils import to_timestamp from lms.djangoapps.grades import tasks from lms.djangoapps.grades.config.models import PersistentGradesEnabledFlag from lms.djangoapps.grades.config.waffle import ENFORCE_FREEZE_GRADE_AFTER_COURSE_END, waffle_flags @@ -35,10 +37,6 @@ from lms.djangoapps.grades.tasks import ( recalculate_subsection_grade_v3 ) from openedx.core.djangoapps.content.block_structure.exceptions import BlockStructureNotFound -from common.djangoapps.student.models import CourseEnrollment, anonymous_id_for_user -from common.djangoapps.student.tests.factories import UserFactory -from common.djangoapps.track.event_transaction_utils import create_new_event_transaction_id, get_event_transaction_id -from common.djangoapps.util.date_utils import to_timestamp from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase @@ -47,7 +45,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, chec from .utils import mock_get_score -class HasCourseWithProblemsMixin(object): +class HasCourseWithProblemsMixin: """ Mixin to provide tests with a sample course with graded subsections """ @@ -80,8 +78,8 @@ class HasCourseWithProblemsMixin(object): ('weighted_possible', 2.0), ('user_id', self.user.id), ('anonymous_user_id', 5), - ('course_id', six.text_type(self.course.id)), - ('usage_id', six.text_type(self.problem.location)), + ('course_id', str(self.course.id)), + ('usage_id', str(self.problem.location)), ('only_if_higher', None), ('modified', self.frozen_now_datetime), ('score_db_table', ScoreDatabaseTableEnum.courseware_student_module), @@ -91,14 +89,14 @@ class HasCourseWithProblemsMixin(object): self.recalculate_subsection_grade_kwargs = OrderedDict([ ('user_id', self.user.id), - ('course_id', six.text_type(self.course.id)), - ('usage_id', six.text_type(self.problem.location)), + ('course_id', str(self.course.id)), + ('usage_id', str(self.problem.location)), ('anonymous_user_id', 5), ('only_if_higher', None), ('expected_modified_time', self.frozen_now_timestamp), ('score_deleted', False), - ('event_transaction_id', six.text_type(get_event_transaction_id())), - ('event_transaction_type', u'edx.grades.problem.submitted'), + ('event_transaction_id', str(get_event_transaction_id())), + ('event_transaction_type', 'edx.grades.problem.submitted'), ('score_db_table', ScoreDatabaseTableEnum.courseware_student_module), ]) @@ -116,7 +114,7 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest ENABLED_SIGNALS = ['course_published', 'pre_publish'] def setUp(self): - super(RecalculateSubsectionGradeTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory() PersistentGradesEnabledFlag.objects.create(enabled_for_all_courses=True, enabled=True) @@ -136,7 +134,7 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest self.set_up_course() send_args = self.problem_weighted_score_changed_kwargs local_task_args = self.recalculate_subsection_grade_kwargs.copy() - local_task_args['event_transaction_type'] = u'edx.grades.problem.submitted' + local_task_args['event_transaction_type'] = 'edx.grades.problem.submitted' local_task_args['force_update_subsections'] = False with self.mock_csm_get_score() and patch( 'lms.djangoapps.grades.tasks.recalculate_subsection_grade_v3.apply_async', @@ -304,7 +302,7 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest recalculate_subsection_grade_v3.apply(kwargs=self.recalculate_subsection_grade_kwargs) self._assert_retry_called(mock_retry) - assert u'Grades: tasks._has_database_updated_with_new_score is False.' in mock_log.info.call_args_list[0][0][0] + assert 'Grades: tasks._has_database_updated_with_new_score is False.' in mock_log.info.call_args_list[0][0][0] @ddt.data( *itertools.product( @@ -338,7 +336,7 @@ class RecalculateSubsectionGradeTest(HasCourseWithProblemsMixin, ModuleStoreTest self._assert_retry_not_called(mock_retry) else: self._assert_retry_called(mock_retry) - assert u'Grades: tasks._has_database_updated_with_new_score is False.'\ + assert 'Grades: tasks._has_database_updated_with_new_score is False.'\ in mock_log.info.call_args_list[0][0][0] @patch('lms.djangoapps.grades.tasks.log') @@ -407,7 +405,7 @@ class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase ENABLED_SIGNALS = ['course_published', 'pre_publish'] def setUp(self): - super(ComputeGradesForCourseTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.users = [UserFactory.create() for _ in range(12)] self.set_up_course() for user in self.users: @@ -417,7 +415,7 @@ class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase def test_behavior(self, batch_size): with mock_get_score(1, 2): result = compute_grades_for_course_v2.delay( - course_key=six.text_type(self.course.id), + course_key=str(self.course.id), batch_size=batch_size, offset=4, ) @@ -431,7 +429,7 @@ class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase for course_key, offset, batch_size in _course_task_args( batch_size=test_batch_size, course_key=self.course.id, from_settings=False ): - assert course_key == six.text_type(self.course.id) + assert course_key == str(self.course.id) assert batch_size == test_batch_size assert offset == offset_expected offset_expected += test_batch_size @@ -442,7 +440,7 @@ class RecalculateGradesForUserTest(HasCourseWithProblemsMixin, ModuleStoreTestCa Test recalculate_course_and_subsection_grades_for_user task. """ def setUp(self): - super(RecalculateGradesForUserTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.user = UserFactory.create() self.set_up_course() CourseEnrollment.enroll(self.user, self.course.id) @@ -454,7 +452,7 @@ class RecalculateGradesForUserTest(HasCourseWithProblemsMixin, ModuleStoreTestCa kwargs = { 'user_id': self.user.id, - 'course_key': six.text_type(self.course.id), + 'course_key': str(self.course.id), } task_result = tasks.recalculate_course_and_subsection_grades_for_user.apply_async(kwargs=kwargs) @@ -474,7 +472,7 @@ class RecalculateGradesForUserTest(HasCourseWithProblemsMixin, ModuleStoreTestCa kwargs = { 'user_id': self.user.id, - 'course_key': six.text_type(self.course.id), + 'course_key': str(self.course.id), } task_result = tasks.recalculate_course_and_subsection_grades_for_user.apply_async(kwargs=kwargs) @@ -490,14 +488,14 @@ class FreezeGradingAfterCourseEndTest(HasCourseWithProblemsMixin, ModuleStoreTes Test enforce_freeze_grade_after_course_end waffle flag controlling grading tasks. """ def setUp(self): - super(FreezeGradingAfterCourseEndTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments + super().setUp() self.users = [UserFactory.create() for _ in range(12)] self.user = self.users[0] self.freeze_grade_flag = waffle_flags()[ENFORCE_FREEZE_GRADE_AFTER_COURSE_END] def _assert_log(self, mock_log, method_name): assert mock_log.info.called - log_message = u"Attempted {} for course '%s', but grades are frozen.".format(method_name) + log_message = "Attempted {} for course '%s', but grades are frozen.".format(method_name) assert log_message in mock_log.info.call_args_list[0][0][0] def _assert_for_freeze_grade_flag( # lint-amnesty, pylint: disable=missing-function-docstring @@ -536,7 +534,7 @@ class FreezeGradingAfterCourseEndTest(HasCourseWithProblemsMixin, ModuleStoreTes ) as mock_compute_grades: result = compute_all_grades_for_course.apply_async( kwargs={ - 'course_key': six.text_type(self.course.id) + 'course_key': str(self.course.id) } ) self._assert_for_freeze_grade_flag( @@ -567,7 +565,7 @@ class FreezeGradingAfterCourseEndTest(HasCourseWithProblemsMixin, ModuleStoreTes with mock_get_score(1, 2): result = compute_grades_for_course.apply_async( kwargs={ - 'course_key': six.text_type(self.course.id), + 'course_key': str(self.course.id), 'batch_size': 2, 'offset': 4, } @@ -597,7 +595,7 @@ class FreezeGradingAfterCourseEndTest(HasCourseWithProblemsMixin, ModuleStoreTes factory = mock_factory.return_value kwargs = { 'user_id': self.user.id, - 'course_key': six.text_type(self.course.id), + 'course_key': str(self.course.id), } result = tasks.recalculate_course_and_subsection_grades_for_user.apply_async(kwargs=kwargs) diff --git a/lms/djangoapps/grades/tests/test_transformer.py b/lms/djangoapps/grades/tests/test_transformer.py index 83b47b978af..c710aaf4118 100644 --- a/lms/djangoapps/grades/tests/test_transformer.py +++ b/lms/djangoapps/grades/tests/test_transformer.py @@ -9,13 +9,11 @@ from copy import deepcopy import ddt import pytz -import six -from six.moves import range +from common.djangoapps.student.tests.factories import UserFactory from lms.djangoapps.course_blocks.api import get_course_blocks from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase from openedx.core.djangoapps.content.block_structure.api import clear_course_from_cache -from common.djangoapps.student.tests.factories import UserFactory from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.django import modulestore from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -35,15 +33,15 @@ class GradesTransformerTestCase(CourseStructureTestCase): ENABLED_SIGNALS = ['course_published'] problem_metadata = { - u'graded': True, - u'weight': 1, - u'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'graded': True, + 'weight': 1, + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), } def setUp(self): - super(GradesTransformerTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - password = u'test' - self.student = UserFactory.create(is_staff=False, username=u'test_student', password=password) + super().setUp() + password = 'test' + self.student = UserFactory.create(is_staff=False, username='test_student', password=password) self.client.login(username=self.student.username, password=password) def _update_course_grading_policy(self, course, grading_policy): @@ -78,8 +76,8 @@ class GradesTransformerTestCase(CourseStructureTestCase): # Append our custom message to the default assertEqual error message self.longMessage = True # pylint: disable=invalid-name assert expectations[field] == block_structure.get_xblock_field(usage_key, field),\ - u'in field {},'.format(repr(field)) - assert block_structure.get_xblock_field(usage_key, u'subtree_edited_on') is not None + 'in field {},'.format(repr(field)) + assert block_structure.get_xblock_field(usage_key, 'subtree_edited_on') is not None def assert_collected_transformer_block_fields(self, block_structure, usage_key, transformer_class, **expectations): """ @@ -94,7 +92,7 @@ class GradesTransformerTestCase(CourseStructureTestCase): for field in expectations: assert expectations[field] == block_structure.get_transformer_block_field( usage_key, transformer_class, field - ), u'in {} and field {}'.format(transformer_class, repr(field)) + ), 'in {} and field {}'.format(transformer_class, repr(field)) def build_course_with_problems(self, data='<problem></problem>', metadata=None): """ @@ -109,18 +107,18 @@ class GradesTransformerTestCase(CourseStructureTestCase): # `CourseStructureTestCase.build_course` for details. return self.build_course([ { - u'org': u'GradesTestOrg', - u'course': u'GB101', - u'run': u'cannonball', - u'metadata': {u'format': u'homework'}, - u'#type': u'course', - u'#ref': u'course', - u'#children': [ + 'org': 'GradesTestOrg', + 'course': 'GB101', + 'run': 'cannonball', + 'metadata': {'format': 'homework'}, + '#type': 'course', + '#ref': 'course', + '#children': [ { - u'metadata': metadata, - u'#type': u'problem', - u'#ref': u'problem', - u'data': data, + 'metadata': metadata, + '#type': 'problem', + '#ref': 'problem', + 'data': data, } ] } @@ -146,45 +144,45 @@ class GradesTransformerTestCase(CourseStructureTestCase): """ return self.build_course([ { - u'org': u'GradesTestOrg', - u'course': u'GB101', - u'run': u'cannonball', - u'metadata': {u'format': u'homework'}, - u'#type': u'course', - u'#ref': u'course', - u'#children': [ + 'org': 'GradesTestOrg', + 'course': 'GB101', + 'run': 'cannonball', + 'metadata': {'format': 'homework'}, + '#type': 'course', + '#ref': 'course', + '#children': [ { - u'#type': u'chapter', - u'#ref': u'chapter', - u'#children': [ + '#type': 'chapter', + '#ref': 'chapter', + '#children': [ { - u'#type': u'sequential', - u'#ref': 'sub_A', - u'#children': [ + '#type': 'sequential', + '#ref': 'sub_A', + '#children': [ { - u'#type': u'vertical', - u'#ref': 'vert_1', - u'#children': [ + '#type': 'vertical', + '#ref': 'vert_1', + '#children': [ { - u'#type': u'vertical', - u'#ref': u'vert_A11', - u'#children': [{u'#type': u'problem', u'#ref': u'prob_A1aa'}] + '#type': 'vertical', + '#ref': 'vert_A11', + '#children': [{'#type': 'problem', '#ref': 'prob_A1aa'}] }, ] }, - {u'#type': u'vertical', u'#ref': 'vert_2', '#parents': [u'vert_A11']}, + {'#type': 'vertical', '#ref': 'vert_2', '#parents': ['vert_A11']}, ] }, { - u'#type': u'sequential', - u'#ref': u'sub_B', - u'#children': [ - {u'#type': u'vertical', u'#ref': 'vert_3', '#parents': ['sub_A']}, + '#type': 'sequential', + '#ref': 'sub_B', + '#children': [ + {'#type': 'vertical', '#ref': 'vert_3', '#parents': ['sub_A']}, { - u'#type': u'sequential', - u'#ref': 'sub_C', + '#type': 'sequential', + '#ref': 'sub_C', '#parents': ['sub_A'], - u'#children': [{u'#type': u'problem', u'#ref': u'prob_BCb'}] + '#children': [{'#type': 'problem', '#ref': 'prob_BCb'}] }, ] }, @@ -209,8 +207,8 @@ class GradesTransformerTestCase(CourseStructureTestCase): 'prob_BCb': {'sub_A', 'sub_B', 'sub_C'}, } blocks = self.build_complicated_hypothetical_course() - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) - for block_ref, expected_subsections in six.iteritems(expected_subsections): + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) + for block_ref, expected_subsections in expected_subsections.items(): actual_subsections = block_structure.get_transformer_block_field( blocks[block_ref].location, self.TRANSFORMER_CLASS_TO_TEST, @@ -220,19 +218,19 @@ class GradesTransformerTestCase(CourseStructureTestCase): def test_unscored_block_collection(self): blocks = self.build_course_with_problems() - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) self.assert_collected_xblock_fields( block_structure, - blocks[u'course'].location, + blocks['course'].location, weight=None, graded=False, has_score=False, due=None, - format=u'homework', + format='homework', ) self.assert_collected_transformer_block_fields( block_structure, - blocks[u'course'].location, + blocks['course'].location, self.TRANSFORMER_CLASS_TO_TEST, max_score=None, explicit_graded=None, @@ -241,20 +239,20 @@ class GradesTransformerTestCase(CourseStructureTestCase): def test_grades_collected_basic(self): blocks = self.build_course_with_problems() - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) self.assert_collected_xblock_fields( block_structure, - blocks[u'problem'].location, - weight=self.problem_metadata[u'weight'], - graded=self.problem_metadata[u'graded'], + blocks['problem'].location, + weight=self.problem_metadata['weight'], + graded=self.problem_metadata['graded'], has_score=True, - due=self.problem_metadata[u'due'], + due=self.problem_metadata['due'], format=None, ) self.assert_collected_transformer_block_fields( block_structure, - blocks[u'problem'].location, + blocks['problem'].location, self.TRANSFORMER_CLASS_TO_TEST, max_score=0, explicit_graded=True, @@ -263,15 +261,15 @@ class GradesTransformerTestCase(CourseStructureTestCase): @ddt.data(True, False, None) def test_graded_at_problem(self, graded): problem_metadata = { - u'has_score': True, + 'has_score': True, } if graded is not None: - problem_metadata[u'graded'] = graded + problem_metadata['graded'] = graded blocks = self.build_course_with_problems(metadata=problem_metadata) - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) self.assert_collected_transformer_block_fields( block_structure, - blocks[u'problem'].location, + blocks['problem'].location, self.TRANSFORMER_CLASS_TO_TEST, explicit_graded=graded, ) @@ -280,27 +278,27 @@ class GradesTransformerTestCase(CourseStructureTestCase): # Demonstrate that the problem data can by collected by the SystemUser # even if the block has access restrictions placed on it. problem_metadata = { - u'graded': True, - u'weight': 1, - u'due': datetime.datetime(2016, 10, 16, 0, 4, 0, tzinfo=pytz.utc), - u'visible_to_staff_only': True, + 'graded': True, + 'weight': 1, + 'due': datetime.datetime(2016, 10, 16, 0, 4, 0, tzinfo=pytz.utc), + 'visible_to_staff_only': True, } blocks = self.build_course_with_problems(metadata=problem_metadata) - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) self.assert_collected_xblock_fields( block_structure, - blocks[u'problem'].location, - weight=problem_metadata[u'weight'], - graded=problem_metadata[u'graded'], + blocks['problem'].location, + weight=problem_metadata['weight'], + graded=problem_metadata['graded'], has_score=True, - due=problem_metadata[u'due'], + due=problem_metadata['due'], format=None, ) def test_max_score_collection(self): - problem_data = u''' + problem_data = ''' <problem> <numericalresponse answer="2"> <textline label="1+1" trailing_text="%" /> @@ -309,17 +307,17 @@ class GradesTransformerTestCase(CourseStructureTestCase): ''' blocks = self.build_course_with_problems(data=problem_data) - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) self.assert_collected_transformer_block_fields( block_structure, - blocks[u'problem'].location, + blocks['problem'].location, self.TRANSFORMER_CLASS_TO_TEST, max_score=1, ) def test_max_score_for_multiresponse_problem(self): - problem_data = u''' + problem_data = ''' <problem> <numericalresponse answer="27"> <textline label="3^3" /> @@ -331,11 +329,11 @@ class GradesTransformerTestCase(CourseStructureTestCase): ''' blocks = self.build_course_with_problems(problem_data) - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) self.assert_collected_transformer_block_fields( block_structure, - blocks[u'problem'].location, + blocks['problem'].location, self.TRANSFORMER_CLASS_TO_TEST, max_score=2, ) @@ -344,7 +342,7 @@ class GradesTransformerTestCase(CourseStructureTestCase): """ Verify that for an invalid dropdown problem, the max score is set to zero. """ - problem_data = u''' + problem_data = ''' <problem> <optionresponse> <p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown @@ -372,25 +370,25 @@ class GradesTransformerTestCase(CourseStructureTestCase): def test_course_version_not_collected_in_old_mongo(self): blocks = self.build_course_with_problems() - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) - assert block_structure.get_xblock_field(blocks[u'course'].location, u'course_version') is None + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) + assert block_structure.get_xblock_field(blocks['course'].location, 'course_version') is None def test_course_version_collected_in_split(self): with self.store.default_store(ModuleStoreEnum.Type.split): blocks = self.build_course_with_problems() - block_structure = get_course_blocks(self.student, blocks[u'course'].location, self.transformers) - assert block_structure.get_xblock_field(blocks[u'course'].location, u'course_version') is not None + block_structure = get_course_blocks(self.student, blocks['course'].location, self.transformers) + assert block_structure.get_xblock_field(blocks['course'].location, 'course_version') is not None assert block_structure.get_xblock_field( - blocks[u'problem'].location, u'course_version' - ) == block_structure.get_xblock_field(blocks[u'course'].location, u'course_version') + blocks['problem'].location, 'course_version' + ) == block_structure.get_xblock_field(blocks['course'].location, 'course_version') def test_grading_policy_collected(self): # the calculated hash of the original and updated grading policies of the test course - original_grading_policy_hash = u'ChVp0lHGQGCevD0t4njna/C44zQ=' - updated_grading_policy_hash = u'TsbX04qWOy1WRnC0NHy+94upPd4=' + original_grading_policy_hash = 'ChVp0lHGQGCevD0t4njna/C44zQ=' + updated_grading_policy_hash = 'TsbX04qWOy1WRnC0NHy+94upPd4=' blocks = self.build_course_with_problems() - course_block = blocks[u'course'] + course_block = blocks['course'] self._validate_grading_policy_hash( course_block.location, original_grading_policy_hash @@ -426,9 +424,9 @@ class MultiProblemModulestoreAccessTestCase(CourseStructureTestCase, SharedModul TRANSFORMER_CLASS_TO_TEST = GradesTransformer def setUp(self): - super(MultiProblemModulestoreAccessTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - password = u'test' - self.student = UserFactory.create(is_staff=False, username=u'test_student', password=password) + super().setUp() + password = 'test' + self.student = UserFactory.create(is_staff=False, username='test_student', password=password) self.client.login(username=self.student.username, password=password) @ddt.data( @@ -443,26 +441,26 @@ class MultiProblemModulestoreAccessTestCase(CourseStructureTestCase, SharedModul """ course = [ { - u'org': u'GradesTestOrg', - u'course': u'GB101', - u'run': u'cannonball', - u'metadata': {u'format': u'homework'}, - u'#type': u'course', - u'#ref': u'course', - u'#children': [], + 'org': 'GradesTestOrg', + 'course': 'GB101', + 'run': 'cannonball', + 'metadata': {'format': 'homework'}, + '#type': 'course', + '#ref': 'course', + '#children': [], }, ] for problem_number in range(random.randrange(10, 20)): - course[0][u'#children'].append( + course[0]['#children'].append( { - u'metadata': { - u'graded': True, - u'weight': 1, - u'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), + 'metadata': { + 'graded': True, + 'weight': 1, + 'due': datetime.datetime(2099, 3, 15, 12, 30, 0, tzinfo=pytz.utc), }, - u'#type': u'problem', - u'#ref': u'problem_{}'.format(problem_number), - u'data': u''' + '#type': 'problem', + '#ref': f'problem_{problem_number}', + 'data': ''' <problem> <numericalresponse answer="{number}"> <textline label="1*{number}" /> @@ -472,6 +470,6 @@ class MultiProblemModulestoreAccessTestCase(CourseStructureTestCase, SharedModul ) with self.store.default_store(store_type): blocks = self.build_course(course) - clear_course_from_cache(blocks[u'course'].id) + clear_course_from_cache(blocks['course'].id) with check_mongo_calls(expected_mongo_queries): - get_course_blocks(self.student, blocks[u'course'].location, self.transformers) + get_course_blocks(self.student, blocks['course'].location, self.transformers) diff --git a/lms/djangoapps/grades/tests/utils.py b/lms/djangoapps/grades/tests/utils.py index d0eb7596d2f..6e2cbe86e6d 100644 --- a/lms/djangoapps/grades/tests/utils.py +++ b/lms/djangoapps/grades/tests/utils.py @@ -5,9 +5,9 @@ Utilities for grades related tests from contextlib import contextmanager from datetime import datetime +from unittest.mock import MagicMock, patch import pytz -from mock import MagicMock, patch from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.module_render import get_module diff --git a/lms/djangoapps/grades/transformer.py b/lms/djangoapps/grades/transformer.py index e1be1cecb68..bfa90ca8040 100644 --- a/lms/djangoapps/grades/transformer.py +++ b/lms/djangoapps/grades/transformer.py @@ -40,14 +40,14 @@ class GradesTransformer(BlockStructureTransformer): WRITE_VERSION = 4 READ_VERSION = 4 FIELDS_TO_COLLECT = [ - u'due', - u'format', - u'graded', - u'has_score', - u'weight', - u'course_version', - u'subtree_edited_on', - u'show_correctness', + 'due', + 'format', + 'graded', + 'has_score', + 'weight', + 'course_version', + 'subtree_edited_on', + 'show_correctness', ] EXPLICIT_GRADED_FIELD_NAME = 'explicit_graded' @@ -58,7 +58,7 @@ class GradesTransformer(BlockStructureTransformer): Unique identifier for the transformer's class; same identifier used in setup.py. """ - return u'grades' + return 'grades' @classmethod def collect(cls, block_structure): @@ -152,7 +152,7 @@ class GradesTransformer(BlockStructureTransformer): max_score = module.max_score() block_structure.set_transformer_block_field(module.location, cls, 'max_score', max_score) if max_score is None: - log.warning(u"GradesTransformer: max_score is None for {}".format(module.location)) + log.warning(f"GradesTransformer: max_score is None for {module.location}") @classmethod def _collect_grading_policy_hash(cls, block_structure): diff --git a/lms/djangoapps/grades/util_services.py b/lms/djangoapps/grades/util_services.py index a061f471b6c..94677e6f7b0 100644 --- a/lms/djangoapps/grades/util_services.py +++ b/lms/djangoapps/grades/util_services.py @@ -4,12 +4,12 @@ from . import grade_utils -class GradesUtilService(object): +class GradesUtilService: """ An interface to be used by xblocks. """ def __init__(self, **kwargs): - super(GradesUtilService, self).__init__() # lint-amnesty, pylint: disable=super-with-arguments + super().__init__() self.course_id = kwargs.get('course_id', None) def are_grades_frozen(self): -- GitLab