Skip to content

Commit 7183009

Browse files
authored
Merge pull request #1 from jrobichaud/asgi
Partially rewrite django-suspense to use StreamingResponse
2 parents 38095aa + d031b70 commit 7183009

File tree

28 files changed

+346
-93
lines changed

28 files changed

+346
-93
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ubuntu-latest
1616
strategy:
1717
matrix:
18-
python-version: ["3.8", "3.9", "3.10", "3.11"]
18+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1919

2020
steps:
2121
- uses: actions/checkout@v4

README.md

Lines changed: 49 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
Django Suspense is small package to easily display a fallback in templates until children have finished loading.
88

9+
910
## Quick start
11+
1012
### 1. Install package:
1113
To get started, install the package from [pypi](https://pypi.org/project/django-suspense/):
1214
```bash
@@ -42,36 +44,24 @@ TEMPLATES = [
4244
```
4345
If you choose not use it as a built-in, you will need to add `{% load suspense %}` to the top of your template whenever you want to use suspense.
4446

45-
### 2. Add `suspense` to `urls.py`:
46-
This is necessary because some parts of the pages will be loaded asynchronously (via http).
47-
```python
48-
from django.urls import include, path
4947

50-
51-
urlpatterns = [
52-
...,
53-
path('/_suspense/', include('suspense.urls'))
54-
]
55-
```
56-
57-
### 3. Create view with slow lazy load object:
48+
### 2. Create view with slow lazy load object:
5849
Because django executes database queries lazily, they may sometimes not work as expected. Let's try to create a very slow but lazy object and write a view function:
5950
```python
51+
from suspense.shortcuts import render
52+
6053
# app/views.py
6154
def view(request):
6255
def obj():
6356
import time
6457

65-
i = 0
66-
while i < 10:
67-
yield i
68-
i += 1
69-
time.sleep(1)
58+
time.sleep(1)
59+
return range(10)
7060

7161
return render(request, 'template.html', {'obj': obj})
7262
```
7363

74-
### 4. Use `suspense` in your template:
64+
### 3. Use `suspense` in your template:
7565
Let's now add the output of the received data to the template. At this point, we still haven't made a database query, so we can easily and quickly show the template right away.
7666
```html
7767
{% load suspense %}
@@ -92,7 +82,47 @@ Let's now add the output of the received data to the template. At this point, we
9282
```
9383
Once obj is ready for use, we will show it. But until it is ready, fallback works. While we are waiting for the data to be displayed, a request is made on the client side.
9484

95-
### 5. Hooray! Everything is ready to use it.
85+
### 4. Hooray! Everything is ready to use it.
86+
87+
88+
## Troubleshooting
89+
90+
### Safari delay in rendering
91+
92+
On Safari if your webpage is very light/simple, you may experience a delay in rendering.
93+
94+
Ex: the page renders only after some django-suspense or all content is downloaded.
95+
96+
WebKit has an issue with streaming responses requiring a certain amount of visible content before to actually start rendering.
97+
98+
See [webkit issue #252413](https://bugs.webkit.org/show_bug.cgi?id=252413)
99+
100+
If you are experiencing this issue, you can use the additional `{% webkit_extra_invisible_bytes %}` template tag to add a few extra invisible bytes in Safari.
101+
102+
```html
103+
{% load suspense %}
104+
105+
{% webkit_extra_invisible_bytes %}
106+
```
107+
108+
By default the `webkit_extra_invisible_bytes` adds 200 bytes but you can specify a different amount:
109+
110+
```html
111+
{% webkit_extra_invisible_bytes 300 %}
112+
```
113+
114+
### Content Security Policy (CSP) nonce error because of `strict-dynamic`
115+
116+
If you are using a Content Security Policy (CSP) with `nonce` and [`strict-dynamic`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#strict-dynamic), you may need to add the `nonce` attribute to the script tag.
117+
118+
You can override the `suspense/replacer.html` template and add the `nonce` attribute to the script tag.
119+
120+
With [django-csp](https://django-csp.readthedocs.io/en/latest/nonce.html#middleware):
121+
122+
```html
123+
{% extends "suspense/replacer.html" %}
124+
{% block script_attributes %}nonce="{{request.csp_nonce}}"{% endblock %}
125+
```
96126

97127

98128
## Contributing

config/tests.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
DEBUG = True
2+
3+
TEMPLATES = [
4+
{
5+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
6+
'DIRS': ["tests/templates"],
7+
'APP_DIRS': True,
8+
'OPTIONS': {
9+
'context_processors': [
10+
'django.template.context_processors.debug',
11+
'django.template.context_processors.request',
12+
'django.contrib.auth.context_processors.auth',
13+
'django.contrib.messages.context_processors.messages',
14+
],
15+
},
16+
},
17+
]
18+
INSTALLED_APPS = [
19+
'django.contrib.auth',
20+
'django.contrib.contenttypes',
21+
'suspense',
22+
]
23+
24+
TIME_ZONE = 'UTC'
25+
26+
USE_I18N = True
27+
28+
USE_TZ = True

config/urls.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
from django.urls import include, path
2-
3-
urlpatterns = [
4-
path('/suspense/', include('suspense.urls')),
5-
]
1+
urlpatterns = []

example/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,5 @@ This command will add several posts with title and content.
3030
## Run server
3131
Now that everything is ready, you can start the local server:
3232
```bash
33-
python manage.py runserver
33+
PYTHONPATH=$PWD/../ ./manage.py runserver
3434
```

example/example/urls.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@
33

44
urlpatterns = [
55
path('', include('posts.urls')),
6-
path('_suspense/', include('suspense.urls')),
76
path('admin/', admin.site.urls),
87
]

example/posts/templates/posts/all.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
{% load suspense %}
44

55
{% block content %}
6+
{% webkit_extra_invisible_bytes %}
7+
68
<aside class="py-8 lg:py-24 bg-gray-50">
79
<div class="px-4 mx-auto max-w-screen-xl">
810
<h2 class="mb-8 text-2xl font-bold text-gray-900">

example/posts/urls.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
from django.urls import path
22

3-
from .views import post, posts
3+
from .views import PostTemplateView, post, posts
44

5-
urlpatterns = [path('', posts), path('<str:slug>/', post)]
5+
urlpatterns = [
6+
path('', posts),
7+
path('class-view/', PostTemplateView.as_view()),
8+
path('<str:slug>/', post),
9+
]

example/posts/views.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
from django.shortcuts import get_object_or_404, render
22

3+
from suspense.shortcuts import render as suspense_render
4+
from suspense.views import SuspenseTemplateView
5+
36
from .models import Post
47

58

69
def posts(request):
710
posts = Post.objects.all()
811

9-
return render(request, 'posts/all.html', {'posts': posts})
12+
return suspense_render(request, 'posts/all.html', {'posts': posts})
1013

1114

1215
def post(request, slug):
1316
post = get_object_or_404(Post, slug=slug)
1417

1518
return render(request, 'posts/post.html', {'post': post})
19+
20+
21+
class PostTemplateView(SuspenseTemplateView):
22+
template_name = 'posts/all.html'
23+
24+
def get_context_data(self, **kwargs):
25+
return {'posts': Post.objects.all()}

example/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ asgiref==3.7.2
22
Django==4.2.7
33
sqlparse==0.4.4
44

5-
django-suspense
5+
#django-suspense

0 commit comments

Comments
 (0)