pyupgrade on lms gating and grades apps (#26532)

This commit is contained in:
M. Zulqarnain
2021-02-22 12:58:41 +05:00
committed by GitHub
parent 36748ff78f
commit e505d99237
73 changed files with 726 additions and 816 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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,

View File

@@ -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'},
}
}
}

View File

@@ -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__'

View File

@@ -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."
)

View File

@@ -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")

View File

@@ -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.

View File

@@ -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'

View File

@@ -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):

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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),
}
)

View File

@@ -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 {

View File

@@ -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,
}

View File

@@ -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):

View File

@@ -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)

View File

@@ -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,
}

View File

@@ -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')},
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models

View File

@@ -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

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models
from opaque_keys.edx.django.models import CourseKeyField

View File

@@ -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')},
),
]

View File

@@ -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')},
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -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')},
),
]

View File

@@ -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')},
),
]

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -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')),
],
),

View File

@@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from django.db import migrations, models

View File

@@ -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)),

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-06-05 13:59

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.21 on 2019-07-03 14:46

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.27 on 2020-01-07 16:52

View File

@@ -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 (

View File

@@ -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)

View File

@@ -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)
)
)

View File

@@ -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,

View File

@@ -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 lms.djangoapps.courseware.tests.factories import GlobalStaffFactory
from lms.djangoapps.program_enrollments.tests.factories import ProgramEnrollmentFactory, ProgramCourseEnrollmentFactory
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
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 ProgramCourseEnrollmentFactory, ProgramEnrollmentFactory
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
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

View File

@@ -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

View File

@@ -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 = [
{

View File

@@ -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):

View File

@@ -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'
),

View File

@@ -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

View File

@@ -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:

View File

@@ -6,7 +6,7 @@ Grade service
from . import api
class GradesService(object):
class GradesService:
"""
Course grade service

View File

@@ -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)
)
)

View File

@@ -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()
]

View File

@@ -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),

View File

@@ -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)

View File

@@ -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")

View File

@@ -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):

View File

@@ -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),
},
),
],

View File

@@ -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)

View File

@@ -1,15 +1,13 @@
""" Tests calling the grades api directly """
import ddt
from mock import patch
from unittest.mock import patch
import ddt
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)

View File

@@ -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()

View File

@@ -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):

View File

@@ -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.'
}

View File

@@ -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),
}
)

View File

@@ -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):

View File

@@ -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 lms.djangoapps.grades.constants import GradeOverrideFeatureEnum
from lms.djangoapps.grades.models import (
PersistentSubsectionGrade,
PersistentSubsectionGradeOverride
)
from lms.djangoapps.grades.services import GradesService
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.services import GradesService
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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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):