Skip to content

Commit

Permalink
Merge pull request #3 from CodeForPhilly/251_add_pdf_export_bufferd
Browse files Browse the repository at this point in the history
251 add pdf export bufferd [WIP]
  • Loading branch information
Zedmor authored Mar 30, 2020
2 parents 52f3ed1 + e852bb0 commit 849131e
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 160 deletions.
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ dash
dash_bootstrap_components
pyyaml
gunicorn
percy
selenium
Empty file removed src/__init__.py
Empty file.
5 changes: 1 addition & 4 deletions src/chime_dash/app/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from chime_dash.app.components.base import Component, HTMLComponentError
from dash_bootstrap_components.themes import BOOTSTRAP

from chime_dash.app.components.location import LocationComponent
from chime_dash.app.components.navbar import Navbar
from chime_dash.app.components.container import Container

Expand All @@ -32,9 +31,7 @@ def __init__(self, language, defaults):
"""
super().__init__(language, defaults)
self.components = OrderedDict(
navbar=Navbar(language, defaults),
container=Container(language, defaults),
location=LocationComponent()
navbar=Navbar(language, defaults), container=Container(language, defaults),
)
self.callback_outputs = []
self.callback_inputs = OrderedDict()
Expand Down
18 changes: 6 additions & 12 deletions src/chime_dash/app/components/container.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"""Initializes the dash html
"""
from collections import OrderedDict
from copy import deepcopy

import dash_bootstrap_components as dbc
import dash_core_components as dcc

from chime_dash.app.components.base import Component, HTMLComponentError
from chime_dash.app.components.content import Content
from chime_dash.app.components.sidebar import Sidebar
from chime_dash.app.services.pdf_printer import print_to_pdf

from penn_chime.models import SimSirModel


Expand All @@ -22,8 +21,7 @@ def __init__(self, language, defaults):
super().__init__(language, defaults)
self.pdf_filename = None
self.components = OrderedDict(
sidebar=Sidebar(language, defaults),
content=Content(language, defaults),
sidebar=Sidebar(language, defaults), content=Content(language, defaults),
)
self.callback_outputs = []
self.callback_inputs = OrderedDict()
Expand All @@ -35,7 +33,9 @@ def get_html(self):
"""Initializes the content container dash html
"""
container = dbc.Container(
children=dbc.Row(self.components["sidebar"].html + self.components["content"].html),
children=dbc.Row(
self.components["sidebar"].html + self.components["content"].html
),
fluid=True,
className="mt-5",
)
Expand All @@ -49,12 +49,6 @@ def callback(self, *args, **kwargs):
kwargs["model"] = SimSirModel(pars)
kwargs["pars"] = pars

save_to_pdf = self.components["sidebar"].save_to_pdf(kwargs)
if save_to_pdf:
self.pdf_filename = print_to_pdf(deepcopy(self.components['content']), kwargs)

kwargs['pdf_url'] = self.pdf_filename

callback_returns = []
for component in self.components.values():
try:
Expand Down
18 changes: 0 additions & 18 deletions src/chime_dash/app/components/location.py

This file was deleted.

81 changes: 46 additions & 35 deletions src/chime_dash/app/components/sidebar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,37 @@
#! _INPUTS should be considered for moving else where
"""
import dash_html_components as dhc

from typing import List, Dict, Any, Tuple
from collections import OrderedDict

from dash.dependencies import Input as CallbackInput, Output
from dash.development.base_component import ComponentMeta
from dash_html_components import Br
from dash_html_components import Br, Div, Nav

from penn_chime.defaults import RateLos
from penn_chime.parameters import Parameters

from chime_dash.app.components.base import Component
from chime_dash.app.utils.templates import create_switch_input, create_number_input, create_header, create_button, \
create_link
from chime_dash.app.utils.templates import (
create_switch_input,
create_number_input,
create_header,
create_button,
create_link,
)

FLOAT_INPUT_MIN = 0.001
FLOAT_INPUT_STEP = "any"

_INPUTS = OrderedDict(
regional_parameters={"type": "header", "size": "h3"},
market_share={"type": "number", "min": FLOAT_INPUT_MIN, "step": FLOAT_INPUT_STEP, "max": 100.0, "percent": True},
market_share={
"type": "number",
"min": FLOAT_INPUT_MIN,
"step": FLOAT_INPUT_STEP,
"max": 100.0,
"percent": True,
},
susceptible={"type": "number", "min": 1, "step": 1},
known_infected={"type": "number", "min": 0, "step": 1},
current_hospitalized={"type": "number", "min": 0, "step": 1},
Expand All @@ -50,7 +59,7 @@
"min": 0.0,
"step": FLOAT_INPUT_STEP,
"max": 100.0,
"percent": True
"percent": True,
},
ventilated_rate={
"type": "number",
Expand All @@ -69,8 +78,7 @@
show_tables={"type": "switch", "value": False},
show_tool_details={"type": "switch", "value": False},
show_additional_projections={"type": "switch", "value": False},
save_as_pdf={"type": "button", "property": "n_clicks"},
pdf_file_link={"type": "link", "property": "href"}
download_as_pdf_link={"type": "link"},
)


Expand All @@ -79,20 +87,27 @@ class Sidebar(Component):
contains the various inputs used to interact
with the model.
"""

# localization temp. for widget descriptions
localization_file = "sidebar.yml"

callback_inputs = OrderedDict(
(key, CallbackInput(component_id=key, component_property=_INPUTS[key].get("property", "value")))
for key in _INPUTS if _INPUTS[key]["type"] not in ("header", "link")
(
key,
CallbackInput(
component_id=key,
component_property=_INPUTS[key].get("property", "value"),
),
)
for key in _INPUTS
if _INPUTS[key]["type"] not in ("header", "link")
)

callback_outputs = [Output(component_id='pdf_file_link', component_property='href'),
Output(component_id='pdf_file_link', component_property='children')]
callback_outputs = [
Output(component_id="download_as_pdf_link", component_property="href")
]

def __init__(self, *args, **kwargs):
self._save_to_pdf = False
self.pdf_button_clicks = 0
super().__init__(*args, **kwargs)

@staticmethod
Expand Down Expand Up @@ -135,7 +150,7 @@ def get_html(self) -> List[ComponentMeta]:
element = create_button(idx, self.content)
elif data["type"] == "link":
elements.append(Br())
element = create_link(idx)
element = create_link(idx, self.content)
else:
raise ValueError(
"Failed to parse input '{idx}' with data '{data}'".format(
Expand All @@ -144,14 +159,11 @@ def get_html(self) -> List[ComponentMeta]:
)
elements.append(element)

sidebar = dhc.Nav(
children=dhc.Div(
sidebar = Nav(
children=Div(
children=elements,
className="p-4",
style={
"height": "calc(100vh - 48px)",
"overflowY": "auto",
},
style={"height": "calc(100vh - 48px)", "overflowY": "auto",},
),
className="col-md-3",
style={
Expand All @@ -160,23 +172,22 @@ def get_html(self) -> List[ComponentMeta]:
"bottom": 0,
"left": 0,
"zIndex": 100,
"boxShadow": "inset -1px 0 0 rgba(0, 0, 0, .1)"
}
"boxShadow": "inset -1px 0 0 rgba(0, 0, 0, .1)",
},
)

return [sidebar]

def save_to_pdf(self, kwargs):
"""
Return status of save to pdf flag and set it off.
"""
if kwargs.get('save_as_pdf', 0) and kwargs.get('save_as_pdf', 0) > self.pdf_button_clicks:
self.pdf_button_clicks = kwargs.get('save_as_pdf', '0')
return True
return False

def callback( # pylint: disable=W0613, R0201
self, *args, **kwargs
) -> List[Dict[str, Any]]:
return [kwargs.get('pdf_url', ''),
self.content['download_report'] if kwargs.get('pdf_url', None) else None]

url = "/download-as-pdf?" + "&".join(
[
"{key}={val}".format(key=key, val=val)
for key, val in kwargs.items()
if not key in ["model", "pars"] and val is not None
]
)

return [url]
80 changes: 40 additions & 40 deletions src/chime_dash/app/services/pdf_printer.py
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@
"""Utilities for exporting dash app to pdf
"""
import json
import base64

from time import sleep

from io import BytesIO

from dash import Dash
from dash.testing.application_runners import ThreadedRunner
from dash.testing.composite import DashComposite
from dash.dependencies import Input
from dash_bootstrap_components.themes import BOOTSTRAP

import dash_core_components as dcc
import uuid
import dash_bootstrap_components as dbc

from chime_dash.app.utils.templates import UPLOAD_DIRECTORY
from dash_html_components import Div
from selenium import webdriver


def send_devtools(driver, cmd, params={}):
resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
url = driver.command_executor._url + resource
body = json.dumps({'cmd': cmd, 'params': params})
response = driver.command_executor._request('POST', url, body)
if response['status']:
raise Exception(response.get('value'))
return response.get('value')
def send_devtools(driver, cmd, params=None):
params = params or None
resource = "/session/%s/chromium/send_command_and_get_result" % driver.session_id
url = driver.command_executor._url + resource
body = json.dumps({"cmd": cmd, "params": params})
response = driver.command_executor._request("POST", url, body)
if response.get("status", False):
raise Exception(response.get("value"))
return response.get("value")


def save_as_pdf(driver, path, options={}):
# https://timvdlippe.github.io/devtools-protocol/tot/Page#method-printToPDF
result = send_devtools(driver, "Page.printToPDF", options)
with open(path, 'wb') as file:
file.write(base64.b64decode(result['data']))
def save_as_pdf(driver, options=None):
"""Saves pdf to buffer object
"""
options = options or {}
# https://timvdlippe.github.io/devtools-protocol/tot/Page#method-printToPDF
result = send_devtools(driver, "Page.printToPDF", options)

cached_file = BytesIO()
cached_file.write(base64.b64decode(result["data"]))
cached_file.seek(0)
return cached_file


def print_to_pdf(component, kwargs):
"""Extracts content and prints pdf to buffer object.
"""
app = Dash(
__name__,
external_stylesheets=[
"https://www1.pennmedicine.org/styles/shared/penn-medicine-header.css",
BOOTSTRAP,
]
"https://www1.pennmedicine.org/styles/shared/penn-medicine-header.css",
BOOTSTRAP,
],
)

layout = Div([
dcc.Location(id="url", refresh=False),
dbc.Container(
children=component.html,
fluid=True,
className="mt-5",
)])

app.layout = layout
app.title = 'CHIME Printer'
app.layout = dbc.Container(children=component.html, fluid=True)
app.title = "CHIME Printer"

outputs = component.callback(**kwargs)

@app.callback(component.callback_outputs, [Input('url', 'pathname')])
def callback(*args): # pylint: disable=W0612
@app.callback(component.callback_outputs, list(component.callback_inputs.values()))
def callback(*args): # pylint: disable=W0612, W0613
return outputs

chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument('--headless')
chrome_options.add_argument("--headless")

with ThreadedRunner() as starter:
with DashComposite(starter, browser='Chrome', options=[chrome_options]) as dc:
with DashComposite(starter, browser="Chrome", options=[chrome_options]) as dc:
dc.start_server(app, port=8051)
while 'Loading...' in dc.driver.page_source:
while "Loading..." in dc.driver.page_source:
sleep(1)
filename = f'chime-{str(uuid.uuid4())[:8]}.pdf'
save_as_pdf(dc.driver, f'{UPLOAD_DIRECTORY}/{filename}', {'landscape': False})
pdf = save_as_pdf(dc.driver, {"landscape": False})
dc.driver.quit()
return f'/download/{filename}'

return pdf
3 changes: 1 addition & 2 deletions src/chime_dash/app/templates/en/sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,4 @@ regional_parameters: Regional Parameters
spread_and_contact: Spread and Contact Parameters
severity_parameters: Severity Parameters
display_parameters: Display Parameters
save_as_pdf: Generate PDF
download_report: Download Report
download_as_pdf_link: Download Report
Loading

0 comments on commit 849131e

Please sign in to comment.