From d49c82f9bdb5689c1595a4d39aa6a1e038ecf33b Mon Sep 17 00:00:00 2001 From: Don Mitchell Date: Wed, 17 Dec 2014 11:57:00 -0500 Subject: [PATCH] Add configuration to studio for asides --- cms/djangoapps/contentstore/views/preview.py | 5 +- .../contentstore/views/tests/test_preview.py | 55 ++++++++++++-- cms/djangoapps/xblock_config/__init__.py | 0 cms/djangoapps/xblock_config/admin.py | 9 +++ .../xblock_config/migrations/0001_initial.py | 74 +++++++++++++++++++ .../xblock_config/migrations/__init__.py | 0 cms/djangoapps/xblock_config/models.py | 28 +++++++ cms/envs/common.py | 1 + pavelib/utils/test/suites/acceptance_suite.py | 3 + scripts/reset-test-db.sh | 3 + 10 files changed, 170 insertions(+), 8 deletions(-) create mode 100644 cms/djangoapps/xblock_config/__init__.py create mode 100644 cms/djangoapps/xblock_config/admin.py create mode 100644 cms/djangoapps/xblock_config/migrations/0001_initial.py create mode 100644 cms/djangoapps/xblock_config/migrations/__init__.py create mode 100644 cms/djangoapps/xblock_config/models.py diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index f6fba13c682c..71d6056f42da 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -33,6 +33,7 @@ from .helpers import render_from_lms from contentstore.views.access import get_user_role +from cms.djangoapps.xblock_config.models import StudioConfig __all__ = ['preview_handler'] @@ -97,8 +98,10 @@ def local_resource_url(self, block, uri): def applicable_aside_types(self, block): """ - Remove acid_aside + Remove acid_aside and honor the config record """ + if not StudioConfig.asides_enabled(block.scope_ids.block_type): + return [] return [ aside_type for aside_type in super(PreviewModuleSystem, self).applicable_aside_types(block) diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py index 6631daace4ac..6ba40c1a58c1 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -1,18 +1,21 @@ """ Tests for contentstore.views.preview.py """ +import re + from django.test import TestCase from django.test.client import RequestFactory +from xblock.core import XBlockAside from student.tests.factories import UserFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from contentstore.views.preview import get_preview_fragment from xmodule.modulestore import ModuleStoreEnum -from xblock.core import XBlockAside from xmodule.modulestore.tests.test_asides import AsideTestType -import re +from cms.djangoapps.xblock_config.models import StudioConfig +from xmodule.modulestore.django import modulestore class GetPreviewHtmlTestCase(TestCase): @@ -22,14 +25,11 @@ class GetPreviewHtmlTestCase(TestCase): Note that there are other existing test cases in test_contentstore that indirectly execute get_preview_fragment via the xblock RESTful API. """ - @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') def test_preview_fragment(self): """ - Test for calling get_preview_html. - - This test used to be specifically about Locators (ensuring that they did not - get translated to Locations). The test now has questionable value. + Test for calling get_preview_html. Ensures data-usage-id is correctly set and + asides are correctly included. """ course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) html = ItemFactory.create( @@ -38,6 +38,10 @@ def test_preview_fragment(self): data={'data': "foobar"} ) + config = StudioConfig.current() + config.enabled = True + config.save() + request = RequestFactory().get('/dummy-url') request.user = UserFactory() request.session = {} @@ -60,3 +64,40 @@ def test_preview_fragment(self): self.assertRegexpMatches(html, "Aside rendered") # Now ensure the acid_aside is not in the result self.assertNotRegexpMatches(html, r"data-block-type=[\"\']acid_aside[\"\']") + + # Ensure about pages don't have asides + about = modulestore().get_item(course.id.make_usage_key('about', 'overview')) + html = get_preview_fragment(request, about, context).content + self.assertNotRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']") + self.assertNotRegexpMatches(html, "Aside rendered") + + @XBlockAside.register_temp_plugin(AsideTestType, 'test_aside') + def test_preview_no_asides(self): + """ + Test for calling get_preview_html. Ensures data-usage-id is correctly set and + asides are correctly excluded because they are not enabled. + """ + course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) + html = ItemFactory.create( + parent_location=course.location, + category="html", + data={'data': "foobar"} + ) + + config = StudioConfig.current() + config.enabled = False + config.save() + + request = RequestFactory().get('/dummy-url') + request.user = UserFactory() + request.session = {} + + # Call get_preview_fragment directly. + context = { + 'reorderable_items': set(), + 'read_only': True + } + html = get_preview_fragment(request, html, context).content + + self.assertNotRegexpMatches(html, r"data-block-type=[\"\']test_aside[\"\']") + self.assertNotRegexpMatches(html, "Aside rendered") diff --git a/cms/djangoapps/xblock_config/__init__.py b/cms/djangoapps/xblock_config/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cms/djangoapps/xblock_config/admin.py b/cms/djangoapps/xblock_config/admin.py new file mode 100644 index 000000000000..daad91a062ed --- /dev/null +++ b/cms/djangoapps/xblock_config/admin.py @@ -0,0 +1,9 @@ +""" +Django admin dashboard configuration for LMS XBlock infrastructure. +""" + +from django.contrib import admin +from config_models.admin import ConfigurationModelAdmin +from cms.djangoapps.xblock_config.models import StudioConfig + +admin.site.register(StudioConfig, ConfigurationModelAdmin) diff --git a/cms/djangoapps/xblock_config/migrations/0001_initial.py b/cms/djangoapps/xblock_config/migrations/0001_initial.py new file mode 100644 index 000000000000..c48d02bd5710 --- /dev/null +++ b/cms/djangoapps/xblock_config/migrations/0001_initial.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'StudioConfig' + db.create_table('xblock_config_studioconfig', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)), + ('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)), + ('disabled_blocks', self.gf('django.db.models.fields.TextField')(default='about course_info static_tab')), + )) + db.send_create_signal('xblock_config', ['StudioConfig']) + + + def backwards(self, orm): + # Deleting model 'StudioConfig' + db.delete_table('xblock_config_studioconfig') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'xblock_config.studioconfig': { + 'Meta': {'object_name': 'StudioConfig'}, + 'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}), + 'disabled_blocks': ('django.db.models.fields.TextField', [], {'default': "'about course_info static_tab'"}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + } + } + + complete_apps = ['xblock_config'] \ No newline at end of file diff --git a/cms/djangoapps/xblock_config/migrations/__init__.py b/cms/djangoapps/xblock_config/migrations/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/cms/djangoapps/xblock_config/models.py b/cms/djangoapps/xblock_config/models.py new file mode 100644 index 000000000000..b002d13850b1 --- /dev/null +++ b/cms/djangoapps/xblock_config/models.py @@ -0,0 +1,28 @@ +""" +Models used by Studio XBlock infrastructure. + +Includes: + StudioConfig: A ConfigurationModel for managing Studio. +""" + +from django.db.models import TextField + +from config_models.models import ConfigurationModel + + +class StudioConfig(ConfigurationModel): + """ + Configuration for XBlockAsides. + """ + disabled_blocks = TextField( + default="about course_info static_tab", + help_text="Space-separated list of XBlocks on which XBlockAsides should never render in studio", + ) + + @classmethod + def asides_enabled(cls, block_type): + """ + Return True if asides are enabled for this type of block in studio + """ + studio_config = cls.current() + return studio_config.enabled and block_type not in studio_config.disabled_blocks.split() diff --git a/cms/envs/common.py b/cms/envs/common.py index 397620ef864b..216cc8fbc6ba 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -581,6 +581,7 @@ 'course_creators', 'student', # misleading name due to sharing with lms 'openedx.core.djangoapps.course_groups', # not used in cms (yet), but tests run + 'xblock_config', # Tracking 'track', diff --git a/pavelib/utils/test/suites/acceptance_suite.py b/pavelib/utils/test/suites/acceptance_suite.py index 377b753836fa..55ac45720900 100644 --- a/pavelib/utils/test/suites/acceptance_suite.py +++ b/pavelib/utils/test/suites/acceptance_suite.py @@ -122,10 +122,13 @@ def _setup_acceptance_db(self): # Run migrations to update the db, starting from its cached state sh("./manage.py lms --settings acceptance migrate --traceback --noinput") + sh("./manage.py cms --settings acceptance migrate --traceback --noinput") else: # If no cached database exists, syncdb before migrating, then create the cache sh("./manage.py lms --settings acceptance syncdb --traceback --noinput") + sh("./manage.py cms --settings acceptance syncdb --traceback --noinput") sh("./manage.py lms --settings acceptance migrate --traceback --noinput") + sh("./manage.py cms --settings acceptance migrate --traceback --noinput") # Create the cache if it doesn't already exist sh("cp {db} {db_cache}".format(db_cache=self.db_cache, db=self.db)) diff --git a/scripts/reset-test-db.sh b/scripts/reset-test-db.sh index 4b6634bc47c2..102e7f367c5b 100755 --- a/scripts/reset-test-db.sh +++ b/scripts/reset-test-db.sh @@ -39,6 +39,7 @@ if [[ -f $DB_CACHE_DIR/bok_choy_schema.sql && -f $DB_CACHE_DIR/bok_choy_data.jso # Re-run migrations to ensure we are up-to-date ./manage.py lms --settings bok_choy migrate --traceback --noinput + ./manage.py cms --settings bok_choy migrate --traceback --noinput # Otherwise, update the test database and update the cache else @@ -48,7 +49,9 @@ else # Re-run migrations on the test database ./manage.py lms --settings bok_choy syncdb --traceback --noinput + ./manage.py cms --settings bok_choy syncdb --traceback --noinput ./manage.py lms --settings bok_choy migrate --traceback --noinput + ./manage.py cms --settings bok_choy migrate --traceback --noinput # Dump the schema and data to the cache ./manage.py lms --settings bok_choy dumpdata > $DB_CACHE_DIR/bok_choy_data.json