Merge branch 'master' into feature/abarrett/lms-notes-app
This commit is contained in:
@@ -8,15 +8,42 @@ import urllib
|
||||
|
||||
|
||||
def fasthash(string):
|
||||
m = hashlib.new("md4")
|
||||
m.update(string)
|
||||
return m.hexdigest()
|
||||
"""
|
||||
Hashes `string` into a string representation of a 128-bit digest.
|
||||
"""
|
||||
md4 = hashlib.new("md4")
|
||||
md4.update(string)
|
||||
return md4.hexdigest()
|
||||
|
||||
|
||||
def cleaned_string(val):
|
||||
"""
|
||||
Converts `val` to unicode and URL-encodes special characters
|
||||
(including quotes and spaces)
|
||||
"""
|
||||
return urllib.quote_plus(smart_str(val))
|
||||
|
||||
|
||||
def safe_key(key, key_prefix, version):
|
||||
safe_key = urllib.quote_plus(smart_str(key))
|
||||
"""
|
||||
Given a `key`, `key_prefix`, and `version`,
|
||||
return a key that is safe to use with memcache.
|
||||
|
||||
if len(safe_key) > 250:
|
||||
safe_key = fasthash(safe_key)
|
||||
`key`, `key_prefix`, and `version` can be numbers, strings, or unicode.
|
||||
"""
|
||||
|
||||
return ":".join([key_prefix, str(version), safe_key])
|
||||
# Clean for whitespace and control characters, which
|
||||
# cause memcache to raise an exception
|
||||
key = cleaned_string(key)
|
||||
key_prefix = cleaned_string(key_prefix)
|
||||
version = cleaned_string(version)
|
||||
|
||||
# Attempt to combine the prefix, version, and key
|
||||
combined = ":".join([key_prefix, version, key])
|
||||
|
||||
# If the total length is too long for memcache, hash it
|
||||
if len(combined) > 250:
|
||||
combined = fasthash(combined)
|
||||
|
||||
# Return the result
|
||||
return combined
|
||||
|
||||
1
common/djangoapps/util/tests/__init__.py
Normal file
1
common/djangoapps/util/tests/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
124
common/djangoapps/util/tests/test_memcache.py
Normal file
124
common/djangoapps/util/tests/test_memcache.py
Normal file
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
Tests for memcache in util app
|
||||
"""
|
||||
|
||||
from django.test import TestCase
|
||||
from django.core.cache import get_cache
|
||||
from django.conf import settings
|
||||
from util.memcache import safe_key
|
||||
|
||||
|
||||
class MemcacheTest(TestCase):
|
||||
"""
|
||||
Test memcache key cleanup
|
||||
"""
|
||||
|
||||
# Test whitespace, control characters, and some non-ASCII UTF-16
|
||||
UNICODE_CHAR_CODES = ([c for c in range(0, 30)] + [127] +
|
||||
[129, 500, 2 ** 8 - 1, 2 ** 8 + 1, 2 ** 16 - 1])
|
||||
|
||||
def setUp(self):
|
||||
self.cache = get_cache('default')
|
||||
|
||||
def test_safe_key(self):
|
||||
key = safe_key('test', 'prefix', 'version')
|
||||
self.assertEqual(key, 'prefix:version:test')
|
||||
|
||||
def test_numeric_inputs(self):
|
||||
|
||||
# Numeric key
|
||||
self.assertEqual(safe_key(1, 'prefix', 'version'), 'prefix:version:1')
|
||||
|
||||
# Numeric prefix
|
||||
self.assertEqual(safe_key('test', 5, 'version'), '5:version:test')
|
||||
|
||||
# Numeric version
|
||||
self.assertEqual(safe_key('test', 'prefix', 5), 'prefix:5:test')
|
||||
|
||||
def test_safe_key_long(self):
|
||||
|
||||
# Choose lengths close to memcached's cutoff (250)
|
||||
for length in [248, 249, 250, 251, 252]:
|
||||
|
||||
# Generate a key of that length
|
||||
key = 'a' * length
|
||||
|
||||
# Make the key safe
|
||||
key = safe_key(key, '', '')
|
||||
|
||||
# The key should now be valid
|
||||
self.assertTrue(self._is_valid_key(key),
|
||||
msg="Failed for key length {0}".format(length))
|
||||
|
||||
def test_long_key_prefix_version(self):
|
||||
|
||||
# Long key
|
||||
key = safe_key('a' * 300, 'prefix', 'version')
|
||||
self.assertTrue(self._is_valid_key(key))
|
||||
|
||||
# Long prefix
|
||||
key = safe_key('key', 'a' * 300, 'version')
|
||||
self.assertTrue(self._is_valid_key(key))
|
||||
|
||||
# Long version
|
||||
key = safe_key('key', 'prefix', 'a' * 300)
|
||||
self.assertTrue(self._is_valid_key(key))
|
||||
|
||||
def test_safe_key_unicode(self):
|
||||
|
||||
for unicode_char in self.UNICODE_CHAR_CODES:
|
||||
|
||||
# Generate a key with that character
|
||||
key = unichr(unicode_char)
|
||||
|
||||
# Make the key safe
|
||||
key = safe_key(key, '', '')
|
||||
|
||||
# The key should now be valid
|
||||
self.assertTrue(self._is_valid_key(key),
|
||||
msg="Failed for unicode character {0}".format(unicode_char))
|
||||
|
||||
def test_safe_key_prefix_unicode(self):
|
||||
|
||||
for unicode_char in self.UNICODE_CHAR_CODES:
|
||||
|
||||
# Generate a prefix with that character
|
||||
prefix = unichr(unicode_char)
|
||||
|
||||
# Make the key safe
|
||||
key = safe_key('test', prefix, '')
|
||||
|
||||
# The key should now be valid
|
||||
self.assertTrue(self._is_valid_key(key),
|
||||
msg="Failed for unicode character {0}".format(unicode_char))
|
||||
|
||||
def test_safe_key_version_unicode(self):
|
||||
|
||||
for unicode_char in self.UNICODE_CHAR_CODES:
|
||||
|
||||
# Generate a version with that character
|
||||
version = unichr(unicode_char)
|
||||
|
||||
# Make the key safe
|
||||
key = safe_key('test', '', version)
|
||||
|
||||
# The key should now be valid
|
||||
self.assertTrue(self._is_valid_key(key),
|
||||
msg="Failed for unicode character {0}".format(unicode_char))
|
||||
|
||||
def _is_valid_key(self, key):
|
||||
"""
|
||||
Test that a key is memcache-compatible.
|
||||
Based on Django's validator in core.cache.backends.base
|
||||
"""
|
||||
|
||||
# Check the length
|
||||
if len(key) > 250:
|
||||
return False
|
||||
|
||||
# Check that there are no spaces or control characters
|
||||
for char in key:
|
||||
if ord(char) < 33 or ord(char) == 127:
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Tests for the util package"""
|
||||
"""Tests for the Zendesk"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
@@ -49,7 +49,10 @@ class _ZendeskApi(object):
|
||||
settings.ZENDESK_USER,
|
||||
settings.ZENDESK_API_KEY,
|
||||
use_api_token=True,
|
||||
api_version=2
|
||||
api_version=2,
|
||||
# As of 2012-05-08, Zendesk is using a CA that is not
|
||||
# installed on our servers
|
||||
client_args={"disable_ssl_certificate_validation": True}
|
||||
)
|
||||
|
||||
def create_ticket(self, ticket):
|
||||
|
||||
@@ -203,9 +203,7 @@ class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
|
||||
|
||||
def save_instance_data(self):
|
||||
for attribute in self.student_attributes:
|
||||
child_attr = getattr(self.child_module, attribute)
|
||||
if child_attr != getattr(self, attribute):
|
||||
setattr(self, attribute, getattr(self.child_module, attribute))
|
||||
setattr(self, attribute, getattr(self.child_module, attribute))
|
||||
|
||||
|
||||
class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
|
||||
|
||||
@@ -8,20 +8,23 @@ class @PeerGrading
|
||||
@use_single_location = @peer_grading_container.data('use-single-location')
|
||||
@peer_grading_outer_container = $('.peer-grading-container')
|
||||
@ajax_url = @peer_grading_container.data('ajax-url')
|
||||
@error_container = $('.error-container')
|
||||
@error_container.toggle(not @error_container.is(':empty'))
|
||||
|
||||
@message_container = $('.message-container')
|
||||
@message_container.toggle(not @message_container.is(':empty'))
|
||||
|
||||
@problem_button = $('.problem-button')
|
||||
@problem_button.click @show_results
|
||||
|
||||
@problem_list = $('.problem-list')
|
||||
@construct_progress_bar()
|
||||
|
||||
if @use_single_location
|
||||
if @use_single_location.toLowerCase() == "true"
|
||||
#If the peer grading element is linked to a single location, then activate the backend for that location
|
||||
@activate_problem()
|
||||
else
|
||||
#Otherwise, activate the panel view.
|
||||
@error_container = $('.error-container')
|
||||
@error_container.toggle(not @error_container.is(':empty'))
|
||||
|
||||
@message_container = $('.message-container')
|
||||
@message_container.toggle(not @message_container.is(':empty'))
|
||||
|
||||
@problem_button = $('.problem-button')
|
||||
@problem_button.click @show_results
|
||||
|
||||
@problem_list = $('.problem-list')
|
||||
@construct_progress_bar()
|
||||
|
||||
construct_progress_bar: () =>
|
||||
problems = @problem_list.find('tr').next()
|
||||
|
||||
@@ -31,15 +31,22 @@ def inherit_metadata(descriptor, model_data):
|
||||
Only metadata specified in self.inheritable_metadata will
|
||||
be inherited
|
||||
"""
|
||||
# The inherited values that are actually being used.
|
||||
if not hasattr(descriptor, '_inherited_metadata'):
|
||||
setattr(descriptor, '_inherited_metadata', {})
|
||||
|
||||
# All inheritable metadata values (for which a value exists in model_data).
|
||||
if not hasattr(descriptor, '_inheritable_metadata'):
|
||||
setattr(descriptor, '_inheritable_metadata', {})
|
||||
|
||||
# Set all inheritable metadata from kwargs that are
|
||||
# in self.inheritable_metadata and aren't already set in metadata
|
||||
for attr in INHERITABLE_METADATA:
|
||||
if attr not in descriptor._model_data and attr in model_data:
|
||||
descriptor._inherited_metadata[attr] = model_data[attr]
|
||||
descriptor._model_data[attr] = model_data[attr]
|
||||
if attr in model_data:
|
||||
descriptor._inheritable_metadata[attr] = model_data[attr]
|
||||
if attr not in descriptor._model_data:
|
||||
descriptor._inherited_metadata[attr] = model_data[attr]
|
||||
descriptor._model_data[attr] = model_data[attr]
|
||||
|
||||
|
||||
def own_metadata(module):
|
||||
|
||||
@@ -11,7 +11,7 @@ from xmodule.raw_module import RawDescriptor
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from .timeinfo import TimeInfo
|
||||
from xblock.core import Object, Integer, Boolean, String, Scope
|
||||
from xmodule.fields import Date, StringyFloat
|
||||
from xmodule.fields import Date, StringyFloat, StringyInteger, StringyBoolean
|
||||
|
||||
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError, MockPeerGradingService
|
||||
from open_ended_grading_classes import combined_open_ended_rubric
|
||||
@@ -28,14 +28,14 @@ EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please
|
||||
|
||||
|
||||
class PeerGradingFields(object):
|
||||
use_for_single_location = Boolean(help="Whether to use this for a single location or as a panel.",
|
||||
use_for_single_location = StringyBoolean(help="Whether to use this for a single location or as a panel.",
|
||||
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
|
||||
link_to_location = String(help="The location this problem is linked to.", default=LINK_TO_LOCATION,
|
||||
scope=Scope.settings)
|
||||
is_graded = Boolean(help="Whether or not this module is scored.", default=IS_GRADED, scope=Scope.settings)
|
||||
is_graded = StringyBoolean(help="Whether or not this module is scored.", default=IS_GRADED, scope=Scope.settings)
|
||||
due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings)
|
||||
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
|
||||
max_grade = Integer(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE,
|
||||
max_grade = StringyInteger(help="The maximum grade that a student can receieve for this problem.", default=MAX_SCORE,
|
||||
scope=Scope.settings)
|
||||
student_data_for_location = Object(help="Student data for a given peer grading problem.",
|
||||
scope=Scope.user_state)
|
||||
@@ -93,9 +93,9 @@ class PeerGradingModule(PeerGradingFields, XModule):
|
||||
if not self.ajax_url.endswith("/"):
|
||||
self.ajax_url = self.ajax_url + "/"
|
||||
|
||||
if not isinstance(self.max_grade, (int, long)):
|
||||
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack
|
||||
self.max_grade = int(self.max_grade)
|
||||
#StringyInteger could return None, so keep this check.
|
||||
if not isinstance(self.max_grade, int):
|
||||
raise TypeError("max_grade needs to be an integer.")
|
||||
|
||||
def closed(self):
|
||||
return self._closed(self.timeinfo)
|
||||
|
||||
@@ -151,6 +151,10 @@ class ImportTestCase(BaseCourseTestCase):
|
||||
# Check that the child inherits due correctly
|
||||
child = descriptor.get_children()[0]
|
||||
self.assertEqual(child.lms.due, Date().from_json(v))
|
||||
self.assertEqual(child._inheritable_metadata, child._inherited_metadata)
|
||||
self.assertEqual(2, len(child._inherited_metadata))
|
||||
self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start'])
|
||||
self.assertEqual(v, child._inherited_metadata['due'])
|
||||
|
||||
# Now export and check things
|
||||
resource_fs = MemoryFS()
|
||||
@@ -184,6 +188,60 @@ class ImportTestCase(BaseCourseTestCase):
|
||||
self.assertEqual(chapter_xml.tag, 'chapter')
|
||||
self.assertFalse('due' in chapter_xml.attrib)
|
||||
|
||||
def test_metadata_no_inheritance(self):
|
||||
"""
|
||||
Checks that default value of None (for due) does not get marked as inherited.
|
||||
"""
|
||||
system = self.get_system()
|
||||
url_name = 'test1'
|
||||
start_xml = '''
|
||||
<course org="{org}" course="{course}"
|
||||
url_name="{url_name}" unicorn="purple">
|
||||
<chapter url="hi" url_name="ch" display_name="CH">
|
||||
<html url_name="h" display_name="H">Two houses, ...</html>
|
||||
</chapter>
|
||||
</course>'''.format(org=ORG, course=COURSE, url_name=url_name)
|
||||
descriptor = system.process_xml(start_xml)
|
||||
compute_inherited_metadata(descriptor)
|
||||
|
||||
self.assertEqual(descriptor.lms.due, None)
|
||||
|
||||
# Check that the child does not inherit a value for due
|
||||
child = descriptor.get_children()[0]
|
||||
self.assertEqual(child.lms.due, None)
|
||||
self.assertEqual(child._inheritable_metadata, child._inherited_metadata)
|
||||
self.assertEqual(1, len(child._inherited_metadata))
|
||||
self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start'])
|
||||
|
||||
def test_metadata_override_default(self):
|
||||
"""
|
||||
Checks that due date can be overriden at child level.
|
||||
"""
|
||||
system = self.get_system()
|
||||
course_due = 'March 20 17:00'
|
||||
child_due = 'April 10 00:00'
|
||||
url_name = 'test1'
|
||||
start_xml = '''
|
||||
<course org="{org}" course="{course}"
|
||||
due="{due}" url_name="{url_name}" unicorn="purple">
|
||||
<chapter url="hi" url_name="ch" display_name="CH">
|
||||
<html url_name="h" display_name="H">Two houses, ...</html>
|
||||
</chapter>
|
||||
</course>'''.format(due=course_due, org=ORG, course=COURSE, url_name=url_name)
|
||||
descriptor = system.process_xml(start_xml)
|
||||
child = descriptor.get_children()[0]
|
||||
child._model_data['due'] = child_due
|
||||
compute_inherited_metadata(descriptor)
|
||||
|
||||
self.assertEqual(descriptor.lms.due, Date().from_json(course_due))
|
||||
self.assertEqual(child.lms.due, Date().from_json(child_due))
|
||||
# Test inherited metadata. Due does not appear here (because explicitly set on child).
|
||||
self.assertEqual(1, len(child._inherited_metadata))
|
||||
self.assertEqual('1970-01-01T00:00:00Z', child._inherited_metadata['start'])
|
||||
# Test inheritable metadata. This has the course inheritable value for due.
|
||||
self.assertEqual(2, len(child._inheritable_metadata))
|
||||
self.assertEqual(course_due, child._inheritable_metadata['due'])
|
||||
|
||||
def test_is_pointer_tag(self):
|
||||
"""
|
||||
Check that is_pointer_tag works properly.
|
||||
|
||||
@@ -9,13 +9,13 @@ from mock import Mock
|
||||
|
||||
class TestFields(object):
|
||||
# Will be returned by editable_metadata_fields.
|
||||
max_attempts = StringyInteger(scope=Scope.settings)
|
||||
max_attempts = StringyInteger(scope=Scope.settings, default=1000)
|
||||
# Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
|
||||
due = Date(scope=Scope.settings)
|
||||
# Will not be returned by editable_metadata_fields because is not Scope.settings.
|
||||
student_answers = Object(scope=Scope.user_state)
|
||||
# Will be returned, and can override the inherited value from XModule.
|
||||
display_name = String(scope=Scope.settings)
|
||||
display_name = String(scope=Scope.settings, default='local default')
|
||||
|
||||
|
||||
class EditableMetadataFieldsTest(unittest.TestCase):
|
||||
@@ -25,27 +25,45 @@ class EditableMetadataFieldsTest(unittest.TestCase):
|
||||
# Tests that the xblock fields (currently tags and name) get filtered out.
|
||||
# Also tests that xml_attributes is filtered out of XmlDescriptor.
|
||||
self.assertEqual(1, len(editable_fields), "Expected only 1 editable field for xml descriptor.")
|
||||
self.assert_display_name_default(editable_fields)
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
|
||||
explicitly_set=False, inheritable=False, value=None, default_value=None)
|
||||
|
||||
def test_override_default(self):
|
||||
# Tests that is_default is correct when a value overrides the default.
|
||||
# Tests that explicitly_set is correct when a value overrides the default (not inheritable).
|
||||
editable_fields = self.get_xml_editable_fields({'display_name': 'foo'})
|
||||
display_name = editable_fields['display_name']
|
||||
self.assertFalse(display_name['is_default'])
|
||||
self.assertEqual('foo', display_name['value'])
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
|
||||
explicitly_set=True, inheritable=False, value='foo', default_value=None)
|
||||
|
||||
def test_additional_field(self):
|
||||
editable_fields = self.get_module_editable_fields({'max_attempts' : '7'})
|
||||
descriptor = self.get_descriptor({'max_attempts' : '7'})
|
||||
editable_fields = descriptor.editable_metadata_fields
|
||||
self.assertEqual(2, len(editable_fields))
|
||||
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts, False, False, 7)
|
||||
self.assert_display_name_default(editable_fields)
|
||||
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts,
|
||||
explicitly_set=True, inheritable=False, value=7, default_value=1000)
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
|
||||
explicitly_set=False, inheritable=False, value='local default', default_value='local default')
|
||||
|
||||
editable_fields = self.get_module_editable_fields({})
|
||||
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts, True, False, None)
|
||||
editable_fields = self.get_descriptor({}).editable_metadata_fields
|
||||
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts,
|
||||
explicitly_set=False, inheritable=False, value=1000, default_value=1000)
|
||||
|
||||
def test_inherited_field(self):
|
||||
editable_fields = self.get_module_editable_fields({'display_name' : 'inherited'})
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name, False, True, 'inherited')
|
||||
model_val = {'display_name' : 'inherited'}
|
||||
descriptor = self.get_descriptor(model_val)
|
||||
# Mimic an inherited value for display_name (inherited and inheritable are the same in this case).
|
||||
descriptor._inherited_metadata = model_val
|
||||
descriptor._inheritable_metadata = model_val
|
||||
editable_fields = descriptor.editable_metadata_fields
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
|
||||
explicitly_set=False, inheritable=True, value='inherited', default_value='inherited')
|
||||
|
||||
descriptor = self.get_descriptor({'display_name' : 'explicit'})
|
||||
# Mimic the case where display_name WOULD have been inherited, except we explicitly set it.
|
||||
descriptor._inheritable_metadata = {'display_name' : 'inheritable value'}
|
||||
descriptor._inherited_metadata = {}
|
||||
editable_fields = descriptor.editable_metadata_fields
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
|
||||
explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value')
|
||||
|
||||
# Start of helper methods
|
||||
def get_xml_editable_fields(self, model_data):
|
||||
@@ -53,7 +71,7 @@ class EditableMetadataFieldsTest(unittest.TestCase):
|
||||
system.render_template = Mock(return_value="<div>Test Template HTML</div>")
|
||||
return XmlDescriptor(system=system, location=None, model_data=model_data).editable_metadata_fields
|
||||
|
||||
def get_module_editable_fields(self, model_data):
|
||||
def get_descriptor(self, model_data):
|
||||
class TestModuleDescriptor(TestFields, XmlDescriptor):
|
||||
|
||||
@property
|
||||
@@ -64,16 +82,12 @@ class EditableMetadataFieldsTest(unittest.TestCase):
|
||||
|
||||
system = test_system()
|
||||
system.render_template = Mock(return_value="<div>Test Template HTML</div>")
|
||||
descriptor = TestModuleDescriptor(system=system, location=None, model_data=model_data)
|
||||
descriptor._inherited_metadata = {'display_name' : 'inherited'}
|
||||
return descriptor.editable_metadata_fields
|
||||
return TestModuleDescriptor(system=system, location=None, model_data=model_data)
|
||||
|
||||
def assert_display_name_default(self, editable_fields):
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name, True, False, None)
|
||||
|
||||
def assert_field_values(self, editable_fields, name, field, is_default, is_inherited, value):
|
||||
def assert_field_values(self, editable_fields, name, field, explicitly_set, inheritable, value, default_value):
|
||||
test_field = editable_fields[name]
|
||||
self.assertEqual(field, test_field['field'])
|
||||
self.assertEqual(is_default, test_field['is_default'])
|
||||
self.assertEqual(is_inherited, test_field['is_inherited'])
|
||||
self.assertEqual(explicitly_set, test_field['explicitly_set'])
|
||||
self.assertEqual(inheritable, test_field['inheritable'])
|
||||
self.assertEqual(value, test_field['value'])
|
||||
self.assertEqual(default_value, test_field['default_value'])
|
||||
|
||||
@@ -624,27 +624,28 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
|
||||
Can be limited by extending `non_editable_metadata_fields`.
|
||||
"""
|
||||
inherited_metadata = getattr(self, '_inherited_metadata', {})
|
||||
inheritable_metadata = getattr(self, '_inheritable_metadata', {})
|
||||
metadata = {}
|
||||
for field in self.fields:
|
||||
|
||||
if field.scope != Scope.settings or field in self.non_editable_metadata_fields:
|
||||
continue
|
||||
|
||||
inherited = False
|
||||
default = False
|
||||
inheritable = False
|
||||
value = getattr(self, field.name)
|
||||
if field.name in self._model_data:
|
||||
default = False
|
||||
default_value = field.default
|
||||
explicitly_set = field.name in self._model_data
|
||||
if field.name in inheritable_metadata:
|
||||
inheritable = True
|
||||
default_value = field.from_json(inheritable_metadata.get(field.name))
|
||||
if field.name in inherited_metadata:
|
||||
if self._model_data.get(field.name) == inherited_metadata.get(field.name):
|
||||
inherited = True
|
||||
else:
|
||||
default = True
|
||||
explicitly_set = False
|
||||
|
||||
metadata[field.name] = {'field': field,
|
||||
'value': value,
|
||||
'is_inherited': inherited,
|
||||
'is_default': default}
|
||||
'default_value': default_value,
|
||||
'inheritable': inheritable,
|
||||
'explicitly_set': explicitly_set }
|
||||
|
||||
return metadata
|
||||
|
||||
|
||||
Reference in New Issue
Block a user