Inter-app API cleanup for Grades

This commit is contained in:
Nimisha Asthagiri
2019-04-28 22:09:54 -04:00
parent a943a2cb63
commit eb0791ec89
58 changed files with 259 additions and 158 deletions

View File

@@ -10,7 +10,7 @@ from pytz import UTC
from contentstore.courseware_index import CoursewareSearchIndexer, LibrarySearchIndexer
from contentstore.proctoring import register_special_exams
from lms.djangoapps.grades.tasks import compute_all_grades_for_course
from lms.djangoapps.grades.api import task_compute_all_grades_for_course
from openedx.core.djangoapps.credit.signals import on_course_publish
from openedx.core.lib.gating import api as gating_api
from track.event_transaction_utils import get_event_transaction_id, get_event_transaction_type
@@ -118,9 +118,9 @@ def handle_grading_policy_changed(sender, **kwargs):
'event_transaction_id': unicode(get_event_transaction_id()),
'event_transaction_type': unicode(get_event_transaction_type()),
}
result = compute_all_grades_for_course.apply_async(kwargs=kwargs, countdown=GRADING_POLICY_COUNTDOWN_SECONDS)
result = task_compute_all_grades_for_course.apply_async(kwargs=kwargs, countdown=GRADING_POLICY_COUNTDOWN_SECONDS)
log.info(u"Grades: Created {task_name}[{task_id}] with arguments {kwargs}".format(
task_name=compute_all_grades_for_course.name,
task_name=task_compute_all_grades_for_course.name,
task_id=result.task_id,
kwargs=kwargs,
))

View File

@@ -25,7 +25,7 @@ class LockedTest(ModuleStoreTestCase):
@patch('cms.djangoapps.contentstore.signals.handlers.cache.add')
@patch('cms.djangoapps.contentstore.signals.handlers.cache.delete')
@patch('cms.djangoapps.contentstore.signals.handlers.compute_all_grades_for_course.apply_async')
@patch('cms.djangoapps.contentstore.signals.handlers.task_compute_all_grades_for_course.apply_async')
@ddt.data(True, False)
def test_locked(self, lock_available, compute_grades_async_mock, delete_mock, add_mock):
add_mock.return_value = lock_available

View File

@@ -28,7 +28,7 @@ from lms.djangoapps.certificates.models import (
CertificateStatuses,
certificate_status_for_student
)
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.verify_student.models import VerificationDeadline
from lms.djangoapps.verify_student.services import IDVerificationService
from lms.djangoapps.verify_student.utils import is_verification_expiring_soon, verification_for_datetime

View File

@@ -32,7 +32,7 @@ from lms.djangoapps.ccx.tests.utils import CcxTestCase, flatten
from lms.djangoapps.ccx.utils import ccx_course, is_email
from lms.djangoapps.ccx.views import get_date
from lms.djangoapps.discussion.django_comment_client.utils import has_forum_access
from lms.djangoapps.grades.tasks import compute_all_grades_for_course
from lms.djangoapps.grades.api import task_compute_all_grades_for_course
from lms.djangoapps.instructor.access import allow_access, list_with_level
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
@@ -109,7 +109,7 @@ def setup_students_and_grades(context):
module_state_key=problem.location
)
compute_all_grades_for_course.apply_async(kwargs={'course_key': unicode(context.course.id)})
task_compute_all_grades_for_course.apply_async(kwargs={'course_key': unicode(context.course.id)})
def unhide(unit):

View File

@@ -46,7 +46,7 @@ from lms.djangoapps.ccx.utils import (
parse_date,
)
from lms.djangoapps.courseware.field_overrides import disable_overrides
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.instructor.enrollment import enroll_email, get_email_params
from lms.djangoapps.instructor.views.gradebook_api import get_grade_book_page
from openedx.core.djangoapps.django_comment_common.models import FORUM_ROLE_ADMINISTRATOR, assign_role

View File

@@ -127,6 +127,25 @@ def get_certificate_for_user(username, course_key):
return format_certificate_for_user(username, cert)
def get_recently_modified_certificates(course_keys=None, start_date=None, end_date=None):
"""
Returns a QuerySet of GeneratedCertificate objects filtered by the input
parameters and ordered by modified_date.
"""
cert_filter_args = {}
if course_keys:
cert_filter_args['course_id__in'] = course_keys
if start_date:
cert_filter_args['modified_date__gte'] = start_date
if end_date:
cert_filter_args['modified_date__lte'] = end_date
return GeneratedCertificate.objects.filter(**cert_filter_args).order_by('modified_date') # pylint: disable=no-member
def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch',
forced_grade=None):
"""

View File

@@ -6,8 +6,8 @@ import logging
from django.core.management.base import BaseCommand
from lms.djangoapps.certificates.models import GeneratedCertificate
from courseware import courses
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.courseware import courses
from lms.djangoapps.grades.api import CourseGradeFactory
log = logging.getLogger(__name__)

View File

@@ -12,6 +12,7 @@ from lxml.etree import ParserError, XMLSyntaxError
from requests.auth import HTTPBasicAuth
from capa.xqueue_interface import XQueueInterface, make_hashkey, make_xheader
from course_modes.models import CourseMode
from lms.djangoapps.certificates.models import CertificateStatuses as status
from lms.djangoapps.certificates.models import (
CertificateWhitelist,
@@ -19,8 +20,7 @@ from lms.djangoapps.certificates.models import (
GeneratedCertificate,
certificate_status_for_student
)
from course_modes.models import CourseMode
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.verify_student.services import IDVerificationService
from student.models import CourseEnrollment, UserProfile
from xmodule.modulestore.django import modulestore

View File

@@ -13,7 +13,7 @@ from lms.djangoapps.certificates.models import (
CertificateStatuses
)
from lms.djangoapps.certificates.tasks import generate_certificate
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.certificates.api import auto_certificate_generation_enabled
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
@@ -102,6 +102,7 @@ def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylin
return
user_enrollments = CourseEnrollment.enrollments_for_user(user=user)
grade_factory = CourseGradeFactory()
expected_verification_status = IDVerificationService.user_status(user)
expected_verification_status = expected_verification_status['status']

View File

@@ -53,11 +53,10 @@ from courseware.model_data import DjangoKeyValueStore, FieldDataCache
from edxmako.shortcuts import render_to_string
from eventtracking import tracker
from lms.djangoapps.courseware.field_overrides import OverrideFieldData
from lms.djangoapps.grades.signals.signals import SCORE_PUBLISHED
from lms.djangoapps.grades.api import signals as grades_signals, GradesUtilService
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
from lms.djangoapps.lms_xblock.runtime import LmsModuleSystem
from lms.djangoapps.grades.util_services import GradesUtilService
from lms.djangoapps.verify_student.services import XBlockVerificationService
from openedx.core.djangoapps.bookmarks.services import BookmarksService
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
@@ -572,7 +571,7 @@ def get_module_system_for_user(
"""
Submit a grade for the block.
"""
SCORE_PUBLISHED.send(
grades_signals.SCORE_PUBLISHED.send(
sender=None,
block=block,
user=user,

View File

@@ -28,8 +28,7 @@ from capa.tests.response_xml_factory import (
from course_modes.models import CourseMode
from courseware.models import BaseStudentModuleHistory, StudentModule
from courseware.tests.helpers import LoginEnrollmentTestCase
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.tasks import compute_all_grades_for_course
from lms.djangoapps.grades.api import CourseGradeFactory, task_compute_all_grades_for_course
from openedx.core.djangoapps.credit.api import get_credit_requirement_status, set_credit_requirements
from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
@@ -406,7 +405,7 @@ class TestCourseGrader(TestSubmittingProblems):
]
}
self.add_grading_policy(grading_policy)
compute_all_grades_for_course.apply_async(kwargs={'course_key': unicode(self.course.id)})
task_compute_all_grades_for_course.apply_async(kwargs={'course_key': unicode(self.course.id)})
def dropping_setup(self):
"""

View File

@@ -29,7 +29,7 @@ from lms.djangoapps.courseware.courses import allow_public_access
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
from lms.djangoapps.gating.api import get_entrance_exam_score_ratio, get_entrance_exam_usage_key
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from openedx.core.djangoapps.crawlers.models import CrawlersConfig
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference

View File

@@ -68,7 +68,7 @@ from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.courses import allow_public_access
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.instructor.enrollment import uses_shib
from lms.djangoapps.instructor.views.api import require_global_staff
from lms.djangoapps.verify_student.services import IDVerificationService

View File

@@ -7,11 +7,11 @@ from django.dispatch import receiver
from completion.models import BlockCompletion
from gating import api as gating_api
from gating.tasks import task_evaluate_subsection_completion_milestones
from lms.djangoapps.grades.signals.signals import SUBSECTION_SCORE_CHANGED
from lms.djangoapps.grades.api import signals as grades_signals
from openedx.core.djangoapps.signals.signals import COURSE_GRADE_CHANGED
@receiver(SUBSECTION_SCORE_CHANGED)
@receiver(grades_signals.SUBSECTION_SCORE_CHANGED)
def evaluate_subsection_gated_milestones(**kwargs):
"""
Receives the SUBSECTION_SCORE_CHANGED signal and triggers the

View File

@@ -9,7 +9,7 @@ from milestones import api as milestones_api
from milestones.tests.utils import MilestonesTestCaseMixin
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
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

View File

@@ -0,0 +1,24 @@
# pylint: disable=unused-import,wildcard-import
"""
Python APIs exposed by the grades app to other in-process apps.
"""
# Public Grades Factories
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.subsection_grade_factory import SubsectionGradeFactory
# Public Grades Functions
from lms.djangoapps.grades.models_api import *
from lms.djangoapps.grades.tasks import compute_all_grades_for_course as task_compute_all_grades_for_course
# Public Grades Modules
from lms.djangoapps.grades import events, constants, context
from lms.djangoapps.grades.signals import signals
from lms.djangoapps.grades.util_services import GradesUtilService
# TODO exposing functionality from Grades handlers seems fishy.
from lms.djangoapps.grades.signals.handlers import disconnect_submissions_signal_receiver
# 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

View File

@@ -21,7 +21,7 @@ class GradesConfig(AppConfig):
ProjectType.LMS: {
PluginURLs.NAMESPACE: u'grades_api',
PluginURLs.REGEX: u'^api/grades/',
PluginURLs.RELATIVE_PATH: u'api.urls',
PluginURLs.RELATIVE_PATH: u'rest_api.urls',
}
},
PluginSettings.CONFIG: {

View File

@@ -48,3 +48,10 @@ def waffle_flags():
flag_undefined_default=True,
),
}
def is_writable_gradebook_enabled(course_key):
"""
Returns whether the writable gradebook app is enabled for the given course.
"""
return waffle_flags()[WRITABLE_GRADEBOOK].is_enabled(course_key)

View File

@@ -10,3 +10,8 @@ class ScoreDatabaseTableEnum(object):
courseware_student_module = 'csm'
submissions = 'submissions'
overrides = 'overrides'
class GradeOverrideFeatureEnum(object):
proctoring = 'PROCTORING'
gradebook = 'GRADEBOOK'

View File

@@ -17,16 +17,6 @@ def grading_context_for_course(course):
return grading_context(course, course_structure)
def visible_to_staff_only(subsection):
"""
Returns True if the given subsection is visible to staff only else False
"""
try:
return subsection.transformer_data['visibility'].fields['merged_visible_to_staff_only']
except KeyError:
return False
def graded_subsections_for_course(course_structure):
"""
Given a course block structure, yields the subsections of the course that are graded
@@ -37,7 +27,7 @@ def graded_subsections_for_course(course_structure):
for chapter_key in course_structure.get_children(course_structure.root_block_usage_key):
for subsection_key in course_structure.get_children(chapter_key):
subsection = course_structure[subsection_key]
if not visible_to_staff_only(subsection) and subsection.graded:
if not _visible_to_staff_only(subsection) and subsection.graded:
yield subsection
@@ -93,3 +83,13 @@ def grading_context(course, course_structure):
'count_all_graded_blocks': count_all_graded_blocks,
'subsection_type_graders': CourseGrade.get_subsection_type_graders(course)
}
def _visible_to_staff_only(subsection):
"""
Returns True if the given subsection is visible to staff only else False
"""
try:
return subsection.transformer_data['visibility'].fields['merged_visible_to_staff_only']
except KeyError:
return False

View File

@@ -13,7 +13,9 @@ from openedx.core.djangoapps.signals.signals import (COURSE_GRADE_CHANGED,
from .config import assume_zero_if_absent, should_persist_grades
from .course_data import CourseData
from .course_grade import CourseGrade, ZeroCourseGrade
from .models import PersistentCourseGrade, prefetch
from .models import PersistentCourseGrade
from .models_api import prefetch_grade_overrides_and_visible_blocks
log = getLogger(__name__)
@@ -170,7 +172,7 @@ class CourseGradeFactory(object):
"""
should_persist = should_persist_grades(course_data.course_key)
if should_persist and force_update_subsections:
prefetch(user, course_data.course_key)
prefetch_grade_overrides_and_visible_blocks(user, course_data.course_key)
course_grade = CourseGrade(
user,

View File

@@ -23,7 +23,7 @@ from opaque_keys.edx.django.models import CourseKeyField, UsageKeyField
from opaque_keys.edx.keys import CourseKey, UsageKey
from coursewarehistoryextended.fields import UnsignedBigIntAutoField, UnsignedBigIntOneToOneField
from lms.djangoapps.grades import events
from lms.djangoapps.grades import events, constants
from openedx.core.lib.cache_utils import get_cache
@@ -743,11 +743,9 @@ class PersistentSubsectionGradeOverrideHistory(models.Model):
.. no_pii:
"""
PROCTORING = 'PROCTORING'
GRADEBOOK = 'GRADEBOOK'
OVERRIDE_FEATURES = (
(PROCTORING, 'proctoring'),
(GRADEBOOK, 'gradebook'),
(constants.GradeOverrideFeatureEnum.proctoring, 'proctoring'),
(constants.GradeOverrideFeatureEnum.gradebook, 'gradebook'),
)
CREATE_OR_UPDATE = 'CREATEORUPDATE'
@@ -764,7 +762,7 @@ class PersistentSubsectionGradeOverrideHistory(models.Model):
feature = models.CharField(
max_length=32,
choices=OVERRIDE_FEATURES,
default=PROCTORING
default=constants.GradeOverrideFeatureEnum.proctoring,
)
action = models.CharField(
max_length=32,
@@ -793,8 +791,3 @@ class PersistentSubsectionGradeOverrideHistory(models.Model):
@classmethod
def get_override_history(cls, override_id):
return cls.objects.filter(override_id=override_id)
def prefetch(user, course_key):
PersistentSubsectionGradeOverride.prefetch(user.id, course_key)
VisibleBlocks.bulk_read(user.id, course_key)

View File

@@ -0,0 +1,48 @@
"""
Provides Python APIs exposed from Grades models.
"""
from lms.djangoapps.grades.models import (
PersistentCourseGrade as _PersistentCourseGrade,
PersistentSubsectionGrade as _PersistentSubsectionGrade,
PersistentSubsectionGradeOverride as _PersistentSubsectionGradeOverride,
VisibleBlocks as _VisibleBlocks,
)
def prefetch_grade_overrides_and_visible_blocks(user, course_key):
_PersistentSubsectionGradeOverride.prefetch(user.id, course_key)
_VisibleBlocks.bulk_read(user.id, course_key)
def prefetch_course_grades(course_key, users):
_PersistentCourseGrade.prefetch(course_key, users)
def prefetch_course_and_subsection_grades(course_key, users):
_PersistentCourseGrade.prefetch(course_key, users)
_PersistentSubsectionGrade.prefetch(course_key, users)
def clear_prefetched_course_grades(course_key):
_PersistentCourseGrade.clear_prefetched_data(course_key)
_PersistentSubsectionGrade.clear_prefetched_data(course_key)
def clear_prefetched_course_and_subsection_grades(course_key):
_PersistentCourseGrade.clear_prefetched_data(course_key)
def get_recently_modified_grades(course_keys, start_date, end_date):
"""
Returns a QuerySet of PersistentCourseGrade objects filtered by the input
parameters and ordered by modified date.
"""
grade_filter_args = {}
if course_keys:
grade_filter_args['course_id__in'] = course_keys
if start_date:
grade_filter_args['modified__gte'] = start_date
if end_date:
grade_filter_args['modified__lte'] = end_date
return _PersistentCourseGrade.objects.filter(**grade_filter_args).order_by('modified')

View File

@@ -8,5 +8,5 @@ from django.conf.urls import include, url
app_name = 'lms.djangoapps.grades'
urlpatterns = [
url(r'^v1/', include('grades.api.v1.urls', namespace='v1'))
url(r'^v1/', include('grades.rest_api.v1.urls', namespace='v1'))
]

View File

@@ -15,24 +15,32 @@ from six import text_type
from util.date_utils import to_timestamp
from courseware.courses import get_course_by_id
from lms.djangoapps.grades.api.serializers import StudentGradebookEntrySerializer, SubsectionGradeResponseSerializer
from lms.djangoapps.grades.api.v1.utils import (
USER_MODEL,
CourseEnrollmentPagination,
GradeViewMixin,
from lms.djangoapps.grades.api import (
CourseGradeFactory,
is_writable_gradebook_enabled,
prefetch_course_and_subsection_grades,
clear_prefetched_course_and_subsection_grades,
constants as grades_constants,
context as grades_context,
events as grades_events,
)
from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK, waffle_flags
from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum
from lms.djangoapps.grades.context import graded_subsections_for_course
from lms.djangoapps.grades.course_data import CourseData
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.events import SUBSECTION_GRADE_CALCULATED, subsection_grade_calculated
# 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 (
PersistentCourseGrade,
PersistentSubsectionGrade,
PersistentSubsectionGradeOverride,
PersistentSubsectionGradeOverrideHistory,
)
from lms.djangoapps.grades.rest_api.serializers import (
StudentGradebookEntrySerializer,
SubsectionGradeResponseSerializer,
)
from lms.djangoapps.grades.rest_api.v1.utils import (
USER_MODEL,
CourseEnrollmentPagination,
GradeViewMixin,
)
from lms.djangoapps.grades.subsection_grade import CreateSubsectionGrade
from lms.djangoapps.grades.tasks import recalculate_subsection_grade_v3
from lms.djangoapps.grades.grade_utils import are_grades_frozen
@@ -70,16 +78,14 @@ def bulk_gradebook_view_context(course_key, users):
list of users, also, fetch all the score relavant data,
storing the result in a RequestCache and deleting grades on context exit.
"""
PersistentSubsectionGrade.prefetch(course_key, users)
PersistentCourseGrade.prefetch(course_key, users)
prefetch_course_and_subsection_grades(course_key, users)
CourseEnrollment.bulk_fetch_enrollment_states(users, course_key)
cohorts.bulk_cache_cohorts(course_key, users)
BulkRoleCache.prefetch(users)
try:
yield
finally:
PersistentSubsectionGrade.clear_prefetched_data(course_key)
PersistentCourseGrade.clear_prefetched_data(course_key)
clear_prefetched_course_and_subsection_grades(course_key)
def verify_writable_gradebook_enabled(view_func):
@@ -95,7 +101,7 @@ def verify_writable_gradebook_enabled(view_func):
Wraps the given view function.
"""
course_key = get_course_key(request, kwargs.get('course_id'))
if not waffle_flags()[WRITABLE_GRADEBOOK].is_enabled(course_key):
if not is_writable_gradebook_enabled(course_key):
raise self.api_error(
status_code=status.HTTP_403_FORBIDDEN,
developer_message='The writable gradebook feature is not enabled for this course.',
@@ -504,7 +510,7 @@ class GradebookView(GradeViewMixin, PaginatedAPIView):
# over users to determine their subsection grades. We purposely avoid fetching
# the user-specific course structure for each user, because that is very expensive.
course_data = CourseData(user=None, course=course)
graded_subsections = list(graded_subsections_for_course(course_data.collected_structure))
graded_subsections = list(grades_context.graded_subsections_for_course(course_data.collected_structure))
if request.GET.get('username'):
with self._get_user_or_raise(request, course_key) as grade_user:
@@ -718,11 +724,11 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView):
override = PersistentSubsectionGradeOverride.update_or_create_override(
requesting_user=request_user,
subsection_grade_model=subsection_grade_model,
feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK,
feature=grades_constants.GradeOverrideFeatureEnum.gradebook,
**override_data
)
set_event_transaction_type(SUBSECTION_GRADE_CALCULATED)
set_event_transaction_type(grades_events.SUBSECTION_GRADE_CALCULATED)
create_new_event_transaction_id()
recalculate_subsection_grade_v3.apply(
@@ -736,12 +742,12 @@ class GradebookBulkUpdateView(GradeViewMixin, PaginatedAPIView):
score_deleted=False,
event_transaction_id=unicode(get_event_transaction_id()),
event_transaction_type=unicode(get_event_transaction_type()),
score_db_table=ScoreDatabaseTableEnum.overrides,
score_db_table=grades_constants.ScoreDatabaseTableEnum.overrides,
force_update_subsections=True,
)
)
# Emit events to let our tracking system to know we updated subsection grade
subsection_grade_calculated(subsection_grade_model)
grades_events.subsection_grade_calculated(subsection_grade_model)
return override
@staticmethod

View File

@@ -19,9 +19,8 @@ from six import text_type
from course_modes.models import CourseMode
from lms.djangoapps.courseware.tests.factories import InstructorFactory, StaffFactory
from lms.djangoapps.grades.api.v1.tests.mixins import GradeViewTestMixin
from lms.djangoapps.grades.api.v1.views import CourseEnrollmentPagination
from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK, waffle_flags
from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum
from lms.djangoapps.grades.course_data import CourseData
from lms.djangoapps.grades.course_grade import CourseGrade
from lms.djangoapps.grades.models import (
@@ -31,6 +30,8 @@ from lms.djangoapps.grades.models import (
PersistentSubsectionGradeOverride,
PersistentSubsectionGradeOverrideHistory,
)
from lms.djangoapps.grades.rest_api.v1.tests.mixins import GradeViewTestMixin
from lms.djangoapps.grades.rest_api.v1.views import CourseEnrollmentPagination
from lms.djangoapps.certificates.models import (
GeneratedCertificate,
CertificateStatuses,
@@ -221,7 +222,7 @@ class CourseGradingViewTest(SharedModuleStoreTestCase, APITestCase):
self.assertEqual(expected_data, resp.data)
def test_course_grade_frozen(self):
with patch('lms.djangoapps.grades.api.v1.gradebook_views.are_grades_frozen') as mock_frozen_grades:
with patch('lms.djangoapps.grades.rest_api.v1.gradebook_views.are_grades_frozen') as mock_frozen_grades:
mock_frozen_grades.return_value = True
self.client.login(username=self.staff.username, password=self.password)
resp = self.client.get(self.get_url(self.course_key))
@@ -907,7 +908,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase):
"""
Should receive a 403 when grades have been frozen for a course.
"""
with patch('lms.djangoapps.grades.api.v1.gradebook_views.are_grades_frozen', return_value=True):
with patch('lms.djangoapps.grades.rest_api.v1.gradebook_views.are_grades_frozen', return_value=True):
with override_waffle_flag(self.waffle_flag, active=True):
getattr(self, login_method)()
post_data = [
@@ -1171,7 +1172,7 @@ class GradebookBulkUpdateViewTest(GradebookViewTestBase):
for audit_item in update_records:
self.assertEqual(audit_item.user, request_user)
self.assertIsNotNone(audit_item.created)
self.assertEqual(audit_item.feature, PersistentSubsectionGradeOverrideHistory.GRADEBOOK)
self.assertEqual(audit_item.feature, GradeOverrideFeatureEnum.gradebook)
self.assertEqual(audit_item.action, PersistentSubsectionGradeOverrideHistory.CREATE_OR_UPDATE)
def test_update_failing_grade(self):
@@ -1348,7 +1349,7 @@ class SubsectionGradeViewTest(GradebookViewTestBase):
subsection_grade_model=self.grade,
earned_all_override=0.0,
earned_graded_override=0.0,
feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK,
feature=GradeOverrideFeatureEnum.gradebook,
)
resp = self.client.get(
@@ -1419,7 +1420,7 @@ class SubsectionGradeViewTest(GradebookViewTestBase):
subsection_grade_model=self.grade,
earned_all_override=0.0,
earned_graded_override=0.0,
feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK,
feature=GradeOverrideFeatureEnum.gradebook,
)
resp = self.client.get(

View File

@@ -11,8 +11,8 @@ from opaque_keys import InvalidKeyError
from rest_framework import status
from rest_framework.test import APITestCase
from lms.djangoapps.grades.api.v1.tests.mixins import GradeViewTestMixin
from lms.djangoapps.grades.api.v1.views import CourseGradesView
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 student.tests.factories import UserFactory

View File

@@ -2,7 +2,7 @@
from django.conf import settings
from django.conf.urls import url
from lms.djangoapps.grades.api.v1 import gradebook_views, views
from lms.djangoapps.grades.rest_api.v1 import gradebook_views, views
app_name = 'lms.djangoapps.grades'

View File

@@ -8,8 +8,6 @@ from rest_framework import status
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.pagination import CursorPagination
from rest_framework.response import Response
from rest_framework.views import APIView
from six import text_type
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin

View File

@@ -10,13 +10,12 @@ from edx_rest_framework_extensions import permissions
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.grades.api.serializers import GradingPolicySerializer
from lms.djangoapps.grades.api.v1.utils import (
from lms.djangoapps.grades.rest_api.serializers import GradingPolicySerializer
from lms.djangoapps.grades.rest_api.v1.utils import (
CourseEnrollmentPagination,
GradeViewMixin,
)
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.models import PersistentCourseGrade
from lms.djangoapps.grades.api import CourseGradeFactory, prefetch_course_grades, clear_prefetched_course_grades
from opaque_keys import InvalidKeyError
from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiveUser
from openedx.core.lib.api.view_utils import PaginatedAPIView, get_course_key, verify_course_exists
@@ -32,11 +31,11 @@ def bulk_course_grade_context(course_key, users):
within a context, storing in a RequestCache and deleting
on context exit.
"""
PersistentCourseGrade.prefetch(course_key, users)
prefetch_course_grades(course_key, users)
try:
yield
finally:
PersistentCourseGrade.clear_prefetched_data(course_key)
clear_prefetched_course_grades(course_key)
class CourseGradesView(GradeViewMixin, PaginatedAPIView):

View File

@@ -7,7 +7,6 @@ from django.contrib.auth import get_user_model
import pytz
from six import text_type
from lms.djangoapps.courseware.courses import get_course
from lms.djangoapps.grades.course_data import CourseData
from lms.djangoapps.grades.subsection_grade import CreateSubsectionGrade
from lms.djangoapps.utils import _get_key
@@ -15,7 +14,7 @@ from opaque_keys.edx.keys import CourseKey, UsageKey
from track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type
from .config.waffle import waffle_flags, REJECTED_EXAM_OVERRIDES_GRADE
from .constants import ScoreDatabaseTableEnum
from .constants import ScoreDatabaseTableEnum, GradeOverrideFeatureEnum
from .events import SUBSECTION_OVERRIDE_EVENT_TYPE
from .models import (
PersistentSubsectionGrade,
@@ -89,7 +88,7 @@ class GradesService(object):
override = PersistentSubsectionGradeOverride.update_or_create_override(
requesting_user=None,
subsection_grade_model=grade,
feature=PersistentSubsectionGradeOverrideHistory.PROCTORING,
feature=GradeOverrideFeatureEnum.proctoring,
earned_all_override=earned_all,
earned_graded_override=earned_graded,
)
@@ -130,7 +129,7 @@ class GradesService(object):
if override is not None:
_ = PersistentSubsectionGradeOverrideHistory.objects.create(
override_id=override.id,
feature=PersistentSubsectionGradeOverrideHistory.PROCTORING,
feature=GradeOverrideFeatureEnum.proctoring,
action=PersistentSubsectionGradeOverrideHistory.DELETE
)
override.delete()
@@ -163,6 +162,7 @@ class GradesService(object):
Given a user_id, course_key, and subsection usage_key,
creates a new ``PersistentSubsectionGrade``.
"""
from lms.djangoapps.courseware.courses import get_course
course = get_course(course_key, depth=None)
subsection = course.get_child(usage_key)
if not subsection:

View File

@@ -16,6 +16,7 @@ from freezegun import freeze_time
from mock import patch
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum
from lms.djangoapps.grades.models import (
BLOCK_RECORD_LIST_VERSION,
BlockRecord,
@@ -309,7 +310,7 @@ class PersistentSubsectionGradeTest(GradesModelTestCase):
subsection_grade_model=grade,
earned_all_override=0.0,
earned_graded_override=0.0,
feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK,
feature=GradeOverrideFeatureEnum.gradebook,
)
grade = PersistentSubsectionGrade.update_or_create_grade(**self.params)

View File

@@ -5,6 +5,7 @@ from datetime import datetime
import ddt
import pytz
from freezegun import freeze_time
from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum
from lms.djangoapps.grades.models import (
PersistentSubsectionGrade,
PersistentSubsectionGradeOverride,
@@ -145,7 +146,7 @@ class GradesServiceTests(ModuleStoreTestCase):
def _verify_override_history(self, override_history, history_action):
self.assertIsNone(override_history.user)
self.assertIsNotNone(override_history.created)
self.assertEqual(override_history.feature, PersistentSubsectionGradeOverrideHistory.PROCTORING)
self.assertEqual(override_history.feature, GradeOverrideFeatureEnum.proctoring)
self.assertEqual(override_history.action, history_action)
@ddt.data(

View File

@@ -9,10 +9,10 @@ from courseware.tests.test_submitting_problems import ProblemSubmissionTestMixin
from lms.djangoapps.grades.config.tests.utils import persistent_grades_feature_flags
from student.tests.factories import UserFactory
from ..constants import GradeOverrideFeatureEnum
from ..models import (
PersistentSubsectionGrade,
PersistentSubsectionGradeOverride,
PersistentSubsectionGradeOverrideHistory,
)
from ..subsection_grade_factory import ZeroSubsectionGrade
from .base import GradeTestBase
@@ -143,7 +143,7 @@ class TestSubsectionGradeFactory(ProblemSubmissionTestMixin, GradeTestBase):
earned_graded_override=earned_graded_override,
earned_all_override=earned_graded_override,
possible_graded_override=possible_graded_override,
feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK,
feature=GradeOverrideFeatureEnum.gradebook,
)
# Now, even if the problem scores interface gives us a 2/3,

View File

@@ -22,10 +22,12 @@ from six import text_type
from course_modes.models import CourseMode
from courseware.models import StudentModule
from eventtracking import tracker
from lms.djangoapps.grades.constants import ScoreDatabaseTableEnum
from lms.djangoapps.grades.events import STATE_DELETED_EVENT_TYPE
from lms.djangoapps.grades.signals.handlers import disconnect_submissions_signal_receiver
from lms.djangoapps.grades.signals.signals import PROBLEM_RAW_SCORE_CHANGED
from lms.djangoapps.grades.api import (
constants as grades_constants,
events as grades_events,
signals as grades_signals,
disconnect_submissions_signal_receiver,
)
from lms.djangoapps.instructor.message_types import (
AccountCreationAndEnrollment,
AddBetaTester,
@@ -298,16 +300,16 @@ def reset_student_attempts(course_id, student, module_state_key, requesting_user
if delete_module:
module_to_reset.delete()
create_new_event_transaction_id()
set_event_transaction_type(STATE_DELETED_EVENT_TYPE)
set_event_transaction_type(grades_events.STATE_DELETED_EVENT_TYPE)
tracker.emit(
unicode(STATE_DELETED_EVENT_TYPE),
unicode(grades_events.STATE_DELETED_EVENT_TYPE),
{
'user_id': unicode(student.id),
'course_id': unicode(course_id),
'problem_id': unicode(module_state_key),
'instructor_id': unicode(requesting_user.id),
'event_transaction_id': unicode(get_event_transaction_id()),
'event_transaction_type': unicode(STATE_DELETED_EVENT_TYPE),
'event_transaction_type': unicode(grades_events.STATE_DELETED_EVENT_TYPE),
}
)
if not submission_cleared:
@@ -351,7 +353,7 @@ def _fire_score_changed_for_block(
if block and block.has_score:
max_score = block.max_score()
if max_score is not None:
PROBLEM_RAW_SCORE_CHANGED.send(
grades_signals.PROBLEM_RAW_SCORE_CHANGED.send(
sender=None,
raw_earned=0,
raw_possible=max_score,
@@ -362,7 +364,7 @@ def _fire_score_changed_for_block(
score_deleted=True,
only_if_higher=False,
modified=datetime.now().replace(tzinfo=pytz.UTC),
score_db_table=ScoreDatabaseTableEnum.courseware_student_module,
score_db_table=grades_constants.ScoreDatabaseTableEnum.courseware_student_module,
)

View File

@@ -7,7 +7,7 @@ from six import text_type
from capa.tests.response_xml_factory import StringResponseXMLFactory
from courseware.tests.factories import StudentModuleFactory
from lms.djangoapps.grades.tasks import compute_all_grades_for_course
from lms.djangoapps.grades.api import task_compute_all_grades_for_course
from student.tests.factories import AdminFactory, CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -73,7 +73,7 @@ class TestGradebook(SharedModuleStoreTestCase):
course_id=self.course.id,
module_state_key=item.location
)
compute_all_grades_for_course.apply_async(kwargs={'course_key': text_type(self.course.id)})
task_compute_all_grades_for_course.apply_async(kwargs={'course_key': text_type(self.course.id)})
self.response = self.client.get(reverse(
'spoc_gradebook',

View File

@@ -12,7 +12,7 @@ from opaque_keys.edx.keys import CourseKey
from courseware.courses import get_course_with_access
from edxmako.shortcuts import render_to_response
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.instructor.views.api import require_level
from xmodule.modulestore.django import modulestore

View File

@@ -43,7 +43,7 @@ from lms.djangoapps.certificates.models import (
GeneratedCertificate
)
from lms.djangoapps.courseware.module_render import get_module_by_usage_id
from lms.djangoapps.grades.config.waffle import WRITABLE_GRADEBOOK, waffle_flags
from lms.djangoapps.grades.api import is_writable_gradebook_enabled
from openedx.core.djangoapps.course_groups.cohorts import DEFAULT_COHORT_NAME, get_course_cohorts, is_course_cohorted
from openedx.core.djangoapps.django_comment_common.models import FORUM_ROLE_ADMINISTRATOR, CourseDiscussionSettings
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
@@ -594,7 +594,7 @@ def _section_student_admin(course, access):
kwargs={'course_id': unicode(course_key)}),
'spoc_gradebook_url': reverse('spoc_gradebook', kwargs={'course_id': unicode(course_key)}),
}
if waffle_flags()[WRITABLE_GRADEBOOK].is_enabled(course_key) and settings.WRITABLE_GRADEBOOK_URL:
if is_writable_gradebook_enabled(course_key) and settings.WRITABLE_GRADEBOOK_URL:
section_data['writable_gradebook_url'] = urljoin(settings.WRITABLE_GRADEBOOK_URL, '/' + text_type(course_key))
return section_data

View File

@@ -16,10 +16,10 @@ from edx_proctoring.api import get_exam_violation_report
from opaque_keys.edx.keys import UsageKey
from six import text_type
from courseware.models import StudentModule
import xmodule.graders as xmgraders
from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
from courseware.models import StudentModule
from lms.djangoapps.grades.context import grading_context_for_course
from lms.djangoapps.grades.api import context as grades_context
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangolib.markup import HTML, Text
@@ -525,7 +525,7 @@ def dump_grading_context(course):
msg += hbar
msg += u"Listing grading context for course %s\n" % text_type(course.id)
gcontext = grading_context_for_course(course)
gcontext = grades_context.grading_context_for_course(course)
msg += "graded sections:\n"
msg += '%s\n' % gcontext['all_graded_subsections_by_type'].keys()

View File

@@ -21,9 +21,11 @@ from courseware.user_state_client import DjangoXBlockUserStateClient
from instructor_analytics.basic import list_problem_responses
from instructor_analytics.csvs import format_dictlist
from lms.djangoapps.certificates.models import CertificateWhitelist, GeneratedCertificate, certificate_info_for_user
from lms.djangoapps.grades.context import grading_context, grading_context_for_course
from lms.djangoapps.grades.models import PersistentCourseGrade, PersistentSubsectionGrade
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import (
CourseGradeFactory,
context as grades_context,
prefetch_course_and_subsection_grades,
)
from lms.djangoapps.teams.models import CourseTeamMembership
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache
@@ -114,7 +116,7 @@ class _CourseGradeReportContext(object):
Returns an OrderedDict that maps an assignment type to a dict of
subsection-headers and average-header.
"""
grading_cxt = grading_context(self.course, self.course_structure)
grading_cxt = grades_context.grading_context(self.course, self.course_structure)
graded_assignments_map = OrderedDict()
for assignment_type_name, subsection_infos in grading_cxt['all_graded_subsections_by_type'].iteritems():
graded_subsections_map = OrderedDict()
@@ -189,8 +191,7 @@ class _CourseGradeBulkContext(object):
self.enrollments = _EnrollmentBulkContext(context, users)
bulk_cache_cohorts(context.course_id, users)
BulkRoleCache.prefetch(users)
PersistentCourseGrade.prefetch(context.course_id, users)
PersistentSubsectionGrade.prefetch(context.course_id, users)
prefetch_course_and_subsection_grades(context.course_id, users)
BulkCourseTags.prefetch(context.course_id, users)
@@ -579,7 +580,7 @@ class ProblemGradeReport(object):
headers in the final report.
"""
scorable_blocks_map = OrderedDict()
grading_context = grading_context_for_course(course)
grading_context = grades_context.grading_context_for_course(course)
for assignment_type_name, subsection_infos in grading_context['all_graded_subsections_by_type'].iteritems():
for subsection_index, subsection_info in enumerate(subsection_infos, start=1):
for scorable_block in subsection_info['scored_descendants']:

View File

@@ -13,7 +13,7 @@ from courseware.courses import get_course_by_id, get_problems_in_section
from courseware.model_data import DjangoKeyValueStore, FieldDataCache
from courseware.models import StudentModule
from courseware.module_render import get_module_for_descriptor_internal
from lms.djangoapps.grades.events import GRADES_OVERRIDE_EVENT_TYPE, GRADES_RESCORE_EVENT_TYPE
from lms.djangoapps.grades.api import events as grades_events
from student.models import get_user_by_username_or_email
from track.event_transaction_utils import create_new_event_transaction_id, set_event_transaction_type
from track.views import task_track
@@ -162,7 +162,7 @@ def rescore_problem_module_state(xmodule_instance_args, module_descriptor, stude
# calls that create events. We retrieve and store the id here because
# the request cache will be erased during downstream calls.
create_new_event_transaction_id()
set_event_transaction_type(GRADES_RESCORE_EVENT_TYPE)
set_event_transaction_type(grades_events.GRADES_RESCORE_EVENT_TYPE)
# specific events from CAPA are not propagated up the stack. Do we want this?
try:
@@ -246,7 +246,7 @@ def override_score_module_state(xmodule_instance_args, module_descriptor, studen
# calls that create events. We retrieve and store the id here because
# the request cache will be erased during downstream calls.
create_new_event_transaction_id()
set_event_transaction_type(GRADES_OVERRIDE_EVENT_TYPE)
set_event_transaction_type(grades_events.GRADES_OVERRIDE_EVENT_TYPE)
problem_weight = instance.weight if instance.weight is not None else 1
if problem_weight == 0:

View File

@@ -20,7 +20,7 @@ from six import text_type
from capa.responsetypes import StudentInputError
from capa.tests.response_xml_factory import CodeResponseXMLFactory, CustomResponseXMLFactory
from courseware.model_data import StudentModule
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.instructor_task.api import (
submit_delete_problem_state_for_all_students,
submit_rescore_problem_for_all_students,

View File

@@ -8,7 +8,7 @@ from django.conf import settings
from django.dispatch import receiver
import lti_provider.outcomes as outcomes
from lms.djangoapps.grades.signals.signals import PROBLEM_WEIGHTED_SCORE_CHANGED
from lms.djangoapps.grades.api import signals as grades_signals
from lti_provider.views import parse_course_and_usage_keys
from xmodule.modulestore.django import modulestore
from .tasks import send_composite_outcome, send_leaf_outcome
@@ -35,7 +35,7 @@ def increment_assignment_versions(course_key, usage_key, user_id):
return assignments
@receiver(PROBLEM_WEIGHTED_SCORE_CHANGED)
@receiver(grades_signals.PROBLEM_WEIGHTED_SCORE_CHANGED)
def score_changed_handler(sender, **kwargs): # pylint: disable=unused-argument
"""
Consume signals that indicate score changes. See the definition of

View File

@@ -9,7 +9,7 @@ from opaque_keys.edx.keys import CourseKey
import lti_provider.outcomes as outcomes
from lms import CELERY_APP
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from lti_provider.models import GradedAssignment
from xmodule.modulestore.django import modulestore

View File

@@ -5,7 +5,7 @@
<%!
from course_modes.models import CourseMode
from lms.djangoapps.certificates.models import CertificateStatuses
from lms.djangoapps.grades.models import PersistentSubsectionGradeOverrideHistory
from lms.djangoapps.grades.api import constants as grades_constants
from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import HTML, Text
from django.urls import reverse
@@ -198,7 +198,7 @@ username = get_enterprise_learner_generic_name(request) or student.username
<p class="override-notice">
%if section.override is not None:
<%last_override_history = section.override.get_history().order_by('created').last()%>
%if (not last_override_history or last_override_history.feature == PersistentSubsectionGradeOverrideHistory.PROCTORING) and section.format == "Exam" and earned == 0:
%if (not last_override_history or last_override_history.feature == grades_constants.GradeOverrideFeatureEnum.proctoring) and section.format == "Exam" and earned == 0:
${_("Suspicious activity detected during proctored exam review. Exam score 0.")}
%else:
${_("Section grade has been overridden.")}

View File

@@ -22,8 +22,8 @@ from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from pytz import UTC
from lms.djangoapps.certificates.models import GeneratedCertificate
from lms.djangoapps.grades.models import PersistentCourseGrade
from lms.djangoapps.certificates.api import get_recently_modified_certificates
from lms.djangoapps.grades.api import get_recently_modified_grades
from openedx.core.djangoapps.credentials.models import NotifyCredentialsConfig
from openedx.core.djangoapps.credentials.signals import handle_cert_change, send_grade_if_interesting
from openedx.core.djangoapps.programs.signals import handle_course_cert_changed
@@ -169,32 +169,18 @@ class Command(BaseCommand):
options['delay']
)
cert_filter_args = {}
grade_filter_args = {}
try:
site_config = SiteConfiguration.objects.get(site__domain=options['site']) if options['site'] else None
except SiteConfiguration.DoesNotExist:
log.error(u'No site configuration found for site %s', options['site'])
if options['courses']:
course_keys = self.get_course_keys(options['courses'])
cert_filter_args['course_id__in'] = course_keys
grade_filter_args['course_id__in'] = course_keys
if options['start_date']:
cert_filter_args['modified_date__gte'] = options['start_date']
grade_filter_args['modified__gte'] = options['start_date']
if options['end_date']:
cert_filter_args['modified_date__lte'] = options['end_date']
grade_filter_args['modified__lte'] = options['end_date']
if not cert_filter_args:
course_keys = self.get_course_keys(options['courses'])
if not (course_keys or options['start_date'] or options['end_date']):
raise CommandError('You must specify a filter (e.g. --courses= or --start-date)')
# pylint: disable=no-member
certs = GeneratedCertificate.objects.filter(**cert_filter_args).order_by('modified_date')
grades = PersistentCourseGrade.objects.filter(**grade_filter_args).order_by('modified')
certs = get_recently_modified_certificates(course_keys, options['start_date'], options['end_date'])
grades = get_recently_modified_grades(course_keys, options['start_date'], options['end_date'])
if options['dry_run']:
self.print_dry_run(certs, grades)
@@ -254,7 +240,7 @@ class Command(BaseCommand):
verbose=verbose
)
def get_course_keys(self, courses):
def get_course_keys(self, courses=None):
"""
Return a list of CourseKeys that we will emit signals to.
@@ -265,7 +251,10 @@ class Command(BaseCommand):
it is a fatal error and will cause us to exit the entire process.
"""
# Use specific courses if specified, but fall back to all courses.
if not courses:
courses = []
course_keys = []
log.info(u"%d courses specified: %s", len(courses), ", ".join(courses))
for course_id in courses:
try:

View File

@@ -6,7 +6,7 @@ from logging import getLogger
from course_modes.models import CourseMode
from django.contrib.sites.models import Site
from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from openedx.core.djangoapps.catalog.utils import get_programs
from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
from openedx.core.djangoapps.site_configuration import helpers

View File

@@ -24,7 +24,7 @@ from lms.djangoapps.certificates import api as certificate_api
from lms.djangoapps.certificates.models import GeneratedCertificate
from lms.djangoapps.commerce.utils import EcommerceService
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from lms.djangoapps.grades.api import CourseGradeFactory
from openedx.core.djangoapps.catalog.utils import get_programs, get_fulfillable_course_runs_for_entitlement
from openedx.core.djangoapps.certificates.api import available_date_for_certificate
from openedx.core.djangoapps.commerce.utils import ecommerce_api_client

View File

@@ -12,7 +12,7 @@ from django.utils.translation import ugettext as _
from completion.models import BlockCompletion
from lms.djangoapps.courseware.access import _has_access_to_course
from lms.djangoapps.course_blocks.api import get_course_blocks
from lms.djangoapps.grades.subsection_grade_factory import SubsectionGradeFactory
from lms.djangoapps.grades.api import SubsectionGradeFactory
from milestones import api as milestones_api
from opaque_keys.edx.keys import UsageKey
from openedx.core.lib.gating.exceptions import GatingValidationError

View File

@@ -10,10 +10,10 @@ from mock import patch, Mock
from ddt import ddt, data, unpack
from django.conf import settings
from lms.djangoapps.gating import api as lms_gating_api
from lms.djangoapps.grades.constants import GradeOverrideFeatureEnum
from lms.djangoapps.grades.models import (
PersistentSubsectionGrade,
PersistentSubsectionGradeOverride,
PersistentSubsectionGradeOverrideHistory,
)
from lms.djangoapps.grades.tests.base import GradeTestBase
from lms.djangoapps.grades.tests.utils import mock_get_score
@@ -392,7 +392,7 @@ class TestGatingGradesIntegration(GradeTestBase):
earned_graded_override=0,
earned_all_override=0,
possible_graded_override=3,
feature=PersistentSubsectionGradeOverrideHistory.GRADEBOOK,
feature=GradeOverrideFeatureEnum.gradebook,
)
# it's important that we stay in the mock_get_score() context here,

View File

@@ -15,7 +15,7 @@ import six
from xblock.completable import XBlockCompletionMode
from xblock.core import XBlock
from lms.djangoapps.grades.signals.signals import PROBLEM_WEIGHTED_SCORE_CHANGED
from lms.djangoapps.grades.api import signals as grades_signals
from openedx.core.djangolib.testing.utils import skip_unless_lms
@@ -120,7 +120,7 @@ class ScorableCompletionHandlerTestCase(CompletionSetUpMixin, TestCase):
def test_signal_calls_handler(self):
with patch('completion.handlers.scorable_block_completion') as mock_handler:
PROBLEM_WEIGHTED_SCORE_CHANGED.send_robust(
grades_signals.PROBLEM_WEIGHTED_SCORE_CHANGED.send_robust(
sender=self,
user_id=self.user.id,
course_id=six.text_type(self.course_key),

View File

@@ -73,6 +73,7 @@ INSTALLED_APPS = (
'courseware',
'student',
'openedx.core.djangoapps.site_configuration',
'lms.djangoapps.grades.apps.GradesConfig',
'lms.djangoapps.certificates.apps.CertificatesConfig',
'openedx.core.djangoapps.user_api',
'course_modes.apps.CourseModesConfig',
@@ -101,6 +102,11 @@ MEDIA_ROOT = tempfile.mkdtemp()
MICROSITE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeBackend'
MICROSITE_TEMPLATE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeTemplateBackend'
RECALCULATE_GRADES_ROUTING_KEY = 'edx.core.default'
POLICY_CHANGE_GRADES_ROUTING_KEY = 'edx.core.default'
POLICY_CHANGE_TASK_RATE_LIMIT = '300/h'
SECRET_KEY = 'insecure-secret-key'
SITE_ID = 1