Commit 2bbd7da9 by cahrens

Add new xblock config models.

TNL-4666
parent d7da5f0a
""" """
Django admin dashboard configuration. Django admin XBlock support configuration.
""" """
from django.contrib import admin from django.contrib import admin
from config_models.admin import ConfigurationModelAdmin from config_models.admin import ConfigurationModelAdmin
from xblock_django.models import XBlockDisableConfig from xblock_django.models import XBlockDisableConfig, XBlockConfig, XBlockConfigFlag
from simple_history.admin import SimpleHistoryAdmin
class XBlockConfigAdmin(SimpleHistoryAdmin):
"""Admin for XBlock Configuration"""
list_display = ('name', 'template', 'support_level', 'deprecated', 'changed_by', 'change_date')
admin.site.register(XBlockDisableConfig, ConfigurationModelAdmin) admin.site.register(XBlockDisableConfig, ConfigurationModelAdmin)
admin.site.register(XBlockConfigFlag, ConfigurationModelAdmin)
admin.site.register(XBlockConfig, XBlockConfigAdmin)
# -*- 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),
('xblock_django', '0002_auto_20160204_0809'),
]
operations = [
migrations.CreateModel(
name='HistoricalXBlockConfig',
fields=[
('id', models.IntegerField(verbose_name='ID', db_index=True, auto_created=True, blank=True)),
('change_date', models.DateTimeField(verbose_name='change date', editable=False, blank=True)),
('name', models.CharField(max_length=255)),
('template', models.CharField(default=b'', max_length=255, blank=True)),
('support_level', models.CharField(default=b'ud', max_length=2, choices=[(b'fs', 'Fully Supported'), (b'ps', 'Provisionally Supported'), (b'ua', 'Unsupported (Opt-in allowed)'), (b'ud', 'Unsupported (Opt-in disallowed)'), (b'da', 'Disabled')])),
('deprecated', models.BooleanField(default=False, help_text="Only XBlocks listed in a course's Advanced Module List can be flagged as deprecated. Note that deprecation is by XBlock name, and is not specific to template.", verbose_name='show deprecation messaging in Studio')),
('history_id', models.AutoField(serialize=False, primary_key=True)),
('history_date', models.DateTimeField()),
('history_type', models.CharField(max_length=1, choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')])),
('changed_by', models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.DO_NOTHING, db_constraint=False, blank=True, to=settings.AUTH_USER_MODEL, null=True)),
('history_user', models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),
],
options={
'ordering': ('-history_date', '-history_id'),
'get_latest_by': 'history_date',
'verbose_name': 'historical x block config',
},
),
migrations.CreateModel(
name='XBlockConfig',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('change_date', models.DateTimeField(auto_now=True, verbose_name='change date')),
('name', models.CharField(max_length=255)),
('template', models.CharField(default=b'', max_length=255, blank=True)),
('support_level', models.CharField(default=b'ud', max_length=2, choices=[(b'fs', 'Fully Supported'), (b'ps', 'Provisionally Supported'), (b'ua', 'Unsupported (Opt-in allowed)'), (b'ud', 'Unsupported (Opt-in disallowed)'), (b'da', 'Disabled')])),
('deprecated', models.BooleanField(default=False, help_text="Only XBlocks listed in a course's Advanced Module List can be flagged as deprecated. Note that deprecation is by XBlock name, and is not specific to template.", verbose_name='show deprecation messaging in Studio')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='changed by')),
],
),
migrations.CreateModel(
name='XBlockConfigFlag',
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')),
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
],
),
migrations.AlterUniqueTogether(
name='xblockconfig',
unique_together=set([('name', 'template')]),
),
]
...@@ -8,6 +8,10 @@ from django.conf import settings ...@@ -8,6 +8,10 @@ from django.conf import settings
from django.db.models import TextField from django.db.models import TextField
from config_models.models import ConfigurationModel from config_models.models import ConfigurationModel
from django.db import models
from django.contrib.auth.models import User
from simple_history.models import HistoricalRecords
class XBlockDisableConfig(ConfigurationModel): class XBlockDisableConfig(ConfigurationModel):
...@@ -72,3 +76,125 @@ class XBlockDisableConfig(ConfigurationModel): ...@@ -72,3 +76,125 @@ class XBlockDisableConfig(ConfigurationModel):
disabled_xblocks=config.disabled_blocks, disabled_xblocks=config.disabled_blocks,
disabled_create_block_types=config.disabled_create_block_types disabled_create_block_types=config.disabled_create_block_types
) )
class XBlockConfigFlag(ConfigurationModel):
"""
Enables site-wide configuration for xblock support state.
"""
class Meta(object):
app_label = "xblock_django"
def __unicode__(self):
return "[XBlockConfigFlag] enabled={}".format(self.enabled)
class XBlockConfig(models.Model):
"""
Configuration for a specific xblock. Currently used for support state.
"""
FULL_SUPPORT = 'fs'
PROVISIONAL_SUPPORT = 'ps'
UNSUPPORTED_OPT_IN = 'ua'
UNSUPPORTED_NO_OPT_IN = 'ud'
DISABLED = 'da'
SUPPORT_CHOICES = (
(FULL_SUPPORT, _('Fully Supported')),
# May lack robustness, course staff should test.
(PROVISIONAL_SUPPORT, _('Provisionally Supported')),
# Unsupported, may not meet edX standards.
(UNSUPPORTED_OPT_IN, _('Unsupported (Opt-in allowed)')),
# Used when deprecating components, never allowed to create in Studio.
(UNSUPPORTED_NO_OPT_IN, _('Unsupported (Opt-in disallowed)')),
# Will not render in the LMS
(DISABLED, _('Disabled')),
)
# for archiving
history = HistoricalRecords()
change_date = models.DateTimeField(auto_now=True, verbose_name=_("change date"))
changed_by = models.ForeignKey(
User,
editable=False,
null=True,
on_delete=models.PROTECT,
# Translators: this label indicates the name of the user who made this change:
verbose_name=_("changed by"),
)
name = models.CharField(max_length=255, null=False)
template = models.CharField(max_length=255, blank=True, default='')
support_level = models.CharField(max_length=2, choices=SUPPORT_CHOICES, default=UNSUPPORTED_NO_OPT_IN)
deprecated = models.BooleanField(
default=False,
verbose_name=_('show deprecation messaging in Studio'),
help_text=_(
"Only XBlocks listed in a course's Advanced Module List can be flagged as deprecated. "
"Note that deprecation is by XBlock name, and is not specific to template.")
)
class Meta(object):
app_label = "xblock_django"
unique_together = ("name", "template")
@property
def _history_user(self):
""" Show in history the user who made the last change. """
return self.changed_by
@_history_user.setter
def _history_user(self, value):
""" Show in history the user who made the last change. """
self.changed_by = value
@property
def _history_date(self):
""" Show in history the date of the last change. """
return self.change_date
@_history_date.setter
def _history_date(self, value):
""" Show in history the date of the last change. """
self.change_date = value
@classmethod
def deprecated_xblocks(cls):
""" Return the QuerySet of deprecated XBlock types. """
return cls.objects.filter(deprecated=True)
@classmethod
def disabled_xblocks(cls):
""" Return the QuerySet of XBlocks that are disabled. """
return cls.objects.filter(support_level=cls.DISABLED)
@classmethod
def authorable_xblocks(cls, allow_unsupported=False, name=None):
"""
Return the QuerySet of XBlocks that can be created in Studio (by default, only fully supported and
provisionally supported). Note that this method looks only at `support_level` and does not take into
account `deprecated`.
Arguments:
allow_unsupported (bool): If `True`, unsupported XBlocks that are flagged as allowing opt-in
will also be returned. Note that unsupported XBlocks are not recommended for use in courses
due to non-compliance with one or more of the base requirements, such as testing, accessibility,
internationalization, and documentation. Default value is `False`.
name (str): If provided, filters the returned XBlocks to those with the provided name. This is
useful for XBlocks with lots of template types.
Returns:
QuerySet: Authorable XBlocks, taking into account `support_level` and `name` (if specified).
"""
blocks = cls.objects.exclude(support_level=cls.DISABLED).exclude(support_level=cls.UNSUPPORTED_NO_OPT_IN)
if not allow_unsupported:
blocks = blocks.exclude(support_level=cls.UNSUPPORTED_OPT_IN)
if name:
blocks = blocks.filter(name=name)
return blocks
def __unicode__(self):
return (
"[XBlockConfig] '{}': template='{}', support level='{}', deprecated={}"
).format(self.name, self.template, self.support_level, self.deprecated)
...@@ -5,7 +5,7 @@ import ddt ...@@ -5,7 +5,7 @@ import ddt
from mock import patch from mock import patch
from django.test import TestCase from django.test import TestCase
from xblock_django.models import XBlockDisableConfig from xblock_django.models import XBlockDisableConfig, XBlockConfig
@ddt.ddt @ddt.ddt
...@@ -56,3 +56,137 @@ class XBlockDisableConfigTestCase(TestCase): ...@@ -56,3 +56,137 @@ class XBlockDisableConfigTestCase(TestCase):
) )
self.assertEqual(XBlockDisableConfig.disabled_create_block_types(), ['annotatable', 'poll', 'survey']) self.assertEqual(XBlockDisableConfig.disabled_create_block_types(), ['annotatable', 'poll', 'survey'])
class XBlockConfigTestCase(TestCase):
"""
Tests for XBlockConfig.
"""
def tearDown(self):
super(XBlockConfigTestCase, self).tearDown()
XBlockConfig.objects.all().delete()
def test_deprecated_blocks(self):
""" Tests the deprecated_xblocks method """
XBlockConfig.objects.create(
name="poll",
support_level=XBlockConfig.UNSUPPORTED_NO_OPT_IN,
deprecated=True
)
XBlockConfig.objects.create(
name="survey",
support_level=XBlockConfig.DISABLED,
deprecated=True
)
XBlockConfig.objects.create(
name="done",
support_level=XBlockConfig.FULL_SUPPORT
)
deprecated_xblock_names = [block.name for block in XBlockConfig.deprecated_xblocks()]
self.assertEqual(["poll", "survey"], deprecated_xblock_names)
def test_disabled_blocks(self):
""" Tests the disabled_xblocks method """
XBlockConfig.objects.create(
name="poll",
support_level=XBlockConfig.UNSUPPORTED_NO_OPT_IN,
deprecated=True
)
XBlockConfig.objects.create(
name="survey",
support_level=XBlockConfig.DISABLED,
deprecated=True
)
XBlockConfig.objects.create(
name="annotatable",
support_level=XBlockConfig.DISABLED,
deprecated=False
)
XBlockConfig.objects.create(
name="done",
support_level=XBlockConfig.FULL_SUPPORT
)
disabled_xblock_names = [block.name for block in XBlockConfig.disabled_xblocks()]
self.assertEqual(["survey", "annotatable"], disabled_xblock_names)
def test_authorable_blocks(self):
""" Tests the authorable_xblocks method """
XBlockConfig.objects.create(
name="problem",
support_level=XBlockConfig.FULL_SUPPORT
)
XBlockConfig.objects.create(
name="problem",
support_level=XBlockConfig.FULL_SUPPORT,
template="multiple_choice"
)
XBlockConfig.objects.create(
name="problem",
support_level=XBlockConfig.UNSUPPORTED_OPT_IN,
template="circuit_simulator"
)
XBlockConfig.objects.create(
name="html",
support_level=XBlockConfig.PROVISIONAL_SUPPORT,
template="zoom"
)
XBlockConfig.objects.create(
name="split_module",
support_level=XBlockConfig.UNSUPPORTED_OPT_IN,
deprecated=True
)
XBlockConfig.objects.create(
name="poll",
support_level=XBlockConfig.UNSUPPORTED_NO_OPT_IN,
deprecated=True
)
XBlockConfig.objects.create(
name="survey",
support_level=XBlockConfig.DISABLED,
)
authorable_xblock_names = [block.name for block in XBlockConfig.authorable_xblocks()]
self.assertEqual(["problem", "problem", "html"], authorable_xblock_names)
authorable_xblock_names = [block.name for block in XBlockConfig.authorable_xblocks(allow_unsupported=True)]
self.assertEqual(["problem", "problem", "problem", "html", "split_module"], authorable_xblock_names)
authorable_xblocks = XBlockConfig.authorable_xblocks(name="problem", allow_unsupported=True)
self.assertEqual(3, len(authorable_xblocks))
self.assertEqual("problem", authorable_xblocks[0].name)
self.assertEqual("", authorable_xblocks[0].template)
self.assertEqual(XBlockConfig.FULL_SUPPORT, authorable_xblocks[0].support_level)
self.assertEqual("problem", authorable_xblocks[1].name)
self.assertEqual("circuit_simulator", authorable_xblocks[1].template)
self.assertEqual(XBlockConfig.UNSUPPORTED_OPT_IN, authorable_xblocks[1].support_level)
self.assertEqual("problem", authorable_xblocks[2].name)
self.assertEqual("multiple_choice", authorable_xblocks[2].template)
self.assertEqual(XBlockConfig.FULL_SUPPORT, authorable_xblocks[2].support_level)
authorable_xblocks = XBlockConfig.authorable_xblocks(name="html")
self.assertEqual(1, len(authorable_xblocks))
self.assertEqual("html", authorable_xblocks[0].name)
self.assertEqual("zoom", authorable_xblocks[0].template)
authorable_xblocks = XBlockConfig.authorable_xblocks(name="video")
self.assertEqual(0, len(authorable_xblocks))
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