Commit 8a0f11bb by sanfordstudent Committed by GitHub

Merge pull request #14781 from edx/sstudent/TNL-6696

add settings for compute grades command
parents d4c6f9fa bdc0b33c
...@@ -5,7 +5,11 @@ from django.contrib import admin ...@@ -5,7 +5,11 @@ from django.contrib import admin
from config_models.admin import ConfigurationModelAdmin, KeyedConfigurationModelAdmin from config_models.admin import ConfigurationModelAdmin, KeyedConfigurationModelAdmin
from lms.djangoapps.grades.config.models import CoursePersistentGradesFlag, PersistentGradesEnabledFlag from lms.djangoapps.grades.config.models import (
CoursePersistentGradesFlag,
PersistentGradesEnabledFlag,
ComputeGradesSetting,
)
from lms.djangoapps.grades.config.forms import CoursePersistentGradesAdminForm from lms.djangoapps.grades.config.forms import CoursePersistentGradesAdminForm
...@@ -25,3 +29,4 @@ class CoursePersistentGradesAdmin(KeyedConfigurationModelAdmin): ...@@ -25,3 +29,4 @@ class CoursePersistentGradesAdmin(KeyedConfigurationModelAdmin):
admin.site.register(CoursePersistentGradesFlag, CoursePersistentGradesAdmin) admin.site.register(CoursePersistentGradesFlag, CoursePersistentGradesAdmin)
admin.site.register(PersistentGradesEnabledFlag, ConfigurationModelAdmin) admin.site.register(PersistentGradesEnabledFlag, ConfigurationModelAdmin)
admin.site.register(ComputeGradesSetting, ConfigurationModelAdmin)
...@@ -4,7 +4,7 @@ controlling persistent grades. ...@@ -4,7 +4,7 @@ controlling persistent grades.
""" """
from config_models.models import ConfigurationModel from config_models.models import ConfigurationModel
from django.conf import settings from django.conf import settings
from django.db.models import BooleanField from django.db.models import BooleanField, IntegerField, TextField
from openedx.core.djangoapps.xmodule_django.models import CourseKeyField from openedx.core.djangoapps.xmodule_django.models import CourseKeyField
from request_cache.middleware import request_cached from request_cache.middleware import request_cached
...@@ -71,3 +71,17 @@ class CoursePersistentGradesFlag(ConfigurationModel): ...@@ -71,3 +71,17 @@ class CoursePersistentGradesFlag(ConfigurationModel):
not_en = "" not_en = ""
# pylint: disable=no-member # pylint: disable=no-member
return u"Course '{}': Persistent Grades {}Enabled".format(self.course_id.to_deprecated_string(), not_en) return u"Course '{}': Persistent Grades {}Enabled".format(self.course_id.to_deprecated_string(), not_en)
class ComputeGradesSetting(ConfigurationModel):
"""
...
"""
class Meta(object):
app_label = "grades"
batch_size = IntegerField(default=100)
course_ids = TextField(
blank=False,
help_text="Whitespace-separated list of course keys for which to compute grades."
)
...@@ -6,13 +6,14 @@ from __future__ import absolute_import, division, print_function, unicode_litera ...@@ -6,13 +6,14 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import logging import logging
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand, CommandError
import six import six
from openedx.core.lib.command_utils import ( from openedx.core.lib.command_utils import (
get_mutually_exclusive_required_option, get_mutually_exclusive_required_option,
parse_course_keys, parse_course_keys,
) )
from lms.djangoapps.grades.config.models import ComputeGradesSetting
from student.models import CourseEnrollment from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -48,6 +49,12 @@ class Command(BaseCommand): ...@@ -48,6 +49,12 @@ class Command(BaseCommand):
default=False, default=False,
) )
parser.add_argument( parser.add_argument(
'--from_settings',
help='Compute grades with settings set via Django admin',
action='store_true',
default=False,
)
parser.add_argument(
'--routing_key', '--routing_key',
dest='routing_key', dest='routing_key',
help='Celery routing key to use.', help='Celery routing key to use.',
...@@ -84,10 +91,11 @@ class Command(BaseCommand): ...@@ -84,10 +91,11 @@ class Command(BaseCommand):
# created, the most recent enrollments may not get processed. # created, the most recent enrollments may not get processed.
# This is an acceptable limitation for our known use cases. # This is an acceptable limitation for our known use cases.
task_options = {'routing_key': options['routing_key']} if options.get('routing_key') else {} task_options = {'routing_key': options['routing_key']} if options.get('routing_key') else {}
batch_size = self._latest_settings().batch_size if options.get('from_settings') else options['batch_size']
kwargs = { kwargs = {
'course_key': six.text_type(course_key), 'course_key': six.text_type(course_key),
'offset': offset, 'offset': offset,
'batch_size': options['batch_size'], 'batch_size': batch_size,
} }
result = tasks.compute_grades_for_course.apply_async( result = tasks.compute_grades_for_course.apply_async(
kwargs=kwargs, kwargs=kwargs,
...@@ -103,11 +111,14 @@ class Command(BaseCommand): ...@@ -103,11 +111,14 @@ class Command(BaseCommand):
""" """
Return a list of courses that need scores computed. Return a list of courses that need scores computed.
""" """
courses_mode = get_mutually_exclusive_required_option(options, 'courses', 'all_courses')
courses_mode = get_mutually_exclusive_required_option(options, 'courses', 'all_courses', 'from_settings')
if courses_mode == 'all_courses': if courses_mode == 'all_courses':
course_keys = [course.id for course in modulestore().get_course_summaries()] course_keys = [course.id for course in modulestore().get_course_summaries()]
else: elif courses_mode == 'courses':
course_keys = parse_course_keys(options['courses']) course_keys = parse_course_keys(options['courses'])
else:
course_keys = parse_course_keys(self._latest_settings().course_ids.split())
return course_keys return course_keys
def _set_log_level(self, options): def _set_log_level(self, options):
...@@ -120,3 +131,6 @@ class Command(BaseCommand): ...@@ -120,3 +131,6 @@ class Command(BaseCommand):
elif options.get('verbosity') >= 1: elif options.get('verbosity') >= 1:
log_level = logging.INFO log_level = logging.INFO
log.setLevel(log_level) log.setLevel(log_level)
def _latest_settings(self):
return ComputeGradesSetting.objects.order_by('-id')[0]
...@@ -16,6 +16,7 @@ from student.models import CourseEnrollment ...@@ -16,6 +16,7 @@ from student.models import CourseEnrollment
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from lms.djangoapps.grades.config.models import ComputeGradesSetting
from lms.djangoapps.grades.management.commands import compute_grades from lms.djangoapps.grades.management.commands import compute_grades
...@@ -59,6 +60,18 @@ class TestComputeGrades(SharedModuleStoreTestCase): ...@@ -59,6 +60,18 @@ class TestComputeGrades(SharedModuleStoreTestCase):
with self.assertRaises(CommandError): with self.assertRaises(CommandError):
self.command._get_course_keys({'courses': [self.course_keys[0], self.course_keys[1], 'badcoursekey']}) self.command._get_course_keys({'courses': [self.course_keys[0], self.course_keys[1], 'badcoursekey']})
def test_from_settings(self):
ComputeGradesSetting.objects.create(course_ids=" ".join(self.course_keys))
courses = self.command._get_course_keys({'from_settings': True})
self.assertEqual(
sorted(six.text_type(course) for course in courses),
self.course_keys,
)
# test that --from_settings always uses the latest setting
ComputeGradesSetting.objects.create(course_ids='badcoursekey')
with self.assertRaises(CommandError):
self.command._get_course_keys({'from_settings': True})
@patch('lms.djangoapps.grades.tasks.compute_grades_for_course') @patch('lms.djangoapps.grades.tasks.compute_grades_for_course')
def test_tasks_fired(self, mock_task): def test_tasks_fired(self, mock_task):
call_command( call_command(
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('grades', '0011_null_edited_time'),
]
operations = [
migrations.CreateModel(
name='ComputeGradesSetting',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
('batch_size', models.IntegerField(default=100)),
('course_ids', models.TextField(help_text=b'Whitespace-separated list of course keys for which to compute grades.')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
],
),
]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment