Skip to content

Commit 852b60d

Browse files
authored
Merge pull request #1 from observablehq/mythmon/241118/readme
2 parents 8861365 + e35937d commit 852b60d

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

README.md

+54
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,59 @@
11
# Framework Embedded Analytics in Django
22

3+
This repository is an example of importing a JS module from a [Observable Cloud](https://observablehq.com/documentation/data-apps/) data app using signed URLs and embedding it in a [Django](https://www.djangoproject.com/). The Observable Cloud data app provides charts of the number of medals won by countries in the 2024 Olympic Games, optionally broken down by continent.
4+
5+
## Tour
6+
7+
The repository uses Observable Cloud's *signed URLs* feature, which enables secure embedding for private data apps. In this example the data app is public, for demonstration purposes.
8+
9+
The entry point for embedding is in the `index` function in [`charts/views.py`][views]. It generates a signed URL containg a JWT generated with [`PyJWT`](https://pypi.org/project/PyJWT/). Then it renders a Jinja template including that URL to send to the browser:
10+
11+
[views]: https://github.com/observablehq/embedded-analytics-django-example/blob/main/charts/views.py
12+
13+
```py
14+
def index(request):
15+
signed_url = sign_embedded_chart_url(f"{project_base_url}/medals-chart.js")
16+
context = {
17+
'medal_chart_url': signed_url,
18+
}
19+
return render(request, "charts/index.html", context)
20+
21+
def sign_embedded_chart_url(url):
22+
parsed_url = urlparse(url)
23+
24+
nbf = datetime.datetime.now().timestamp()
25+
nbf -= (nbf % (SIGNATURE_ALIGN_MIN * 60))
26+
exp = nbf + (SIGNATURE_VALIDITY_MIN * 60)
27+
28+
payload_data = {
29+
'sub': 'django-example',
30+
'urn:observablehq:path': parsed_url.path,
31+
'nbf': int(nbf),
32+
'exp': int(exp),
33+
}
34+
token = jwt.encode(payload_data, settings.EMBED_PRIVATE_KEY, algorithm='EdDSA')
35+
return f"{url}?token={token}"
36+
```
37+
38+
The Jinja template at [`charts/templates/charts/index.html`][template] includes a JS script that imports the module from the signed URL, and then renders it into the page:
39+
40+
[template]: https://github.com/observablehq/embedded-analytics-django-example/blob/main/charts/templates/charts/index.html
41+
42+
```jinja
43+
{% extends "./layout.html" %}
44+
45+
{% block content %}
46+
<h1>Olympic Medals</h1>
47+
<div id="medals-chart"></div>
48+
49+
<script type="module">
50+
const {MedalsChart} = await import("{{ medal_chart_url }}");
51+
const target = document.querySelector("#medals-chart");
52+
target.append(await MedalsChart());
53+
</script>
54+
{% endblock %}
55+
```
56+
357
## Development
458

559
Create and activate virtual environment

charts/views.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,18 @@
55
from django.shortcuts import render
66
from django.conf import settings
77

8+
project_base_url = "https://observablehq.observablehq.cloud/olympian-embeds"
9+
810
def index(request):
9-
signed_url = sign_embedded_chart_url("https://observablehq.observablehq.cloud/olympian-embeds/medals-chart.js")
11+
signed_url = sign_embedded_chart_url(f"{project_base_url}/medals-chart.js")
1012
context = {
1113
'medal_chart_url': signed_url,
1214
}
1315
return render(request, "charts/index.html", context)
1416

1517
def continent(name, code):
1618
def fn(request):
17-
signed_url = sign_embedded_chart_url(f"https://observablehq.observablehq.cloud/olympian-embeds/continent/{code}/chart.js")
19+
signed_url = sign_embedded_chart_url(f"{project_base_url}/continent/{code}/chart.js")
1820
context = {
1921
'medal_chart_url': signed_url,
2022
'continent_name': name,

0 commit comments

Comments
 (0)