Skip to content

Commit 7d257db

Browse files
committed
fix: add ResourceTemplates
1 parent 3d59b30 commit 7d257db

File tree

2 files changed

+139
-43
lines changed

2 files changed

+139
-43
lines changed

xblocks_contrib/html/html.py

Lines changed: 139 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
This XBlock allows users to embed HTML content inside courses.
44
"""
55

6+
import logging
7+
import os
68
import re
9+
from importlib import resources
710

11+
import yaml
812
from django.conf import settings
913
from django.utils.translation import gettext_noop as _
1014
from web_fragments.fragment import Fragment
@@ -14,15 +18,148 @@
1418

1519
from xblocks_contrib.html.utils import escape_html_characters
1620

21+
log = logging.getLogger(__name__)
22+
1723
resource_loader = ResourceLoader(__name__)
1824

1925
# The global (course-agnostic) anonymous user ID for the user.
2026
ATTR_KEY_DEPRECATED_ANONYMOUS_USER_ID = "edx-platform.deprecated_anonymous_user_id"
2127

2228

29+
class ResourceTemplates:
30+
"""
31+
Gets the yaml templates associated with a containing cls for display in the Studio.
32+
33+
The cls must have a 'template_dir_name' attribute. It finds the templates as directly
34+
in this directory under 'templates'.
35+
36+
Additional templates can be loaded by setting the
37+
CUSTOM_RESOURCE_TEMPLATES_DIRECTORY configuration setting.
38+
39+
Note that a template must end with ".yaml" extension otherwise it will not be
40+
loaded.
41+
"""
42+
43+
template_packages = [__name__]
44+
45+
@classmethod
46+
def _load_template(cls, template_path, template_id):
47+
"""
48+
Reads an loads the yaml content provided in the template_path and
49+
return the content as a dictionary.
50+
"""
51+
if not os.path.exists(template_path):
52+
return None
53+
54+
with open(template_path) as file_object:
55+
template = yaml.safe_load(file_object)
56+
template["template_id"] = template_id
57+
return template
58+
59+
@classmethod
60+
def _load_templates_in_dir(cls, dirpath):
61+
"""
62+
Lists every resource template found in the provided dirpath.
63+
"""
64+
templates = []
65+
for template_file in os.listdir(dirpath):
66+
if not template_file.endswith(".yaml"):
67+
log.warning("Skipping unknown template file %s", template_file)
68+
continue
69+
70+
template = cls._load_template(os.path.join(dirpath, template_file), template_file)
71+
templates.append(template)
72+
return templates
73+
74+
@classmethod
75+
def templates(cls):
76+
"""
77+
Returns a list of dictionary field: value objects that describe possible templates that can be used
78+
to seed a module of this type.
79+
80+
Expects a class attribute template_dir_name that defines the directory
81+
inside the 'templates' resource directory to pull templates from.
82+
"""
83+
templates = {}
84+
85+
for dirpath in cls.get_template_dirpaths():
86+
for template in cls._load_templates_in_dir(dirpath):
87+
templates[template["template_id"]] = template
88+
89+
return list(templates.values())
90+
91+
@classmethod
92+
def get_template_dir(cls): # lint-amnesty, pylint: disable=missing-function-docstring
93+
if getattr(cls, "template_dir_name", None):
94+
dirname = os.path.join("templates", cls.template_dir_name) # lint-amnesty, pylint: disable=no-member
95+
template_path = resources.files(__name__.rsplit(".", 1)[0]) / dirname
96+
97+
if not template_path.is_dir():
98+
log.warning(
99+
"No resource directory {dir} found when loading {cls_name} templates".format(
100+
dir=dirname,
101+
cls_name=cls.__name__,
102+
)
103+
)
104+
return
105+
return dirname
106+
return
107+
108+
@classmethod
109+
def get_template_dirpaths(cls):
110+
"""
111+
Returns of list of directories containing resource templates.
112+
"""
113+
template_dirpaths = []
114+
template_dirname = cls.get_template_dir()
115+
if template_dirname:
116+
template_path = resources.files(__name__.rsplit(".", 1)[0]) / template_dirname
117+
if template_path.is_dir():
118+
with resources.as_file(template_path) as template_real_path:
119+
template_dirpaths.append(str(template_real_path))
120+
121+
custom_template_dir = cls.get_custom_template_dir()
122+
if custom_template_dir:
123+
template_dirpaths.append(custom_template_dir)
124+
return template_dirpaths
125+
126+
@classmethod
127+
def get_custom_template_dir(cls):
128+
"""
129+
If settings.CUSTOM_RESOURCE_TEMPLATES_DIRECTORY is defined, check if it has a
130+
subdirectory named as the class's template_dir_name and return the full path.
131+
"""
132+
template_dir_name = getattr(cls, "template_dir_name", None)
133+
134+
if template_dir_name is None:
135+
return
136+
137+
resource_dir = settings.CUSTOM_RESOURCE_TEMPLATES_DIRECTORY
138+
139+
if not resource_dir:
140+
return None
141+
142+
template_dir_path = os.path.join(resource_dir, template_dir_name)
143+
144+
if os.path.exists(template_dir_path):
145+
return template_dir_path
146+
return None
147+
148+
@classmethod
149+
def get_template(cls, template_id):
150+
"""
151+
Get a single template by the given id (which is the file name identifying it w/in the class's
152+
template_dir_name)
153+
"""
154+
for directory in sorted(cls.get_template_dirpaths(), reverse=True):
155+
abs_path = os.path.join(directory, template_id)
156+
if os.path.exists(abs_path):
157+
return cls._load_template(abs_path, template_id)
158+
159+
23160
@XBlock.needs("i18n")
24161
@XBlock.needs("user")
25-
class HtmlBlock(XBlock):
162+
class HtmlBlock(ResourceTemplates, XBlock):
26163
"""
27164
The HTML XBlock
28165
This provides the base class for all Html-ish blocks (including the HTML XBlock).
@@ -117,39 +254,11 @@ def public_view(self, context):
117254
"""
118255
return self.student_view(context)
119256

120-
# def studio_view(self, context): # pylint: disable=unused-argument
121-
# """
122-
# Generates a fragment that redirects to the new Studio editor URL.
123-
# """
124-
# course_key = self.runtime.course_id
125-
# block_id = str(self.scope_ids.usage_id)
126-
127-
# new_url = f"http://apps.local.openedx.io:2001/authoring/course/{course_key}/editor/html/{block_id}"
128-
129-
# fragment = Fragment()
130-
# fragment.add_content(
131-
# f"""
132-
# <script>
133-
# window.location.href = "{new_url}";
134-
# </script>
135-
# """
136-
# )
137-
# return fragment
138-
139257
def studio_view(self, context=None): # pylint: disable=unused-argument
140258
"""
141259
Return the studio view, which redirects to the frontend-app-authoring editor.
142260
"""
143-
fragment = Fragment()
144-
fragment.add_content(f"""
145-
<div class="xblock-editor-redirect">
146-
<a href="/authoring/course/{self.location.course_key}/editor/html/{self.location}" class="btn btn-primary">
147-
{_("Edit Content")}
148-
</a>
149-
</div>
150-
""")
151-
fragment.add_css(resource_loader.load_unicode("static/css/html_editor.css"))
152-
return fragment
261+
raise NotImplementedError("The studio view is not implemented. Please use the new Studio editor URL.")
153262

154263
@staticmethod
155264
def workbench_scenarios():

xblocks_contrib/html/static/css/html_editor.css

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)