Skip to content

Commit 04740f1

Browse files
AddisonSchillercslzchen
authored andcommitted
Public_file query param and office365 renderer
Adding support for a `public_file` query param so the OSF can request a public renderer. Added office365 which is a public renderer. This uses office online to do .docx file conversions.
1 parent 8bb2dd4 commit 04740f1

File tree

12 files changed

+176
-2
lines changed

12 files changed

+176
-2
lines changed

mfr/core/exceptions.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,25 @@ def __init__(self, message, *args, metadata_url: str='', response: str='', **kwa
145145
'response': self.response
146146
}])
147147

148+
149+
class QueryParameterError(ProviderError):
150+
"""The MFR related errors raised from a :class:`mfr.core.provider` and relating to query parameters
151+
should inherit from MetadataError
152+
This error is thrown when a query parameter is used missused
153+
"""
154+
155+
__TYPE = 'query_parameter'
156+
157+
def __init__(self, message, *args, url: str='', code: int=400, **kwargs):
158+
super().__init__(message, code=code, *args, **kwargs)
159+
self.url = url
160+
self.return_code = code
161+
self.attr_stack.append([self.__TYPE, {
162+
'url': self.url,
163+
'returncode': self.return_code,
164+
}])
165+
166+
148167
class TooBigToRenderError(ProviderError):
149168
"""If the user tries to render a file larger than a server specified maximum, throw a
150169
TooBigToRenderError.

mfr/core/provider.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,19 @@ def download(self):
4848

4949
class ProviderMetadata:
5050

51-
def __init__(self, name, ext, content_type, unique_key, download_url):
51+
def __init__(self, name, ext, content_type, unique_key, download_url, is_public=False):
5252
self.name = name
5353
self.ext = ext
5454
self.content_type = content_type
5555
self.unique_key = unique_key
5656
self.download_url = download_url
57+
self.is_public = is_public
5758

5859
def serialize(self):
5960
return {
6061
'name': self.name,
6162
'ext': self.ext,
63+
'is_public': self.is_public,
6264
'content_type': self.content_type,
6365
'unique_key': str(self.unique_key),
6466
'download_url': str(self.download_url),

mfr/core/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ def make_renderer(name, metadata, file_path, url, assets_url, export_url):
7676
:rtype: :class:`mfr.core.extension.BaseRenderer`
7777
"""
7878
normalized_name = (name and name.lower()) or 'none'
79+
if metadata.is_public:
80+
try:
81+
return driver.DriverManager(
82+
namespace='mfr.public_renderers',
83+
name=normalized_name,
84+
invoke_on_load=True,
85+
invoke_args=(metadata, file_path, url, assets_url, export_url),
86+
).driver
87+
except:
88+
# Check for a public renderer, if one doesn't exist, use a regular one
89+
# Real exceptions handled by main driver.DriverManager
90+
pass
91+
7992
try:
8093
return driver.DriverManager(
8194
namespace='mfr.renderers',

mfr/extensions/office365/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
# Office 365 Renderer
3+
4+
5+
This renderer uses Office Online to render .docx files for us. If the Office Online URL ever changes, it will also need to be changed here in settings.
6+
7+
Currently there is no OSF side component for these changes. Once there is, this specific note can be removed. In the meantime in order to test this renderer, you need to go to your local OSF copy of this file: https://github.com/CenterForOpenScience/osf.io/blob/develop/addons/base/views.py#L728-L736
8+
and add 'public_file' : 1, to the dict. This will send all files as public files.
9+
10+
Testing this renderer locally is hard. Since Office Online needs access to the files it will not work with private files or ones hosted locally. To see what the docx files will render like, replace the render function with something that looks like this:
11+
12+
```
13+
def render(self):
14+
static_url = 'https://files.osf.io/v1/resources/<fake_project_id>/providers/osfstorage/<fake_file_id>'
15+
url = settings.OFFICE_BASE_URL + download_url.url
16+
return self.TEMPLATE.render(base=self.assets_url, url=url)
17+
18+
```
19+
20+
The file at `static_url` must be publicly available.

mfr/extensions/office365/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .render import Office365Renderer # noqa

mfr/extensions/office365/render.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import os
2+
import furl
3+
4+
from mfr.core import extension
5+
from mako.lookup import TemplateLookup
6+
from mfr.extensions.office365 import settings
7+
8+
9+
class Office365Renderer(extension.BaseRenderer):
10+
"""A renderer for use with public .docx files.
11+
12+
Office online can render .docx files to pdf for us.
13+
This renderer will only ever be made if a query param with `public_file=1` is sent.
14+
It then generates and embeds an office online url into an
15+
iframe and returns the template. The file it is trying to render MUST
16+
be available publically online. This renderer will not work if testing locally.
17+
18+
"""
19+
20+
TEMPLATE = TemplateLookup(
21+
directories=[
22+
os.path.join(os.path.dirname(__file__), 'templates')
23+
]).get_template('viewer.mako')
24+
25+
def render(self):
26+
download_url = furl.furl(self.metadata.download_url).set(query='')
27+
url = settings.OFFICE_BASE_URL + download_url.url
28+
return self.TEMPLATE.render(base=self.assets_url, url=url)
29+
30+
@property
31+
def file_required(self):
32+
return False
33+
34+
@property
35+
def cache_result(self):
36+
return False

mfr/extensions/office365/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from mfr import settings
2+
3+
4+
config = settings.child('OFFICE365_EXTENSION_CONFIG')
5+
6+
OFFICE_BASE_URL = 'https://view.officeapps.live.com/op/embed.aspx?src='
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<style>
2+
iframe {
3+
width: 100%;
4+
height: 800;
5+
}
6+
</style>
7+
8+
<iframe src=${url} frameborder='0'></iframe>
9+
10+
<script src="/static/js/mfr.js"></script>
11+
<script src="/static/js/mfr.child.js"></script>

mfr/providers/osf/provider.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,23 @@ async def metadata(self):
119119
cleaned_url.args.pop(unneeded, None)
120120
self.metrics.add('metadata.clean_url_args', str(cleaned_url))
121121
unique_key = hashlib.sha256((metadata['data']['etag'] + cleaned_url.url).encode('utf-8')).hexdigest()
122-
return provider.ProviderMetadata(name, ext, content_type, unique_key, download_url)
122+
123+
is_public = False
124+
125+
if 'public_file' in cleaned_url.args:
126+
if cleaned_url.args['public_file'] not in ['0', '1']:
127+
raise exceptions.QueryParameterError(
128+
'The `public_file` query paramter should either `0`, `1`, or unused. Instead '
129+
'got {}'.format(cleaned_url.args['public_file']),
130+
url=download_url,
131+
provider=self.NAME,
132+
code=400,
133+
)
134+
135+
is_public = cleaned_url.args['public_file'] == '1'
136+
137+
return provider.ProviderMetadata(name, ext, content_type,
138+
unique_key, download_url, is_public=is_public)
123139

124140
async def download(self):
125141
"""Download file from WaterButler, returning stream."""

setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ def parse_requirements(requirements):
4040
'http = mfr.providers.http:HttpProvider',
4141
'osf = mfr.providers.osf:OsfProvider',
4242
],
43+
'mfr.public_renderers': [
44+
'.docx = mfr.extensions.office365:Office365Renderer',
45+
],
4346
'mfr.exporters': [
4447
# google docs
4548
'.gdraw = mfr.extensions.image:ImageExporter',

tests/extensions/office365/__init__.py

Whitespace-only changes.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import furl
2+
import pytest
3+
4+
from mfr.extensions.office365 import settings
5+
from mfr.core.provider import ProviderMetadata
6+
from mfr.extensions.office365 import Office365Renderer
7+
8+
9+
@pytest.fixture
10+
def metadata():
11+
return ProviderMetadata('test', '.pdf', 'text/plain', '1234',
12+
'http://wb.osf.io/file/test.pdf?token=1234&public_file=1',
13+
is_public=True)
14+
15+
16+
@pytest.fixture
17+
def file_path():
18+
return '/tmp/test.docx'
19+
20+
21+
@pytest.fixture
22+
def url():
23+
return 'http://osf.io/file/test.pdf'
24+
25+
26+
@pytest.fixture
27+
def assets_url():
28+
return 'http://mfr.osf.io/assets'
29+
30+
31+
@pytest.fixture
32+
def export_url():
33+
return 'http://mfr.osf.io/export?url=' + url()
34+
35+
36+
@pytest.fixture
37+
def renderer(metadata, file_path, url, assets_url, export_url):
38+
return Office365Renderer(metadata, file_path, url, assets_url, export_url)
39+
40+
41+
class TestOffice365Renderer:
42+
43+
def test_render_pdf(self, renderer, metadata, assets_url):
44+
download_url = furl.furl(metadata.download_url).set(query='')
45+
body_url = settings.OFFICE_BASE_URL + download_url.url
46+
body = renderer.render()
47+
assert '<iframe src={} frameborder=\'0\'></iframe>'.format(body_url) in body

0 commit comments

Comments
 (0)