Skip to content

Commit f0458d8

Browse files
committed
feat: pdf block
1 parent 5081226 commit f0458d8

13 files changed

Lines changed: 463 additions & 9 deletions

File tree

‎.coveragerc‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
[run]
22
branch = True
3-
source = xblocks_contrib
3+
source =
4+
xblocks_contrib
5+
xblock_pdf

‎README.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ These are the XBlocks being moved here, and each of their statuses:
2121
* ``word_cloud`` -- Ready to Use
2222
* ``annotatable`` -- Ready to Use
2323
* ``lti`` -- In Development
24+
* ``pdf`` -- Done
2425
* ``html`` -- Ready to Use
2526
* ``discussion`` -- Placeholder
2627
* ``problem`` -- In Development

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"private": true,
33
"workspaces": [
4-
"xblocks_contrib/*/static"
4+
"xblocks_contrib/*/static",
5+
"xblock_pdf/*/static"
56
],
67
"scripts": {
78
"test": "npm run test --workspaces",

‎setup.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def package_data(pkg, sub_roots):
175175
author_email="oscm@openedx.org",
176176
url="https://github.com/openedx/xblocks-contrib",
177177
packages=find_packages(
178-
include=["xblocks_contrib", "xblocks_contrib.*"],
178+
include=["xblocks_contrib", "xblocks_contrib.*", "xblock_pdf"],
179179
exclude=["*tests"],
180180
),
181181
include_package_data=True,
@@ -204,7 +204,18 @@ def package_data(pkg, sub_roots):
204204
"_problem_extracted = xblocks_contrib:ProblemBlock",
205205
"_video_extracted = xblocks_contrib:VideoBlock",
206206
"_word_cloud_extracted = xblocks_contrib:WordCloudBlock",
207+
# 'Done' XBlocks-- ones that are ready for general use today,
208+
# and have been migrated fully from edx-platform or their original
209+
# repository.
210+
"pdf = xblock_pdf:PDFBlock",
207211
]
208212
},
209-
package_data=package_data("xblocks_contrib", ["static", "public", "templates"]),
213+
package_data={
214+
**package_data(
215+
"xblocks_contrib", ["static", "public", "templates"],
216+
),
217+
**package_data(
218+
"xblock_pdf", ["static", "templates"],
219+
),
220+
},
210221
)

‎tox.ini‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ match-dir = (?!migrations)
1616
[pytest]
1717
DJANGO_SETTINGS_MODULE = xblocks_contrib.test_settings
1818
django_find_project = false
19-
addopts = --cov xblocks_contrib --cov-report term-missing --cov-report xml
19+
addopts = --cov xblocks_contrib --cov xblocks_pdf --cov-report term-missing --cov-report xml
2020
norecursedirs = .* docs requirements site-packages
2121

2222
[testenv]
@@ -62,8 +62,8 @@ allowlist_externals =
6262
deps =
6363
-r{toxinidir}/requirements/quality.txt
6464
commands =
65-
pylint xblocks_contrib
66-
pycodestyle xblocks_contrib
67-
pydocstyle xblocks_contrib
68-
isort --check-only --diff xblocks_contrib
65+
pylint xblocks_contrib xblock_pdf
66+
pycodestyle xblocks_contrib xblock_pdf
67+
pydocstyle xblocks_contrib xblock_pdf
68+
isort --check-only --diff xblocks_contrib xblock_pdf
6969
make selfcheck

‎xblock_pdf/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Init for PDFBlock."""
2+
3+
from .pdf import PDFBlock

‎xblock_pdf/pdf.py‎

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""pdfXBlock main Python class."""
2+
3+
from django.utils.translation import gettext_noop as _
4+
from xblock.core import XBlock
5+
from xblock.fields import Boolean, Scope, String
6+
from xblock.fragment import Fragment
7+
from xblock.utils.resources import ResourceLoader
8+
9+
from .utils import bool_from_str, is_all_download_disabled
10+
11+
resource_loader = ResourceLoader(__name__)
12+
13+
14+
@XBlock.needs('i18n')
15+
class PDFBlock(XBlock):
16+
"""PDF XBlock. Allows authors to embed PDFs in their courses."""
17+
18+
icon_class = "other"
19+
20+
display_name = String(
21+
display_name=_("Display Name"),
22+
default=_("PDF"),
23+
scope=Scope.settings,
24+
help=_("This name appears in the horizontal navigation at the top of the page.")
25+
)
26+
27+
url = String(
28+
display_name=_("PDF URL"),
29+
default=_("https://tutorial.math.lamar.edu/pdf/Trig_Cheat_Sheet.pdf"),
30+
scope=Scope.content,
31+
help=_("The URL for your PDF.")
32+
)
33+
34+
allow_download = Boolean(
35+
display_name=_("PDF Download Allowed"),
36+
default=True,
37+
scope=Scope.content,
38+
help=_("Display a download button for this PDF.")
39+
)
40+
41+
source_text = String(
42+
display_name=_("Source document button text"),
43+
default="",
44+
scope=Scope.content,
45+
help=_(
46+
"Add a download link for the source file of your PDF. "
47+
"Use it for example to provide the PowerPoint file used to create this PDF."
48+
)
49+
)
50+
51+
source_url = String(
52+
display_name=_("Source document URL"),
53+
default="",
54+
scope=Scope.content,
55+
help=_(
56+
"Add a download link for the source file of your PDF. "
57+
"Use it for example to provide the PowerPoint file used to create this PDF."
58+
)
59+
)
60+
61+
def student_view(self, context=None):
62+
"""Primary view of the XBlock, shown to students when viewing courses."""
63+
context = {
64+
'display_name': self.display_name,
65+
'url': self.url,
66+
'allow_download': self.allow_download,
67+
'disable_all_download': is_all_download_disabled(),
68+
'source_text': self.source_text,
69+
'source_url': self.source_url,
70+
}
71+
html = resource_loader.render_django_template(
72+
'templates/html/pdf_view.html',
73+
context=context,
74+
i18n_service=self.runtime.service(self, "i18n"),
75+
)
76+
77+
event_type = 'edx.pdf.loaded'
78+
event_data = {
79+
'url': self.url,
80+
'source_url': self.source_url,
81+
}
82+
self.runtime.publish(self, event_type, event_data)
83+
frag = Fragment(html)
84+
frag.add_javascript(resource_loader.load_unicode("static/js/pdf_view.js"))
85+
frag.initialize_js('pdfXBlockInitView')
86+
return frag
87+
88+
def studio_view(self, context=None):
89+
"""
90+
Secondary view of the XBlock.
91+
92+
Shown to teachers when editing the XBlock.
93+
"""
94+
context = {
95+
'display_name': self.display_name,
96+
'url': self.url,
97+
'allow_download': self.allow_download,
98+
'disable_all_download': is_all_download_disabled(),
99+
'source_text': self.source_text,
100+
'source_url': self.source_url
101+
}
102+
html = resource_loader.render_django_template(
103+
'templates/html/pdf_edit.html',
104+
context=context,
105+
i18n_service=self.runtime.service(self, "i18n"),
106+
)
107+
frag = Fragment(html)
108+
frag.add_javascript(resource_loader.load_unicode("static/js/pdf_edit.js"))
109+
frag.initialize_js('pdfXBlockInitEdit')
110+
return frag
111+
112+
@XBlock.json_handler
113+
def on_download(self, data, suffix=''): # pylint: disable=unused-argument
114+
"""Download file event handler."""
115+
event_type = 'edx.pdf.downloaded'
116+
event_data = {
117+
'url': self.url,
118+
'source_url': self.source_url,
119+
}
120+
self.runtime.publish(self, event_type, event_data)
121+
122+
@XBlock.json_handler
123+
def save_pdf(self, data, suffix=''): # pylint: disable=unused-argument
124+
"""Save handler."""
125+
self.display_name = data['display_name']
126+
self.url = data['url']
127+
128+
if not is_all_download_disabled():
129+
self.allow_download = bool_from_str(data['allow_download'])
130+
self.source_text = data['source_text']
131+
self.source_url = data['source_url']
132+
133+
return {
134+
'result': 'success',
135+
}

‎xblock_pdf/static/js/pdf_edit.js‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* Javascript for pdfXBlock. */
2+
function pdfXBlockInitEdit(runtime, element) {
3+
$(element).find('.action-cancel').bind('click', function () {
4+
runtime.notify('cancel', {});
5+
});
6+
7+
$(element).find('.action-save').bind('click', function () {
8+
var data = {
9+
'display_name': $('#pdf_edit_display_name').val(),
10+
'url': $('#pdf_edit_url').val(),
11+
'allow_download': $('#pdf_edit_allow_download').val() || '',
12+
'source_text': $('#pdf_edit_source_text').val() || '',
13+
'source_url': $('#pdf_edit_source_url').val() || ''
14+
};
15+
16+
runtime.notify('save', { state: 'start' });
17+
18+
var handlerUrl = runtime.handlerUrl(element, 'save_pdf');
19+
$.post(handlerUrl, JSON.stringify(data)).done(function (response) {
20+
if (response.result === 'success') {
21+
runtime.notify('save', { state: 'end' });
22+
}
23+
else {
24+
runtime.notify('error', { msg: response.message });
25+
}
26+
});
27+
});
28+
}

‎xblock_pdf/static/js/pdf_view.js‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* Javascript for pdfXBlock. */
2+
function pdfXBlockInitView(runtime, element) {
3+
/* Weird behaviour :
4+
* In the LMS, element is the DOM container.
5+
* In the CMS, element is the jQuery object associated*
6+
* So here I make sure element is the jQuery object */
7+
if (element.innerHTML) {
8+
element = $(element);
9+
}
10+
11+
$(function () {
12+
element.find('.pdf-download-button').on('click', function () {
13+
var handlerUrl = runtime.handlerUrl(element, 'on_download');
14+
$.post(handlerUrl, '{}');
15+
});
16+
});
17+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{% load i18n %}
2+
<div class="wrapper-comp-settings editor-with-buttons is-active" id="settings-tab">
3+
<ul class="list-input settings-list">
4+
5+
<li class="field comp-setting-entry is-set">
6+
<div class="wrapper-comp-setting">
7+
<label class="label setting-label" for="pdf_edit_display_name">{% trans "Name" %}</label>
8+
<input class="input setting-input" id="pdf_edit_display_name" value="{{ display_name }}" type="text">
9+
</div>
10+
<span class="tip setting-help">{% trans "This name appears in the horizontal navigation at the top of the page." %}</span>
11+
</li>
12+
13+
<li class="field comp-setting-entry is-set">
14+
<div class="wrapper-comp-setting">
15+
<label class="label setting-label" for="pdf_edit_url">{% trans "PDF URL" %}</label>
16+
<input class="input setting-input" id="pdf_edit_url" value="{{ url }}" type="text">
17+
</div>
18+
<span class="tip setting-help">{% trans "The URL for your PDF." %}</span>
19+
</li>
20+
21+
{% if not disable_all_download %}
22+
<li class="field comp-setting-entry is-set">
23+
<div class="wrapper-comp-setting">
24+
<label class="label setting-label" for="pdf_edit_allow_download">{% trans "PDF Download Allowed" %}</label>
25+
<select class="input setting-input" id="pdf_edit_allow_download">
26+
<option value="True" {% if allow_download %}selected{% endif %}>{% trans "True" %}</option>
27+
<option value="False" {% if not allow_download %}selected{% endif %}>{% trans "False" %}</option>
28+
</select>
29+
</div>
30+
<span class="tip setting-help">{% trans "Display a download link to this PDF for convenience. Please note that even if this is disabled, the embedded PDF viewer may still display its own download button." %}</span>
31+
</li>
32+
33+
<li class="field comp-setting-entry is-set">
34+
<div class="wrapper-comp-setting">
35+
<label class="label setting-label" for="pdf_edit_source_text">{% trans "Source document button text" %}</label>
36+
<input class="input setting-input" id="pdf_edit_source_text" value="{{ source_text }}" type="text" placeholder="{% trans 'Default : Download the source document' %}">
37+
</div>
38+
<div class="wrapper-comp-setting">
39+
<label class="label setting-label" for="pdf_edit_source_url">{% trans "Source document URL" %}</label>
40+
<input class="input setting-input" id="pdf_edit_source_url" value="{{ source_url }}" type="text">
41+
</div>
42+
<span class="tip setting-help">{% trans "Add a download link for the source file of your PDF. Use it for example to provide the PowerPoint file used to create this PDF." %}</span>
43+
</li>
44+
{% endif %}
45+
46+
</ul>
47+
48+
<div class="xblock-actions">
49+
<ul>
50+
<li class="action-item">
51+
<a href="#" class="button action-primary action-save">{% trans "Save" %}</a>
52+
</li>
53+
<li class="action-item">
54+
<a href="#" class="button action-cancel">{% trans "Cancel" %}</a>
55+
</li>
56+
</ul>
57+
</div>
58+
</div>

0 commit comments

Comments
 (0)