Slide 1

Slide 1 text

everyday superpowers Chris May—October 16, 2023 HTML-ivating your Django app with Alpine, HTMX, and Streaming HTML Slides

Slide 2

Slide 2 text

everyday superpowers SPA

Slide 3

Slide 3 text

everyday superpowers SPA

Slide 4

Slide 4 text

everyday superpowers

Slide 5

Slide 5 text

everyday superpowers SPA—Single Page Application • A prevalent architectural pattern • Initial page load downloads a JavaScript application • This application handles all interactions • Uses APIs to communicate with servers • Changes the HTML and URL of the browser without leaving the page

Slide 6

Slide 6 text

everyday superpowers —Thoughtworks “SPAs incur complexity that simply doesn't exist with traditional server-based websites… Too often teams are skipping that trade-off analysis, blindly accepting the complexity of SPAs by default even when business needs don't justify it.” Source: https://www.thoughtworks.com/radar/techniques/spa-by-default

Slide 7

Slide 7 text

everyday superpowers —Thoughtworks “Some developers aren't aware of an alternative approach because they've spent their entire career in a framework like React.” Source: https://www.thoughtworks.com/radar/techniques/spa-by-default

Slide 8

Slide 8 text

everyday superpowers Experience drove SPA popularity SPAs became very popular because they provided a better experience: • Every interaction didn’t cause a new page to load • Powered dynamic and engaging interfaces • Interface is updated from small payloads • Changing data updates interface

Slide 9

Slide 9 text

everyday superpowers Taylor’s story “Making the world’s fastest website, and other mistakes” https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na

Slide 10

Slide 10 text

everyday superpowers web app native app amazon.com walmart.com Source: https://dev.to/tigt/so-what-c8j

Slide 11

Slide 11 text

everyday superpowers “Be so fast it’s fun on the worst devices and networks our customers use” Taylor’s goal:

Slide 12

Slide 12 text

everyday superpowers 130KB Budget (for all HTML, CSS, and JS)

Slide 13

Slide 13 text

everyday superpowers Remaining budget 20KB (for all HTML, CSS, and JS) ReactJS Redux 44.7KB

Slide 14

Slide 14 text

everyday superpowers “Maybe if I sprinkled the HTML with just enough CSS to look good… and if I had any room left, some laser-focused JavaScript for the pieces that benefit most from complex interactivity.” —Taylor Hunt Source: https://dev.to/tigt/making-the-worlds-fastest-website-and-other-mistakes-56na

Slide 15

Slide 15 text

everyday superpowers web app native app amazon.com walmart.com Source: https://dev.to/tigt/so-what-c8j demo app

Slide 16

Slide 16 text

everyday superpowers Hi, I’m Chris May! Python technical coach & Lead Software Engineer @ source7 I help python developers and teams reach their potential by temporarily joining teams and improving how they build software. https://everydaysuperpowers.dev

Slide 17

Slide 17 text

everyday superpowers How to create elevated experiences

Slide 18

Slide 18 text

everyday superpowers Components of elevated experiences • Remove whole-page refreshes for every interaction • Use small payloads from the server to update the interface • Update HTML as a result of changes in data • Empower rich on-page interactions • Be fast

Slide 19

Slide 19 text

everyday superpowers

Slide 20

Slide 20 text

everyday superpowers

Slide 21

Slide 21 text

everyday superpowers

Slide 22

Slide 22 text

everyday superpowers django-unicorn

Slide 23

Slide 23 text

everyday superpowers

Slide 24

Slide 24 text

everyday superpowers

Slide 25

Slide 25 text

everyday superpowers Components of elevated experiences ✓Remove whole-page refreshes for every interaction HTMX ✓Use small payloads from the server to update the interface HTMX • Update HTML as a result of changes in data • Empowered rich on-page interactions • Be fast

Slide 26

Slide 26 text

everyday superpowers

Slide 27

Slide 27 text

everyday superpowers

Slide 28

Slide 28 text

everyday superpowers Components of elevated experiences ✓Remove whole-page refreshes for every interaction HTMX ✓Use small payloads from the server to update the interface HTMX ✓Update HTML as a result of changes in data Alpine ✓Empowered rich on-page interactions Alpine • Be fast

Slide 29

Slide 29 text

everyday superpowers What about developer experience?

Slide 30

Slide 30 text

everyday superpowers Developer experience boost • Both frameworks can use HTML attributes to control behavior • Locality • Remove your JS build system • Write mostly Python and HTML • Easier maintenance • Faster iteration cycles

Slide 31

Slide 31 text

everyday superpowers https://youtu.be/3GObi93tjZI?t=1078

Slide 32

Slide 32 text

everyday superpowers https://youtu.be/3GObi93tjZI?t=1141

Slide 33

Slide 33 text

everyday superpowers Components of elevated experiences ✓Remove whole-page refreshes for every interaction HTMX ✓Use small payloads from the server to update the interface HTMX ✓Update HTML as a result of changes in data Alpine ✓Empowered rich on-page interactions Alpine • Be fast

Slide 34

Slide 34 text

everyday superpowers The cost of SPAs on mobile devices Source: https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks

Slide 35

Slide 35 text

everyday superpowers

Slide 36

Slide 36 text

everyday superpowers

Slide 37

Slide 37 text

everyday superpowers Deliver critical elements as quickly as possible With traditional web apps, Python will: • Gather all the data pieces • Grab the templates • Render and stitch together the whole template in memory • Send it down the pipe This is sub-optimal and occasionally hurts the experience.

Slide 38

Slide 38 text

everyday superpowers Deliver critical elements as quickly as possible What we want: • Start rendering the templates • Send it down the pipe as soon as you can • Wait for data • When a piece of data is available, render its fragment of HTML and send it down the pipe.

Slide 39

Slide 39 text

everyday superpowers 1 of 4 Standard process

Slide 40

Slide 40 text

everyday superpowers 1 of 4 Standard process

Slide 41

Slide 41 text

everyday superpowers 2 of 4 Basic streaming

Slide 42

Slide 42 text

everyday superpowers 3 of 4 Stream every item

Slide 43

Slide 43 text

everyday superpowers .skeleton:not(:has(.item + .item + .item + .item ))::before { /* A faded white bar scrubbing across the skeleton */ content: ''; position: absolute; height: 100%; width: 3rem; background: linear-gradient(90deg, rgba(255, 255, 255, 0) 10%, rgba(255, 255, 255, 0.5)... animation: shimmer 2.5s linear infinite; } @keyframes shimmer { /* Animation moving the faded white bar across the skeleton */ 0% {transform: translateX(-100%)} 100% {transform: translateX(100vw)} } .skeleton:not(:has(.item + .item + .item + .item)) { /* The skeleton boxes */ position: relative; background-repeat: no-repeat; background-image: linear-gradient(#eee 0, #dfe1e1 95%), linear-gradient(#eee 0, #dfe1e1 95%), linear-gradient(#eee 0, #dfe1e1 95%), linear-gradient(#eee 0, #dfe1e1 95%), linear-gradient(#f9f9f9 0, #fefefe 95%), ... }

Slide 44

Slide 44 text

everyday superpowers 4 of 4 Add skeletons

Slide 45

Slide 45 text

everyday superpowers

Slide 46

Slide 46 text

everyday superpowers How to create the demo in Django

Slide 47

Slide 47 text

everyday superpowers import asyncio from django.http import StreamingHttpResponse from django.template.loader import get_template from home.recommendations import customized_recommendations async def stream_homepage_content(): for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 48

Slide 48 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 49

Slide 49 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 50

Slide 50 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 51

Slide 51 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 52

Slide 52 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 53

Slide 53 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 54

Slide 54 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 55

Slide 55 text

everyday superpowers async def stream_homepage_content(): for item in customized_recommendations: # Faking a slow recommendation engine await asyncio.sleep(.7) yield item async def index(request): template = get_template('home.html') return StreamingHttpResponse( template.template.generate_async({ 'recommendations': stream_homepage_content() }) )

Slide 56

Slide 56 text

everyday superpowers How do you do this in Django templates today? O ctober 2023

Slide 57

Slide 57 text

everyday superpowers Option 1 • Split templates into pieces • Yield each part • Via Django 4.2’s new async iteration in StreamingHttpResponse O ctober 2023

Slide 58

Slide 58 text

everyday superpowers Option 1 _shell.html _shell.html home.html _item.html O ctober 2023

Slide 59

Slide 59 text

everyday superpowers import asyncio from django.http import StreamingHttpResponse from django.template.loader import render_to_string from home.recommendations import customized_recommendations async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html').split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 60

Slide 60 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 61

Slide 61 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 62

Slide 62 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 63

Slide 63 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 64

Slide 64 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 65

Slide 65 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 66

Slide 66 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %}
O ctober 2023

Slide 67

Slide 67 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %}
O ctober 2023

Slide 68

Slide 68 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %}
O ctober 2023

Slide 69

Slide 69 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %}
O ctober 2023

Slide 70

Slide 70 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 71

Slide 71 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 72

Slide 72 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 73

Slide 73 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 74

Slide 74 text

everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \ .split('') yield pre_shell for item in customized_recommendations: await asyncio.sleep(.7) # Faking a slow recommendation engine yield render_to_string('home/_item.html', dict(recommendation=item)) yield post_shell async def index(request): return StreamingHttpResponse(stream_homepage_content()) O ctober 2023

Slide 75

Slide 75 text

everyday superpowers Option 1 • Split templates into pieces • Yield each part • Via Django 4.2’s new async iteration in StreamingHttpResponse O ctober 2023

Slide 76

Slide 76 text

everyday superpowers Option 2 • Render a view • Subscribe to Server-Sent Events with HTMX • Send the slow parts to the page when they’re ready O ctober 2023

Slide 77

Slide 77 text

everyday superpowers import asyncio import random import re from django.http import StreamingHttpResponse from django.shortcuts import render from django.template.loader import render_to_string from home.recommendations import customized_recommendations def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse(streaming_content=sse_recommendation(), content_type='text/event-stream') def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 78

Slide 78 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 79

Slide 79 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 80

Slide 80 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %}
{% endfor %}
O ctober 2023

Slide 81

Slide 81 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %}
{% endfor %}
O ctober 2023

Slide 82

Slide 82 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %}
{% endfor %}
O ctober 2023

Slide 83

Slide 83 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %}
{% endfor %}
O ctober 2023

Slide 84

Slide 84 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %}
{% endfor %}
O ctober 2023

Slide 85

Slide 85 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %}
{% endfor %}
O ctober 2023

Slide 86

Slide 86 text

everyday superpowers

recommended for you

{% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %}
{% endfor %}
O ctober 2023

Slide 87

Slide 87 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 88

Slide 88 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 89

Slide 89 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 90

Slide 90 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 91

Slide 91 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 92

Slide 92 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 93

Slide 93 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 94

Slide 94 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 95

Slide 95 text

everyday superpowers def index(request): return render(request, 'home/home_sse.html') async def sse_recommendation(): recommendations = [] for item in customized_recommendations: await asyncio.sleep(.7) content = render_to_string('home/_item.html', dict(recommendation=item)) recommendations.append( re.sub('\n', '', content) ) all_recommendations = ''.join(recommendations) yield f'data: {all_recommendations}\n\n' def handle_sse(request): return StreamingHttpResponse( streaming_content=sse_recommendation(), content_type=‘text/event-stream' ) O ctober 2023

Slide 96

Slide 96 text

everyday superpowers Option 2 • Render a view • Subscribe to Server-Sent Events with HTMX • Send the slow parts to the page when they’re ready O ctober 2023

Slide 97

Slide 97 text

everyday superpowers https://github.com/PyHAT-stack/web-async-patterns Let’s build better options together!

Slide 98

Slide 98 text

everyday superpowers Exceptional experiences with Django • Use StreamingHttpResponse to stream critical elements to the user as quickly as possible. • Use HTML fragments to update parts of the page with HTMX. • Leverage scoped-down frameworks like AlpineJS to power rich interactions.

Slide 99

Slide 99 text

everyday superpowers Proof-of-concept repo: https://github.com/PyHAT-stack/web-async-patterns Slides: https://speakerdeck.com/chrismay/html-ivating-your-django- app-with-alpine-htmx-and-streaming-html For more from me: https://everydaysuperpowers.dev https://fosstodon.org/@_chrismay