Upgrade to Pro — share decks privately, control downloads, hide ads and more …

HTML-ivating your 
Django app with Alpine, HTMX, and Streaming HTML

Chris May
October 15, 2023
240

HTML-ivating your 
Django app with Alpine, HTMX, and Streaming HTML

The rise of SPAs has brought many benefits, but it has also introduced complexity and performance overheads that can be overwhelming. As Django developers, we know the power and elegance of the Django framework, and we believe it can deliver a better user experience without the need for heavy JavaScript frameworks.

In this talk, we will dive into the concepts of HTMX, a lightweight library that allows us to update parts of the HTML directly from the server, and AlpineJS, a minimal JavaScript framework for enhancing interactivity. We will explore how these tools can be integrated seamlessly with Django to create modern web apps with enhanced user experiences.

Additionally, we'll see how Django 4.2's `StreamingHttpResponse` lays the foundation for better experiences of views that require slow queries, microservice calls, or APIs.

Chris May

October 15, 2023
Tweet

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. everyday superpowers “Be so fast it’s fun on the worst

    devices and networks our customers use” Taylor’s goal:
  7. 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
  8. 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
  9. 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
  10. 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
  11. 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
  12. 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
  13. 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
  14. everyday superpowers The cost of SPAs on mobile devices Source:

    https://timkadlec.com/remembers/2020-04-21-the-cost-of-javascript-frameworks
  15. 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.
  16. 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.
  17. 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%), ... }
  18. 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() }) )
  19. 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() }) )
  20. 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() }) )
  21. 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() }) )
  22. 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() }) )
  23. 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() }) )
  24. 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() }) )
  25. 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() }) )
  26. 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() }) )
  27. everyday superpowers Option 1 • Split templates into pieces •

    Yield each part • Via Django 4.2’s new async iteration in StreamingHttpResponse O ctober 2023
  28. 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('<!-- --- stream products --- -->') 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
  29. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  30. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  31. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  32. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  33. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  34. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  35. everyday superpowers <!-- in home.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]"> {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %} <!-- --- stream products --- --> </div> </div> <!-- ./product --> O ctober 2023
  36. everyday superpowers <!-- in home.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]"> {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %} <!-- --- stream products --- --> </div> </div> <!-- ./product --> O ctober 2023
  37. everyday superpowers <!-- in home.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]"> {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %} <!-- --- stream products --- --> </div> </div> <!-- ./product --> O ctober 2023
  38. everyday superpowers <!-- in home.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]"> {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% endfor %} <!-- --- stream products --- --> </div> </div> <!-- ./product --> O ctober 2023
  39. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  40. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  41. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  42. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  43. everyday superpowers async def stream_homepage_content(): pre_shell, post_shell = render_to_string('home/home.html') \

    .split('<!-- --- stream products --- -->') 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
  44. everyday superpowers Option 1 • Split templates into pieces •

    Yield each part • Via Django 4.2’s new async iteration in StreamingHttpResponse O ctober 2023
  45. 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
  46. 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
  47. 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
  48. 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
  49. everyday superpowers <!-- in home_sse.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]" id="home-recommendations" > {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %} <div hx-ext="sse" sse-connect="/recommend-sse" sse-swap="message" hx-target="#home-recommendations" ></div> {% endfor %} </div> </div> <!-- ./product --> O ctober 2023
  50. everyday superpowers <!-- in home_sse.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]" id="home-recommendations" > {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %} <div hx-ext="sse" sse-connect="/recommend-sse" sse-swap="message" hx-target="#home-recommendations" ></div> {% endfor %} </div> </div> <!-- ./product --> O ctober 2023
  51. everyday superpowers <!-- in home_sse.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]" id="home-recommendations" > {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %} <div hx-ext="sse" sse-connect="/recommend-sse" sse-swap="message" hx-target="#home-recommendations" ></div> {% endfor %} </div> </div> <!-- ./product --> O ctober 2023
  52. everyday superpowers <!-- in home_sse.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]" id="home-recommendations" > {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %} <div hx-ext="sse" sse-connect="/recommend-sse" sse-swap="message" hx-target="#home-recommendations" ></div> {% endfor %} </div> </div> <!-- ./product --> O ctober 2023
  53. everyday superpowers <!-- in home_sse.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]" id="home-recommendations" > {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %} <div hx-ext="sse" sse-connect="/recommend-sse" sse-swap="message" hx-target="#home-recommendations" ></div> {% endfor %} </div> </div> <!-- ./product --> O ctober 2023
  54. everyday superpowers <!-- in home_sse.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]" id="home-recommendations" > {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %} <div hx-ext="sse" sse-connect="/recommend-sse" sse-swap="message" hx-target="#home-recommendations" ></div> {% endfor %} </div> </div> <!-- ./product --> O ctober 2023
  55. everyday superpowers <!-- in home_sse.html --> <!-- product --> <div

    class="container pb-16"> <h2 class="text-2xl font-medium text-gray-800 uppercase mb-6">recommended for you</h2> <div class="grid grid-cols-2 md:grid-cols-4 gap-6 skeleton min-h-[325px]" id="home-recommendations" > {% for item in recommendations %} {% include 'home/_item.html' with recommendation=item %} {% empty %} <div hx-ext="sse" sse-connect="/recommend-sse" sse-swap="message" hx-target="#home-recommendations" ></div> {% endfor %} </div> </div> <!-- ./product --> O ctober 2023
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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.