Files
edx-platform/cms/djangoapps/models/settings/course_metadata.py
Se Won Jang 11d260910f Implemented Validation for Course Advanced Setting
This commit adds validation for course advanced settings. Currently when course
administrators make invalid changes in the Settings/Advanced Settings tab,
they're not notified through a new modal window of the list of invalid settings
changes.

* Extending CourseMetadata
    - Previously, we only had update_from_json method in CourseMetadata.py,
      and it was only validating one field every POST request.
    - Now we have validate_and_update_from_json method that encapsulates the
      functionality of update_from_json into a validation call
    - To avoid discrepancy of validation standards between modules, validation
      uses the from_json method implemented to each field in xblock.

* Different Response in advanced settings ajax requests
    - After receiving a POST ajax request, course.py calls
      validate_and_update_from_json, and sends a json object of either:
        1) valid course metadata model
        2) error objects

* Error Messages shown in validation-error-modal
    - error objects passed through ajax are shown in a separate modal.
2014-08-26 11:24:38 -07:00

137 lines
4.9 KiB
Python

from xblock.fields import Scope
from xmodule.modulestore.django import modulestore
from django.utils.translation import ugettext as _
class CourseMetadata(object):
'''
For CRUD operations on metadata fields which do not have specific editors
on the other pages including any user generated ones.
The objects have no predefined attrs but instead are obj encodings of the
editable metadata.
'''
# The list of fields that wouldn't be shown in Advanced Settings.
FILTERED_LIST = ['xml_attributes',
'start',
'end',
'enrollment_start',
'enrollment_end',
'tabs',
'graceperiod',
'checklists',
'show_timezone',
'format',
'graded',
'hide_from_toc',
'pdf_textbooks',
'user_partitions',
'name', # from xblock
'tags', # from xblock
'visible_to_staff_only'
]
@classmethod
def fetch(cls, descriptor):
"""
Fetch the key:value editable course details for the given course from
persistence and return a CourseMetadata model.
"""
result = {}
for field in descriptor.fields.values():
if field.scope != Scope.settings:
continue
if field.name in cls.FILTERED_LIST:
continue
result[field.name] = {
'value': field.read_json(descriptor),
'display_name': _(field.display_name),
'help': _(field.help),
'deprecated': field.runtime_options.get('deprecated', False)
}
return result
@classmethod
def update_from_json(cls, descriptor, jsondict, user, filter_tabs=True):
"""
Decode the json into CourseMetadata and save any changed attrs to the db.
Ensures none of the fields are in the blacklist.
"""
# Copy the filtered list to avoid permanently changing the class attribute.
filtered_list = list(cls.FILTERED_LIST)
# Don't filter on the tab attribute if filter_tabs is False.
if not filter_tabs:
filtered_list.remove("tabs")
# Validate the values before actually setting them.
key_values = {}
for key, model in jsondict.iteritems():
# should it be an error if one of the filtered list items is in the payload?
if key in filtered_list:
continue
try:
val = model['value']
if hasattr(descriptor, key) and getattr(descriptor, key) != val:
key_values[key] = descriptor.fields[key].from_json(val)
except (TypeError, ValueError) as err:
raise ValueError(_("Incorrect format for field '{name}'. {detailed_message}".format(
name=model['display_name'], detailed_message=err.message)))
return cls.update_from_dict(key_values, descriptor, user)
@classmethod
def validate_and_update_from_json(cls, descriptor, jsondict, user, filter_tabs=True):
"""
Validate the values in the json dict (validated by xblock fields from_json method)
If all fields validate, go ahead and update those values in the database.
If not, return the error objects list.
Returns:
did_validate: whether values pass validation or not
errors: list of error objects
result: the updated course metadata or None if error
"""
filtered_list = list(cls.FILTERED_LIST)
if not filter_tabs:
filtered_list.remove("tabs")
filtered_dict = dict((k, v) for k, v in jsondict.iteritems() if k not in filtered_list)
did_validate = True
errors = []
key_values = {}
updated_data = None
for key, model in filtered_dict.iteritems():
try:
val = model['value']
if hasattr(descriptor, key) and getattr(descriptor, key) != val:
key_values[key] = descriptor.fields[key].from_json(val)
except (TypeError, ValueError) as err:
did_validate = False
errors.append({'message': err.message, 'model': model})
# If did validate, go ahead and update the metadata
if did_validate:
updated_data = cls.update_from_dict(key_values, descriptor, user)
return did_validate, errors, updated_data
@classmethod
def update_from_dict(cls, key_values, descriptor, user):
"""
Update metadata descriptor in modulestore from key_values.
"""
for key, value in key_values.iteritems():
setattr(descriptor, key, value)
if len(key_values):
modulestore().update_item(descriptor, user.id)
return cls.fetch(descriptor)