From 32a5fc0e5459f891ac1c52c7fe60b349b5c52329 Mon Sep 17 00:00:00 2001
From: Chris Rossi
Date: Wed, 8 Jan 2014 15:39:35 -0500
Subject: [PATCH] Make problem types pluggable.
This patch adds the ability for arbitrary packages to register
themselves with xmodule.x_module.ResourceTemplates as being providers of
boiler plate templates used for defining the types of problems
instructors can add to a course. This allows third party add-ons to
define new problem types that can then be available to instructors to
use.
This patch is the result of discussion with Ned Batchelder on the right
way to approach this problem. The solution provided here is,
admittedly, a little bit of a hack. But the focus was on making a least
invasive fix to make something work, in anticipation that when problems
get moved into the newere XBlock architecture something a little nicer
will be in place.
---
.../tests/templates/test/announcement.yaml | 23 ++++++++
.../tests/templates/test/anon_user_id.yaml | 19 +++++++
.../tests/templates/test/latex_html.yaml | 21 +++++++
.../tests/templates/test/somefile.notyaml | 0
.../tests/templates/test/zooming_image.yaml | 26 +++++++++
.../xmodule/tests/test_resource_templates.py | 56 +++++++++++++++++++
common/lib/xmodule/xmodule/x_module.py | 44 +++++++++------
7 files changed, 171 insertions(+), 18 deletions(-)
create mode 100644 common/lib/xmodule/xmodule/tests/templates/test/announcement.yaml
create mode 100644 common/lib/xmodule/xmodule/tests/templates/test/anon_user_id.yaml
create mode 100644 common/lib/xmodule/xmodule/tests/templates/test/latex_html.yaml
create mode 100644 common/lib/xmodule/xmodule/tests/templates/test/somefile.notyaml
create mode 100644 common/lib/xmodule/xmodule/tests/templates/test/zooming_image.yaml
create mode 100644 common/lib/xmodule/xmodule/tests/test_resource_templates.py
diff --git a/common/lib/xmodule/xmodule/tests/templates/test/announcement.yaml b/common/lib/xmodule/xmodule/tests/templates/test/announcement.yaml
new file mode 100644
index 0000000000..25855c013a
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/templates/test/announcement.yaml
@@ -0,0 +1,23 @@
+---
+metadata:
+ display_name: Announcement
+data: |
+
+ -
+
September 21
+
+
+ Words of encouragement! This is a short note that most students will read.
+ Anant Agarwal (6.002x Principal Instructor)
+
+ Primary versus Secondary Updates:
Unfortunately, the internet throws a lot of text at students, and they
+ do not read everything that they are given. However, many students do read all that they are
+ given, and so detailed explainations in this section will benefit the most concientious.
+ Any essential information should be extremely quickly summarized in the primary section for skimmers.
+ Star Forum Poster
+ Students appreciate knowing that the course staff is reading what they post, and one of several ways
+ that you can do this is by acknowledging the star posters in your announcements.
+
+
+
+
diff --git a/common/lib/xmodule/xmodule/tests/templates/test/anon_user_id.yaml b/common/lib/xmodule/xmodule/tests/templates/test/anon_user_id.yaml
new file mode 100644
index 0000000000..cfba26a6b4
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/templates/test/anon_user_id.yaml
@@ -0,0 +1,19 @@
+---
+metadata:
+ display_name: Anonymous User ID
+data: |
+ Your explanatory text here.
+ YOUR_LINK_TEXT
+
+
diff --git a/common/lib/xmodule/xmodule/tests/templates/test/latex_html.yaml b/common/lib/xmodule/xmodule/tests/templates/test/latex_html.yaml
new file mode 100644
index 0000000000..9163b3e8b9
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/templates/test/latex_html.yaml
@@ -0,0 +1,21 @@
+---
+metadata:
+ display_name: E-text Written in LaTeX
+ source_code: |
+ \subsection{Example of E-text in LaTeX}
+
+ It is very convenient to write complex equations in LaTeX.
+
+ \begin{equation}
+ x = \frac{-b\pm\sqrt{b^2-4*a*c}}{2a}
+ \end{equation}
+
+ Seize the moment.
+
+data: |
+
+ Example: E-text page
+
+ It is very convenient to write complex equations in LaTeX.
+
+
diff --git a/common/lib/xmodule/xmodule/tests/templates/test/somefile.notyaml b/common/lib/xmodule/xmodule/tests/templates/test/somefile.notyaml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/lib/xmodule/xmodule/tests/templates/test/zooming_image.yaml b/common/lib/xmodule/xmodule/tests/templates/test/zooming_image.yaml
new file mode 100644
index 0000000000..dc79c75fff
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/templates/test/zooming_image.yaml
@@ -0,0 +1,26 @@
+---
+metadata:
+ display_name: Zooming Image
+data: |
+ ZOOMING DIAGRAMS
+ Some edX classes use extremely large, extremely detailed graphics. To make it easier to understand we can offer two versions of those graphics, with the zoomed section showing when you click on the main view.
+ The example below is from 7.00x: Introduction to Biology and shows a subset of the biochemical reactions that cells carry out.
+ You can view the chemical structures of the molecules by clicking on them. The magnified view also lists the enzymes involved in each step.
+
+
+
+
+
diff --git a/common/lib/xmodule/xmodule/tests/test_resource_templates.py b/common/lib/xmodule/xmodule/tests/test_resource_templates.py
new file mode 100644
index 0000000000..80d21d98ac
--- /dev/null
+++ b/common/lib/xmodule/xmodule/tests/test_resource_templates.py
@@ -0,0 +1,56 @@
+"""
+Tests for xmodule.x_module.ResourceTemplates
+"""
+import unittest
+
+from xmodule.x_module import ResourceTemplates
+
+
+class ResourceTemplatesTests(unittest.TestCase):
+ """
+ Tests for xmodule.x_module.ResourceTemplates
+ """
+ def test_templates(self):
+ expected = set([
+ 'latex_html.yaml',
+ 'zooming_image.yaml',
+ 'announcement.yaml',
+ 'anon_user_id.yaml'])
+ got = set((t['template_id'] for t in TestClass.templates()))
+ self.assertEqual(expected, got)
+
+ def test_templates_no_suchdir(self):
+ self.assertEqual(len(TestClass2.templates()), 0)
+
+ def test_get_template(self):
+ self.assertEqual(
+ TestClass.get_template('latex_html.yaml')['template_id'],
+ 'latex_html.yaml')
+
+
+class TestClass(ResourceTemplates):
+ """
+ Derives from the class under test for testing purposes.
+
+ Since `ResourceTemplates` is intended to be used as a mixin, we need to
+ derive a class from it in order to fill in some data it's expecting to find
+ in its mro.
+ """
+ template_packages = [__name__]
+
+ @classmethod
+ def get_template_dir(cls):
+ return 'templates/test'
+
+
+class TestClass2(TestClass):
+ """
+ Like TestClass, but `get_template_dir` returns a directory that doesn't
+ exist.
+
+ See `TestClass`.
+ """
+
+ @classmethod
+ def get_template_dir(cls):
+ return 'foo'
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index e7bc5c47fb..ac91ecb119 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -6,7 +6,12 @@ import yaml
from functools import partial
from lxml import etree
from collections import namedtuple
-from pkg_resources import resource_listdir, resource_string, resource_isdir
+from pkg_resources import (
+ resource_exists,
+ resource_listdir,
+ resource_string,
+ resource_isdir,
+)
from webob import Response
from webob.multidict import MultiDict
@@ -14,7 +19,7 @@ from xblock.core import XBlock
from xblock.fields import Scope, Integer, Float, List, XBlockMixin, String
from xblock.fragment import Fragment
from xblock.plugin import default_select
-from xblock.runtime import Runtime, MemoryIdManager
+from xblock.runtime import Runtime
from xmodule.fields import RelativeTime
from xmodule.errortracker import exc_info_to_str
@@ -508,6 +513,8 @@ class ResourceTemplates(object):
Gets the templates associated w/ a containing cls. The cls must have a 'template_dir_name' attribute.
It finds the templates as directly in this directory under 'templates'.
"""
+ template_packages = [__name__]
+
@classmethod
def templates(cls):
"""
@@ -520,14 +527,17 @@ class ResourceTemplates(object):
templates = []
dirname = cls.get_template_dir()
if dirname is not None:
- for template_file in resource_listdir(__name__, dirname):
- if not template_file.endswith('.yaml'):
- log.warning("Skipping unknown template file %s", template_file)
+ for pkg in cls.template_packages:
+ if not resource_isdir(pkg, dirname):
continue
- template_content = resource_string(__name__, os.path.join(dirname, template_file))
- template = yaml.safe_load(template_content)
- template['template_id'] = template_file
- templates.append(template)
+ for template_file in resource_listdir(pkg, dirname):
+ if not template_file.endswith('.yaml'):
+ log.warning("Skipping unknown template file %s", template_file)
+ continue
+ template_content = resource_string(pkg, os.path.join(dirname, template_file))
+ template = yaml.safe_load(template_content)
+ template['template_id'] = template_file
+ templates.append(template)
return templates
@@ -555,15 +565,13 @@ class ResourceTemplates(object):
"""
dirname = cls.get_template_dir()
if dirname is not None:
- try:
- template_content = resource_string(__name__, os.path.join(dirname, template_id))
- except IOError:
- return None
- template = yaml.safe_load(template_content)
- template['template_id'] = template_id
- return template
- else:
- return None
+ path = os.path.join(dirname, template_id)
+ for pkg in cls.template_packages:
+ if resource_exists(pkg, path):
+ template_content = resource_string(pkg, path)
+ template = yaml.safe_load(template_content)
+ template['template_id'] = template_id
+ return template
def prefer_xmodules(identifier, entry_points):