Inter-app API cleanup for Grades
This commit is contained in:
@@ -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,
|
||||
))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
24
lms/djangoapps/grades/api.py
Normal file
24
lms/djangoapps/grades/api.py
Normal 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
|
||||
@@ -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: {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -10,3 +10,8 @@ class ScoreDatabaseTableEnum(object):
|
||||
courseware_student_module = 'csm'
|
||||
submissions = 'submissions'
|
||||
overrides = 'overrides'
|
||||
|
||||
|
||||
class GradeOverrideFeatureEnum(object):
|
||||
proctoring = 'PROCTORING'
|
||||
gradebook = 'GRADEBOOK'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
48
lms/djangoapps/grades/models_api.py
Normal file
48
lms/djangoapps/grades/models_api.py
Normal 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')
|
||||
@@ -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'))
|
||||
]
|
||||
@@ -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
|
||||
@@ -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(
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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):
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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']:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.")}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user