Adds config models to pass arguments to management command

This would enable simulate_publish management command to
fetch its arguments from config model.

PROD-548
This commit is contained in:
Adeel Khan
2019-09-16 17:39:34 +05:00
parent d16bf75777
commit 98c6c1af49
5 changed files with 125 additions and 6 deletions

View File

@@ -8,7 +8,7 @@ from __future__ import absolute_import
from config_models.admin import ConfigurationModelAdmin
from django.contrib import admin
from .models import CourseOverview, CourseOverviewImageConfig, CourseOverviewImageSet
from .models import CourseOverview, CourseOverviewImageConfig, CourseOverviewImageSet, SimulateCoursePublishConfig
class CourseOverviewAdmin(admin.ModelAdmin):
@@ -73,6 +73,11 @@ class CourseOverviewImageSetAdmin(admin.ModelAdmin):
fields = ('course_overview_id', 'small_url', 'large_url')
class SimulateCoursePublishConfigAdmin(ConfigurationModelAdmin):
pass
admin.site.register(CourseOverview, CourseOverviewAdmin)
admin.site.register(CourseOverviewImageConfig, CourseOverviewImageConfigAdmin)
admin.site.register(CourseOverviewImageSet, CourseOverviewImageSetAdmin)
admin.site.register(SimulateCoursePublishConfig, SimulateCoursePublishConfigAdmin)

View File

@@ -21,10 +21,10 @@ import textwrap
import time
import six
from django.core.management.base import BaseCommand
from django.core.management.base import BaseCommand, CommandError
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.content.course_overviews.models import SimulateCoursePublishConfig
from lms.djangoapps.ccx.tasks import course_published_handler as ccx_receiver_fn
from xmodule.modulestore.django import SignalHandler, modulestore
@@ -42,10 +42,10 @@ class Command(BaseCommand):
$ ./manage.py lms --settings=devstack_docker simulate_publish --delay 10
# Find all available listeners
$ ./manage.py lms --settings=devstack_docker simulate_publish --show_listeners
$ ./manage.py lms --settings=devstack_docker simulate_publish --show_receivers
# Send the publish signal to two courses and two listeners
$ ./manage.py lms --settings=devstack_docker simulate_publish --listeners \
$ ./manage.py lms --settings=devstack_docker simulate_publish --receivers \
openedx.core.djangoapps.content.course_overviews.signals._listen_for_course_publish \
openedx.core.djangoapps.bookmarks.signals.trigger_update_xblocks_cache_task \
--courses course-v1:edX+DemoX+Demo_Course edX/MODULESTORE_100/2018
@@ -157,8 +157,30 @@ class Command(BaseCommand):
u"with this flag, so that CCX receivers are omitted."
)
),
parser.add_argument(
'--args-from-database',
action='store_true',
help='Use arguments from the SimulateCoursePublishConfig model instead of the command line.',
),
def get_args_from_database(self):
""" Returns an options dictionary from the current SimulateCoursePublishConfig model. """
config = SimulateCoursePublishConfig.current()
if not config.enabled:
raise CommandError('SimulateCourseConfigPublish is disabled, but --args-from-database was requested.')
# We don't need fancy shell-style whitespace/quote handling - none of our arguments are complicated
argv = config.arguments.split()
parser = self.create_parser('manage.py', 'simulate_publish')
return parser.parse_args(argv).__dict__ # we want a dictionary, not a non-iterable Namespace object
def handle(self, *args, **options):
if options['args_from_database']:
options = self.get_args_from_database()
if options['show_receivers']:
return self.print_show_receivers()

View File

@@ -5,15 +5,21 @@ from __future__ import absolute_import
import six
from django.core.management import call_command
from django.core.management.base import CommandError
from testfixtures import LogCapture
import lms.djangoapps.ccx.tasks
import openedx.core.djangoapps.content.course_overviews.signals
from openedx.core.djangoapps.content.course_overviews.management.commands.simulate_publish import Command, name_from_fn
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview, SimulateCoursePublishConfig
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import SwitchedSignal
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
LOGGER_NAME = 'simulate_publish'
class TestSimulatePublish(SharedModuleStoreTestCase):
"""Test simulate_publish, our fake course-publish signal command."""
@@ -97,6 +103,7 @@ class TestSimulatePublish(SharedModuleStoreTestCase):
delay=0,
force_lms=False,
skip_ccx=False,
args_from_database=False
)
default_options.update(kwargs)
return default_options
@@ -147,3 +154,35 @@ class TestSimulatePublish(SharedModuleStoreTestCase):
def sample_receiver_2(self, sender, course_key, **kwargs): # pylint: disable=unused-argument
"""Custom receiver for testing."""
self.received_2.append(course_key)
def test_args_from_database(self):
"""Test management command arguments injected from config model."""
# Nothing in the database, should default to disabled
with self.assertRaisesRegex(CommandError, 'SimulateCourseConfigPublish is disabled.*'):
call_command('simulate_publish', '--args-from-database')
# Add a config
config = SimulateCoursePublishConfig.current()
config.arguments = '--delay 20 --dry-run'
config.enabled = True
config.save()
with LogCapture(LOGGER_NAME) as log:
call_command('simulate_publish')
log.check_present(
(
LOGGER_NAME, 'INFO',
u"simulate_publish starting, dry-run={}, delay={} seconds".format('False', '0')
),
)
with LogCapture(LOGGER_NAME) as log:
call_command('simulate_publish', '--args-from-database')
log.check_present(
(
LOGGER_NAME, 'INFO',
u"simulate_publish starting, dry-run={}, delay={} seconds".format('True', '20')
),
)

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-09-16 10:45
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('course_overviews', '0015_historicalcourseoverview'),
]
operations = [
migrations.CreateModel(
name='SimulateCoursePublishConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
('arguments', models.TextField(blank=True, default=b'', help_text=b'Useful for manually running a Jenkins job. Specify like "--delay 10 --listeners A B C --courses X Y Z".')),
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
],
options={
'verbose_name': 'simulate_publish argument',
},
),
]

View File

@@ -896,3 +896,25 @@ class CourseOverviewImageConfig(ConfigurationModel):
return u"CourseOverviewImageConfig(enabled={}, small={}, large={})".format(
self.enabled, self.small, self.large
)
class SimulateCoursePublishConfig(ConfigurationModel):
"""
Manages configuration for a run of the simulate_publish management command.
.. no_pii:
"""
class Meta(object):
app_label = 'course_overviews'
verbose_name = 'simulate_publish argument'
arguments = models.TextField(
blank=True,
help_text='Useful for manually running a Jenkins job. Specify like "--delay 10 --receivers A B C \
--courses X Y Z".',
default='',
)
def __unicode__(self):
return six.text_type(self.arguments)