Commit 8ba41635 by Calen Pennington

WIP. Data loads, but not all of it

parent 3d6cbf47
......@@ -499,7 +499,7 @@ def load_preview_module(request, preview_id, descriptor, instance_state, shared_
"""
system = preview_module_system(request, preview_id, descriptor)
try:
module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
module = descriptor.xmodule(system)
except:
module = ErrorDescriptor.from_descriptor(
descriptor,
......
......@@ -83,7 +83,7 @@ class LoncapaProblem(object):
Main class for capa Problems.
'''
def __init__(self, problem_text, id, state=None, seed=None, system=None):
def __init__(self, problem_text, id, correct_map=None, done=None, seed=None, system=None):
'''
Initializes capa Problem.
......@@ -91,7 +91,8 @@ class LoncapaProblem(object):
- problem_text (string): xml defining the problem
- id (string): identifier for this problem; often a filename (no spaces)
- state (dict): student state
- correct_map (dict): data specifying whether the student has completed the problem
- done (bool): Whether the student has answered the problem
- seed (int): random number generator seed (int)
- system (ModuleSystem): ModuleSystem instance which provides OS,
rendering, and user context
......@@ -103,16 +104,11 @@ class LoncapaProblem(object):
self.problem_id = id
self.system = system
self.seed = seed
self.done = done
self.correct_map = CorrectMap()
if state:
if 'seed' in state:
self.seed = state['seed']
if 'student_answers' in state:
self.student_answers = state['student_answers']
if 'correct_map' in state:
self.correct_map.set_dict(state['correct_map'])
if 'done' in state:
self.done = state['done']
if correct_map is not None:
self.correct_map.set_dict(correct_map)
# TODO: Does this deplete the Linux entropy pool? Is this fast enough?
if not self.seed:
......
......@@ -7,6 +7,7 @@ from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.xml_module import XmlDescriptor
from xmodule.exceptions import InvalidDefinitionError
from .model import String, Scope
DEFAULT = "_DEFAULT_GROUP"
......@@ -68,37 +69,7 @@ class ABTestDescriptor(RawDescriptor, XmlDescriptor):
template_dir_name = "abtest"
def __init__(self, system, definition=None, **kwargs):
"""
definition is a dictionary with the following layout:
{'data': {
'experiment': 'the name of the experiment',
'group_portions': {
'group_a': 0.1,
'group_b': 0.2
},
'group_contents': {
'group_a': [
'url://for/content/module/1',
'url://for/content/module/2',
],
'group_b': [
'url://for/content/module/3',
],
DEFAULT: [
'url://for/default/content/1'
]
}
},
'children': [
'url://for/content/module/1',
'url://for/content/module/2',
'url://for/content/module/3',
'url://for/default/content/1',
]}
"""
kwargs['shared_state_key'] = definition['data']['experiment']
RawDescriptor.__init__(self, system, definition, **kwargs)
experiment = String(help="Experiment that this A/B test belongs to", scope=Scope.content)
@classmethod
def definition_from_xml(cls, xml_object, system):
......
......@@ -12,6 +12,9 @@ import requests
import time
import copy
from .model import Scope, ModelType, List, String, Object, Boolean
Date = ModelType
log = logging.getLogger(__name__)
......@@ -21,6 +24,39 @@ edx_xml_parser = etree.XMLParser(dtd_validation=False, load_dtd=False,
class CourseDescriptor(SequenceDescriptor):
module_class = SequenceModule
textbooks = List(help="List of pairs of (title, url) for textbooks used in this course", default=[], scope=Scope.content)
wiki_slug = String(help="Slug that points to the wiki for this course", scope=Scope.content)
enrollment_start = Date(help="Date that enrollment for this class is opened", scope=Scope.settings)
enrollment_end = Date(help="Date that enrollment for this class is closed", scope=Scope.settings)
end = Date(help="Date that this class ends", scope=Scope.settings)
advertised_start = Date(help="Date that this course is advertised to start", scope=Scope.settings)
grading_policy = Object(help="Grading policy definition for this class", scope=Scope.content)
info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
# An extra property is used rather than the wiki_slug/number because
# there are courses that change the number for different runs. This allows
# courses to share the same css_class across runs even if they have
# different numbers.
#
# TODO get rid of this as soon as possible or potentially build in a robust
# way to add in course-specific styling. There needs to be a discussion
# about the right way to do this, but arjun will address this ASAP. Also
# note that the courseware template needs to change when this is removed.
css_class = String(help="DO NOT USE THIS", scope=Scope.settings)
# TODO: This is a quick kludge to allow CS50 (and other courses) to
# specify their own discussion forums as external links by specifying a
# "discussion_link" in their policy JSON file. This should later get
# folded in with Syllabus, Course Info, and additional Custom tabs in a
# more sensible framework later.
discussion_link = String(help="DO NOT USE THIS", scope=Scope.settings)
# TODO: same as above, intended to let internal CS50 hide the progress tab
# until we get grade integration set up.
# Explicit comparison to True because we always want to return a bool.
hide_progress_tab = Boolean(help="DO NOT USE THIS", scope=Scope.settings)
template_dir_name = 'course'
class Textbook:
......@@ -69,10 +105,11 @@ class CourseDescriptor(SequenceDescriptor):
return table_of_contents
def __init__(self, system, definition=None, **kwargs):
super(CourseDescriptor, self).__init__(system, definition, **kwargs)
def __init__(self, *args, **kwargs):
super(CourseDescriptor, self).__init__(*args, **kwargs)
self.textbooks = []
for title, book_url in self.definition['data']['textbooks']:
for title, book_url in self.textbooks:
try:
self.textbooks.append(self.Textbook(title, book_url))
except:
......@@ -81,7 +118,8 @@ class CourseDescriptor(SequenceDescriptor):
log.exception("Couldn't load textbook ({0}, {1})".format(title, book_url))
continue
self.wiki_slug = self.definition['data']['wiki_slug'] or self.location.course
if self.wiki_slug is None:
self.wiki_slug = self.location.course
msg = None
if self.start is None:
......@@ -98,7 +136,7 @@ class CourseDescriptor(SequenceDescriptor):
# disable the syllabus content for courses that do not provide a syllabus
self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))
self.set_grading_policy(self.definition['data'].get('grading_policy', None))
self.set_grading_policy(self.grading_policy)
def defaut_grading_policy(self):
"""
......@@ -203,7 +241,7 @@ class CourseDescriptor(SequenceDescriptor):
# cdodge: import the grading policy information that is on disk and put into the
# descriptor 'definition' bucket as a dictionary so that it is persisted in the DB
instance.definition['data']['grading_policy'] = policy
instance.grading_policy = policy
# now set the current instance. set_grading_policy() will apply some inheritance rules
instance.set_grading_policy(policy)
......@@ -395,38 +433,14 @@ class CourseDescriptor(SequenceDescriptor):
@property
def start_date_text(self):
displayed_start = self._try_parse_time('advertised_start') or self.start
return time.strftime("%b %d, %Y", displayed_start)
return time.strftime("%b %d, %Y", self.advertised_start or self.start)
@property
def end_date_text(self):
return time.strftime("%b %d, %Y", self.end)
# An extra property is used rather than the wiki_slug/number because
# there are courses that change the number for different runs. This allows
# courses to share the same css_class across runs even if they have
# different numbers.
#
# TODO get rid of this as soon as possible or potentially build in a robust
# way to add in course-specific styling. There needs to be a discussion
# about the right way to do this, but arjun will address this ASAP. Also
# note that the courseware template needs to change when this is removed.
@property
def css_class(self):
return self.metadata.get('css_class', '')
@property
def info_sidebar_name(self):
return self.metadata.get('info_sidebar_name', 'Course Handouts')
@property
def discussion_link(self):
"""TODO: This is a quick kludge to allow CS50 (and other courses) to
specify their own discussion forums as external links by specifying a
"discussion_link" in their policy JSON file. This should later get
folded in with Syllabus, Course Info, and additional Custom tabs in a
more sensible framework later."""
return self.metadata.get('discussion_link', None)
@property
def forum_posts_allowed(self):
......@@ -443,12 +457,6 @@ class CourseDescriptor(SequenceDescriptor):
return True
@property
def hide_progress_tab(self):
"""TODO: same as above, intended to let internal CS50 hide the progress tab
until we get grade integration set up."""
# Explicit comparison to True because we always want to return a bool.
return self.metadata.get('hide_progress_tab') == True
@property
def end_of_course_survey_url(self):
......
......@@ -74,12 +74,11 @@ class ErrorDescriptor(JSONEditingDescriptor):
}
# real metadata stays in the content, but add a display name
metadata = {'display_name': 'Error: ' + location.name}
model_data = {'display_name': 'Error: ' + location.name}
return ErrorDescriptor(
system,
definition,
location=location,
metadata=metadata
location,
model_data,
)
def get_context(self):
......
......@@ -21,20 +21,19 @@ class MakoModuleDescriptor(XModuleDescriptor):
the descriptor as the `module` parameter to that template
"""
def __init__(self, system, definition=None, **kwargs):
def __init__(self, system, location, model_data):
if getattr(system, 'render_template', None) is None:
raise TypeError('{system} must have a render_template function'
' in order to use a MakoDescriptor'.format(
system=system))
super(MakoModuleDescriptor, self).__init__(system, definition, **kwargs)
super(MakoModuleDescriptor, self).__init__(system, location, model_data)
def get_context(self):
"""
Return the context to render the mako template with
"""
return {'module': self,
'metadata': self.metadata,
'editable_metadata_fields' : self.editable_metadata_fields
'editable_metadata_fields': self.editable_fields
}
def get_html(self):
......@@ -44,6 +43,6 @@ class MakoModuleDescriptor(XModuleDescriptor):
# cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
@property
def editable_metadata_fields(self):
subset = [name for name in self.metadata.keys() if name not in self.system_metadata_fields]
subset = [field.name for field in self.fields if field.name not in self.system_metadata_fields]
return subset
from collections import namedtuple
class ModuleScope(object):
USAGE, DEFINITION, TYPE, ALL = xrange(4)
class Scope(namedtuple('ScopeBase', 'student module')):
pass
Scope.content = Scope(student=False, module=ModuleScope.DEFINITION)
Scope.student_state = Scope(student=True, module=ModuleScope.USAGE)
Scope.settings = Scope(student=True, module=ModuleScope.USAGE)
Scope.student_preferences = Scope(student=True, module=ModuleScope.TYPE)
Scope.student_info = Scope(student=True, module=ModuleScope.ALL)
class ModelType(object):
sequence = 0
def __init__(self, help=None, default=None, scope=Scope.content):
self._seq = self.sequence
self._name = "unknown"
self.help = help
self.default = default
self.scope = scope
ModelType.sequence += 1
@property
def name(self):
return self._name
def __get__(self, instance, owner):
if instance is None:
return self
return instance._model_data.get(self.name, self.default)
def __set__(self, instance, value):
instance._model_data[self.name] = value
def __delete__(self, instance):
del instance._model_data[self.name]
def __repr__(self):
return "<{0.__class__.__name} {0.__name__}>".format(self)
def __lt__(self, other):
return self._seq < other._seq
Int = Float = Boolean = Object = List = String = Any = ModelType
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
# Find registered methods
reg_methods = {}
for value in attrs.itervalues():
for reg_type, names in getattr(value, "_method_registrations", {}).iteritems():
for n in names:
reg_methods[reg_type + n] = value
attrs['registered_methods'] = reg_methods
if attrs.get('has_children', False):
attrs['children'] = ModelType(help='The children of this XModule', default=[], scope=None)
@property
def child_map(self):
return dict((child.name, child) for child in self.children)
attrs['child_map'] = child_map
fields = []
for n, v in attrs.items():
if isinstance(v, ModelType):
v._name = n
fields.append(v)
fields.sort()
attrs['fields'] = fields
return super(ModelMetaclass, cls).__new__(cls, name, bases, attrs)
......@@ -187,12 +187,13 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
err_msg
)
descriptor.metadata['data_dir'] = course_dir
setattr(descriptor, 'data_dir', course_dir)
xmlstore.modules[course_id][descriptor.location] = descriptor
for child in descriptor.get_children():
parent_tracker.add_parent(child.location, descriptor.location)
if hasattr(descriptor, 'children'):
for child in descriptor.children:
parent_tracker.add_parent(child.location, descriptor.location)
return descriptor
render_template = lambda: ''
......@@ -425,14 +426,14 @@ class XMLModuleStore(ModuleStoreBase):
# breaks metadata inheritance via get_children(). Instead
# (actually, in addition to, for now), we do a final inheritance pass
# after we have the course descriptor.
XModuleDescriptor.compute_inherited_metadata(course_descriptor)
#XModuleDescriptor.compute_inherited_metadata(course_descriptor)
# now import all pieces of course_info which is expected to be stored
# in <content_dir>/info or <content_dir>/info/<url_name>
self.load_extra_content(system, course_descriptor, 'course_info', self.data_dir / course_dir / 'info', course_dir, url_name)
# now import all static tabs which are expected to be stored in
# in <content_dir>/tabs or <content_dir>/tabs/<url_name>
# in <content_dir>/tabs or <content_dir>/tabs/<url_name>
self.load_extra_content(system, course_descriptor, 'static_tab', self.data_dir / course_dir / 'tabs', course_dir, url_name)
self.load_extra_content(system, course_descriptor, 'custom_tag_template', self.data_dir / course_dir / 'custom_tags', course_dir, url_name)
......@@ -444,30 +445,30 @@ class XMLModuleStore(ModuleStoreBase):
def load_extra_content(self, system, course_descriptor, category, base_dir, course_dir, url_name):
if url_name:
path = base_dir / url_name
path = base_dir / url_name
if not os.path.exists(path):
path = base_dir
for filepath in glob.glob(path/ '*'):
for filepath in glob.glob(path / '*'):
with open(filepath) as f:
try:
html = f.read().decode('utf-8')
# tabs are referenced in policy.json through a 'slug' which is just the filename without the .html suffix
slug = os.path.splitext(os.path.basename(filepath))[0]
loc = Location('i4x', course_descriptor.location.org, course_descriptor.location.course, category, slug)
module = HtmlDescriptor(system, definition={'data' : html}, **{'location' : loc})
module = HtmlDescriptor(system, loc, {'data': html})
# VS[compat]:
# Hack because we need to pull in the 'display_name' for static tabs (because we need to edit them)
# from the course policy
if category == "static_tab":
for tab in course_descriptor.tabs or []:
if tab.get('url_slug') == slug:
module.metadata['display_name'] = tab['name']
module.metadata['data_dir'] = course_dir
self.modules[course_descriptor.id][module.location] = module
module.display_name = tab['name']
module.data_dir = course_dir
self.modules[course_descriptor.id][module.location] = module
except Exception, e:
logging.exception("Failed to load {0}. Skipping... Exception: {1}".format(filepath, str(e)))
logging.exception("Failed to load {0}. Skipping... Exception: {1}".format(filepath, str(e)))
system.error_tracker("ERROR: " + str(e))
def get_instance(self, course_id, location, depth=0):
......
......@@ -67,10 +67,6 @@ class CustomTagDescriptor(RawDescriptor):
return template.render(**params)
def __init__(self, system, definition, **kwargs):
'''Render and save the template for this descriptor instance'''
super(CustomTagDescriptor, self).__init__(system, definition, **kwargs)
@property
def rendered_html(self):
return self.render_template(self.system, self.definition['data'])
......
......@@ -287,9 +287,9 @@ class XmlDescriptor(XModuleDescriptor):
filepath = cls._format_filepath(xml_object.tag, name_to_pathname(url_name))
definition_xml = cls.load_file(filepath, system.resources_fs, location)
else:
definition_xml = xml_object # this is just a pointer, not the real definition content
definition_xml = xml_object # this is just a pointer, not the real definition content
definition = cls.load_definition(definition_xml, system, location) # note this removes metadata
definition = cls.load_definition(definition_xml, system, location) # note this removes metadata
# VS[compat] -- make Ike's github preview links work in both old and
# new file layouts
if is_pointer_tag(xml_object):
......@@ -299,13 +299,13 @@ class XmlDescriptor(XModuleDescriptor):
metadata = cls.load_metadata(definition_xml)
# move definition metadata into dict
dmdata = definition.get('definition_metadata','')
dmdata = definition.get('definition_metadata', '')
if dmdata:
metadata['definition_metadata_raw'] = dmdata
try:
metadata.update(json.loads(dmdata))
except Exception as err:
log.debug('Error %s in loading metadata %s' % (err,dmdata))
log.debug('Error %s in loading metadata %s' % (err, dmdata))
metadata['definition_metadata_err'] = str(err)
# Set/override any metadata specified by policy
......@@ -313,11 +313,14 @@ class XmlDescriptor(XModuleDescriptor):
if k in system.policy:
cls.apply_policy(metadata, system.policy[k])
model_data = {}
model_data.update(metadata)
model_data.update(definition)
return cls(
system,
definition,
location=location,
metadata=metadata,
location,
model_data,
)
@classmethod
......
function github_status {
gcli status create mitx mitx $GIT_COMMIT \
--params=$1 \
target_url:$BUILD_URL \
description:"Build #$BUILD_NUMBER is running" \
-f csv
}
function github_mark_failed_on_exit {
trap '[ $? == "0" ] || github_status state:failed' EXIT
}
\ No newline at end of file
......@@ -85,7 +85,7 @@ def course_image_url(course):
"""Try to look up the image url for the course. If it's not found,
log an error and return the dead link"""
if isinstance(modulestore(), XMLModuleStore):
path = course.metadata['data_dir'] + "/images/course_image.jpg"
path = course.data_dir + "/images/course_image.jpg"
return try_staticfiles_lookup(path)
else:
loc = course.location._replace(tag='c4x', category='asset', name='images_course_image.jpg')
......@@ -162,7 +162,9 @@ def get_course_about_section(course, section_key):
key=section_key, url=course.location.url()))
return None
elif section_key == "title":
return course.metadata.get('display_name', course.url_name)
if course.display_name is None:
return course.url_name
return course.display_name
elif section_key == "university":
return course.location.org
elif section_key == "number":
......@@ -220,7 +222,7 @@ def get_course_syllabus_section(course, section_key):
filepath = find_file(fs, dirs, section_key + ".html")
with fs.open(filepath) as htmlFile:
return replace_urls(htmlFile.read().decode('utf-8'),
course.metadata['data_dir'], course_namespace=course.location)
course.data_dir, course_namespace=course.location)
except ResourceNotFoundError:
log.exception("Missing syllabus section {key} in course {url}".format(
key=section_key, url=course.location.url()))
......
......@@ -245,7 +245,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
make_psychometrics_data_update_handler(instance_module))
try:
module = descriptor.xmodule_constructor(system)(instance_state, shared_state)
module = descriptor.xmodule(system)
except:
log.exception("Error creating module from descriptor {0}".format(descriptor))
......@@ -259,7 +259,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
error_msg=exc_info_to_str(sys.exc_info()))
# Make an error module
return err_descriptor.xmodule_constructor(system)(None, None)
return err_descriptor.xmodule(system)
_get_html = module.get_html
......
......@@ -40,7 +40,7 @@ end
def django_admin(system, env, command, *args)
django_admin = ENV['DJANGO_ADMIN_PATH'] || select_executable('django-admin.py', 'django-admin')
return "#{django_admin} #{command} --settings=#{system}.envs.#{env} --pythonpath=. #{args.join(' ')}"
return "#{django_admin} #{command} --traceback --settings=#{system}.envs.#{env} --pythonpath=. #{args.join(' ')}"
end
def django_for_jasmine(system, django_reload)
......
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