Skip to content

Commit

Permalink
url_for and app_path refactor (#179)
Browse files Browse the repository at this point in the history
* static and app_path refactor

* fix nginx config

* fix template static url

* more url_for fixes

* fixing example with url_for

* fix examples with url_for

* renamed docker dir to deployment

* make tests compatible with app_path
  • Loading branch information
fzumstein authored Nov 8, 2024
1 parent 1ee55ba commit d5e349b
Show file tree
Hide file tree
Showing 39 changed files with 165 additions and 116 deletions.
4 changes: 4 additions & 0 deletions DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,7 @@ To use the most restrictive CSP header, set `XLWINGS_ENABLE_EXCEL_ONLINE=false`
## Prettier

The VS Code extension prettier requires to set the configuration to `.prettierrc.js` explicitly.

## Alert window

When debugging the alert/dialog window, you need to open up a separate instance of the dev tools.
7 changes: 6 additions & 1 deletion app/custom_scripts/examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ..config import settings


@script(target_cell="[xlwings_button]Sheet1!B4")
@script
def hello_world(book: xw.Book):
sheet = book.sheets.active
cell = sheet["A1"]
Expand All @@ -19,6 +19,11 @@ def hello_world(book: xw.Book):
cell.value = "Hello xlwings!"


@script
def show_alert(book: xw.Book):
book.app.alert("This is an alert!", title="xlwings Server Alert")


@script
def setup_custom_functions(book: xw.Book):
prefix = f"{settings.functions_namespace}"
Expand Down
17 changes: 9 additions & 8 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
from .object_handles import ObjectCacheConverter
from .routers import socketio as socketio_router
from .routers.manifest import router as manifest_router
from .routers.root import router as root_router
from .routers.taskpane import router as taskpane_router
from .routers.xlwings import router as xlwings_router
from .templates import templates

# Logging
logging.basicConfig(level=settings.log_level.upper())
Expand All @@ -23,6 +25,10 @@
# App
app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)

# Starlette's url_for returns fully qualified URLs causing issues if the reverse proxy
# handles TLS and the app runs on http (https://github.com/encode/starlette/issues/843)
templates.env.globals["url_for"] = app.url_path_for

# Register Converter
ObjectCacheConverter.register(object, "object", "obj")

Expand All @@ -44,11 +50,13 @@
socketio_router.sio,
# Only forward ASGI traffic if there's no message queue and hence 1 worker setup
cors_app if not settings.socketio_message_queue_url else None,
socketio_path=f"{settings.app_path}/socket.io",
)
main_app = sio_app if not settings.socketio_message_queue_url else cors_app


# Routers
app.include_router(root_router)
app.include_router(xlwings_router)
app.include_router(taskpane_router)
app.include_router(manifest_router)
Expand Down Expand Up @@ -106,17 +114,10 @@ async def add_security_headers(request, call_next):
return response


# Endpoints
@app.get("/")
async def root():
# This endpoint could be used for a health check
return {"status": "ok"}


# Static files: in prod might be served by something like nginx or via
# https://github.com/matthiask/blacknoise or https://github.com/Archmonger/ServeStatic
app.mount(
"/static",
settings.static_url_path,
StaticFiles(directory=settings.static_dir),
name="static",
)
Expand Down
2 changes: 1 addition & 1 deletion app/routers/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from ..config import settings
from ..templates import TemplateResponse

router = APIRouter()
router = APIRouter(prefix=settings.app_path)

logger = logging.getLogger(__name__)

Expand Down
16 changes: 16 additions & 0 deletions app/routers/root.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import logging

from fastapi import APIRouter

from ..config import settings

router = APIRouter(prefix=settings.app_path)

logger = logging.getLogger(__name__)


# Endpoints
@router.get("/")
async def root():
# This endpoint could be used for a health check
return {"status": "ok"}
3 changes: 2 additions & 1 deletion app/routers/taskpane.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from fastapi import APIRouter, Request

from ..config import settings
from ..templates import TemplateResponse

router = APIRouter()
router = APIRouter(prefix=settings.app_path)


@router.get("/taskpane")
Expand Down
2 changes: 1 addition & 1 deletion app/routers/xlwings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/xlwings")
router = APIRouter(prefix=f"{settings.app_path}/xlwings")


@router.get("/alert")
Expand Down
1 change: 1 addition & 0 deletions app/static/js/core/examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const appLoader = {
let bookName = await xlwings.getActiveBookName();
// Works with both an unsaved book ("Book1") as well as a saved one "Book1.xlsx"
if (bookName.includes("Book1")) {
// TODO: fix if app_path is provided
this.url = "/taskpane?app=1";
} else {
this.url = "/taskpane?app=2";
Expand Down
2 changes: 1 addition & 1 deletion app/static/js/core/sheet-buttons.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async function registerCellToButton(hyperlinkCellRef, scriptName, config) {
: "";
await xlwings.runPython(
window.location.origin +
(appPath && appPath.appPath !== "" ? `/${appPath.appPath}` : "") +
(appPath && appPath.appPath !== "" ? `${appPath.appPath}` : "") +
`/xlwings/custom-scripts-call/${scriptName}`,
{ ...config, auth: token },
);
Expand Down
4 changes: 2 additions & 2 deletions app/static/js/core/socketio-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const appPath = appPathElement ? JSON.parse(appPathElement.textContent) : null;
try {
globalThis.socket = io({
path:
(appPath && appPath.appPath !== "" ? `/${appPath.appPath}` : "") +
(appPath && appPath.appPath !== "" ? `${appPath.appPath}` : "") +
"/socket.io/",
auth: async (callback) => {
let token = await globalThis.getAuth();
Expand All @@ -28,7 +28,7 @@ globalThis.socket.on("xlwings:trigger-script", async (data) => {
let token = await globalThis.getAuth();
xlwings.runPython(
window.location.origin +
(appPath && appPath.appPath !== "" ? `/${appPath.appPath}` : "") +
(appPath && appPath.appPath !== "" ? `${appPath.appPath}` : "") +
"/xlwings/custom-scripts-call/" +
data.script_name,
{ ...data.config, auth: token },
Expand Down
2 changes: 1 addition & 1 deletion app/static/js/core/xlwingsjs/alert.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export async function xlAlert(prompt, title, buttons, mode, callback) {
}
Office.context.ui.displayDialogAsync(
window.location.origin +
(appPath && appPath.appPath !== "" ? `/${appPath.appPath}` : "") +
(appPath && appPath.appPath !== "" ? `${appPath.appPath}` : "") +
`/xlwings/alert?prompt=` +
encodeURIComponent(`${prompt}`) +
`&title=` +
Expand Down
2 changes: 1 addition & 1 deletion app/static/js/core/xlwingsjs/xlwings.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function init() {

await runPython(
window.location.origin +
(appPath && appPath.appPath !== "" ? `/${appPath.appPath}` : "") +
(appPath && appPath.appPath !== "" ? `${appPath.appPath}` : "") +
"/xlwings/custom-scripts-call/" +
element.getAttribute("xw-click"),
{ ...config, auth: token, errorDisplayMode: "taskpane" },
Expand Down
14 changes: 14 additions & 0 deletions app/templates/alert_base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{% extends "base.html" %}

{% block socketio %}
{% endblock socketio %}

{% block custom_functions_code %}
{% endblock custom_functions_code %}

{% block hotreload %}
{% endblock hotreload %}

{% block extra_head %}
<script src="{{ url_for('static', path='/js/core/xlwings-alert.js') }}" defer></script>
{% endblock extra_head %}
74 changes: 39 additions & 35 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,37 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Task pane</title>
<link rel="icon" type="image/png" href="{{ settings.static_url_path }}/images/favicon.png" />
<link rel="icon" type="image/png" href="{{ url_for('static', path='/images/favicon.png') }}" />
{# htmx #}
{% if settings.enable_htmx %}
<script src="{{ settings.static_url_path }}/vendor/htmx.org/dist/htmx.min.js"></script>
<script src="{{ settings.static_url_path }}/vendor/htmx-ext-head-support/head-support.js"></script>
<script src="{{ settings.static_url_path }}/vendor/htmx-ext-loading-states/loading-states.js"></script>
<script src="{{ settings.static_url_path }}/js/core/htmx-handlers.js" defer></script>
<script src="{{ url_for('static', path='/vendor/htmx.org/dist/htmx.min.js') }}"></script>
<script src="{{ url_for('static', path='/vendor/htmx-ext-head-support/head-support.js') }}"></script>
<script src="{{ url_for('static', path='/vendor/htmx-ext-loading-states/loading-states.js') }}"></script>
<script src="{{ url_for('static', path='/js/core/htmx-handlers.js') }}" defer></script>
{% endif %}
<script src="{{ settings.static_url_path }}/js/core/officejs-history-fix-part1.js"></script>
<script src="{{ url_for('static', path='/js/core/officejs-history-fix-part1.js') }}"></script>
{# Office.js #}
{% if settings.public_addin_store %}
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js"></script>
{% else %}
<script src="{{ settings.static_url_path }}/vendor/@microsoft/office-js/dist/office.js"></script>
<script src="{{ url_for('static', path='/vendor/@microsoft/office-js/dist/office.js') }}"></script>
{% endif %}
<script src="{{ settings.static_url_path }}/js/core/officejs-history-fix-part2.js"></script>
<script src="{{ url_for('static', path='/js/core/officejs-history-fix-part2.js') }}"></script>
{# Socket.io (must come before xlwings.js) #}
{% if settings.enable_socketio %}
<script src="{{ settings.static_url_path }}/vendor/socket.io/client-dist/socket.io.min.js"></script>
{% endif %}
{% block socketio %}
{% if settings.enable_socketio %}
<script src="{{ url_for('static', path='/vendor/socket.io/client-dist/socket.io.min.js') }}"></script>
<script src="{{ url_for('static', path='/js/core/socketio-handlers.js') }}" defer></script>
{% endif %}
{% endblock socketio %}
{# xlwings.js (must come before custom-function-code) #}
<script type="module" src="{{ settings.static_url_path }}/js/core/xlwingsjs/xlwings.js" defer></script>
<script type="module" src="{{ settings.static_url_path }}/js/core/xlwingsjs/auth.js" defer></script>
<script type="module" src="{{ settings.static_url_path }}/js/core/xlwingsjs/utils.js" defer></script>
<script type="module" src="{{ settings.static_url_path }}/js/core/xlwingsjs/alert.js" defer></script>
<script type="module" src="{{ url_for('static', path='/js/core/xlwingsjs/xlwings.js') }}" defer></script>
<script type="module" src="{{ url_for('static', path='/js/core/xlwingsjs/auth.js') }}" defer></script>
<script type="module" src="{{ url_for('static', path='/js/core/xlwingsjs/utils.js') }}" defer></script>
<script type="module" src="{{ url_for('static', path='/js/core/xlwingsjs/alert.js') }}" defer></script>
{# Alpine.js CSP boilerplate (must come before custom JS, which must come before alpinejs library) #}
{% if settings.enable_alpinejs_csp %}
<script src="{{ settings.static_url_path }}/js/core/alpinejs-csp-boilerplate.js"></script>
<script src="{{ url_for('static', path='/js/core/alpinejs-csp-boilerplate.js') }}"></script>
{% endif %}
{# JS Config #}
<!-- prettier-ignore-start -->
Expand All @@ -46,40 +49,41 @@
{ "xlwingsVersion": {{ settings.xlwings_version|tojson }} }
</script>
<!-- prettier-ignore-end -->
<script src="{{ settings.static_url_path }}/js/auth.js"></script>
<script src="{{ url_for('static', path='/js/auth.js') }}"></script>
{# Load Custom Functions #}
<script src="{{ settings.app_path }}/xlwings/custom-functions-code" defer></script>
{% block custom_functions_code %}
<script src="{{ url_for('custom_functions_code') }}" defer></script>
{% endblock custom_functions_code %}
{# Bootstrap with the xlwings theme #}
{% if settings.enable_bootstrap %}
<link
rel="stylesheet"
href="{{ settings.static_url_path }}/vendor/bootstrap-xlwings/dist/bootstrap-xlwings.min.css" />
<script src="{{ settings.static_url_path }}/vendor/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="{{ settings.static_url_path }}/js/core/bootstrap-customizations.js" defer></script>
href="{{ url_for('static', path='/vendor/bootstrap-xlwings/dist/bootstrap-xlwings.min.css') }}" />
<script src="{{ url_for('static', path='/vendor/bootstrap/dist/js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ url_for('static', path='/js/core/bootstrap-customizations.js') }}" defer></script>
{% endif %}
{# Examples #}
{% if settings.enable_examples %}
<script src="{{ settings.static_url_path }}/js/core/examples.js" defer></script>
<script src="{{ url_for('static', path='/js/core/examples.js') }}" defer></script>
{% endif %}
{# Own #}
<link rel="stylesheet" href="{{ settings.static_url_path }}/css/core.css" />
<link rel="stylesheet" href="{{ settings.static_url_path }}/css/style.css" />
{% if settings.enable_socketio %}
<script src="{{ settings.static_url_path }}/js/core/socketio-handlers.js" defer></script>
{% endif %}
<script src="{{ settings.app_path }}/xlwings/custom-scripts-sheet-buttons" defer></script>
<script src="{{ settings.static_url_path }}/js/core/sheet-buttons.js" defer></script>
<script src="{{ settings.static_url_path }}/js/main.js" defer></script>
<script src="{{ settings.static_url_path }}/js/ribbon.js" defer></script>
{% if settings.environment == "dev" and settings.enable_socketio %}
<script src="{{ settings.static_url_path }}/js/core/hotreload.js" defer></script>
{% endif %}
<link rel="stylesheet" href="{{ url_for('static', path='/css/core.css') }}" />
<link rel="stylesheet" href="{{ url_for('static', path='/css/style.css') }}" />
<script src="{{ url_for('custom_scripts_sheet_buttons') }}" defer></script>
<script src="{{ url_for('static', path='/js/core/sheet-buttons.js') }}" defer></script>
<script src="{{ url_for('static', path='/js/main.js') }}" defer></script>
<script src="{{ url_for('static', path='/js/ribbon.js') }}" defer></script>
{% block hotreload %}
{% if settings.environment == "dev" and settings.enable_socketio %}
<script src="{{ url_for('static', path='/js/core/hotreload.js') }}" defer></script>
{% endif %}
{% endblock hotreload %}
{% block extra_head %}
{# This allows to selectively load Alpine.js components #}
{% endblock extra_head %}
{# Alpine.js CSP build (must come after alpinejs-csp-boilerplate.js and custom code such as main.js) #}
{% if settings.enable_alpinejs_csp %}
<script src="{{ settings.static_url_path }}/vendor/@alpinejs/csp/dist/cdn.min.js" defer></script>
<script src="{{ url_for('static', path='/vendor/@alpinejs/csp/dist/cdn.min.js') }}" defer></script>
{% endif %}
{% endblock head %}
</head>
Expand Down
3 changes: 2 additions & 1 deletion app/templates/examples/alpine/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ To try it out, replace `app/routers/taskpane.py` with the following code:
```python
from fastapi import APIRouter, Request

from ..config import settings
from ..templates import TemplateResponse

router = APIRouter()
router = APIRouter(prefix=settings.app_path)


@router.get("/taskpane")
Expand Down
3 changes: 2 additions & 1 deletion app/templates/examples/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ To try it out, replace `app/routers/taskpane.py` with the following code:
from fastapi import APIRouter, Request

from .. import dependencies as dep
from ..config import settings
from ..templates import TemplateResponse

router = APIRouter()
router = APIRouter(prefix=settings.app_path)


@router.get("/taskpane")
Expand Down
2 changes: 1 addition & 1 deletion app/templates/examples/auth/protected.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
<div class="container-fluid pt-3 ps-3">
<h1>Protected Task Pane</h1>
<p>Your are logged in as {{ current_user.name }}.</p>
<a hx-get="/taskpane" hx-target="body" class="btn btn-primary">Home</a>
<a hx-get="{{ url_for('taskpane') }}" hx-target="body" class="btn btn-primary">Home</a>
</div>
{% endblock content %}
2 changes: 1 addition & 1 deletion app/templates/examples/auth/public.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
{% block content %}
<div class="container-fluid pt-3 ps-3">
<h1>Public Task Pane</h1>
<a hx-get="/taskpane/protected" hx-target="body" class="btn btn-primary">Go to Protected Area</a>
<a hx-get="{{ url_for('taskpane_protected') }}" hx-target="body" class="btn btn-primary">Go to Protected Area</a>
</div>
{% endblock content %}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% block content %}
<div class="container-fluid pt-3 ps-3" hx-target="this">
{% include "_book.html" %}
<form xw-book="true" xw-config='{"exclude": "MySheet"}' hx-post="/name" hx-swap="outerHTML">
<form xw-book="true" xw-config='{"exclude": "MySheet"}' hx-post="{{ url_for('name') }}" hx-swap="outerHTML">
<div class="mb-3">
<label for="inputName" class="form-label">Name</label>
<input class="form-control" name="name" id="inputName" autocorrect="off" />
Expand Down
7 changes: 5 additions & 2 deletions app/templates/examples/hello_world/taskpane_hello.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@
<h1>Example Task Pane</h1>
<div class="mb-3 d-flex flex-column gap-2">
<button xw-click="hello_world" xw-config='{"exclude": "MySheet"}' class="btn btn-primary btn-sm" type="button">
Hello World
Write 'Hello/Bye xlwings!' to A1
</button>
<button
xw-click="setup_custom_functions"
xw-config='{"exclude": "MySheet"}'
class="btn btn-primary btn-sm"
type="button">
Insert custom functions
Insert sheet with custom functions
</button>
<button xw-click="show_alert" xw-config='{"exclude": "MySheet"}' class="btn btn-primary btn-sm" type="button">
Show an alert
</button>
</div>
</div>
Expand Down
3 changes: 2 additions & 1 deletion app/templates/examples/htmx_form/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ To try it out, replace `app/routers/taskpane.py` with the following code:
from fastapi import APIRouter, Form, Request

from .. import custom_functions
from ..config import settings
from ..templates import TemplateResponse

router = APIRouter()
router = APIRouter(prefix=settings.app_path)


@router.get("/taskpane")
Expand Down
Loading

0 comments on commit d5e349b

Please sign in to comment.