From ea6b3b83abb34af823d0f26b11645305411c6603 Mon Sep 17 00:00:00 2001 From: Dan Jacob Date: Tue, 30 Mar 2021 16:36:21 +0300 Subject: [PATCH] Document updates --- doc/source/main.rst | 125 +++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 77 deletions(-) diff --git a/doc/source/main.rst b/doc/source/main.rst index 60d98d6..1487d91 100644 --- a/doc/source/main.rst +++ b/doc/source/main.rst @@ -425,108 +425,79 @@ That's it! In this example are returning a very simple string value, so we don't Note that this technique is something of an anti-pattern; if you have to update multiple parts of a page, a full refresh (i.e. a normal Turbo visit) is probably a better idea. It's useful though in some edge cases where you need to avoid this. -================== -Using Turbo Frames -================== +=================================== +The turbo_stream_response decorator +=================================== -Rendering Turbo Frames is straightforward. Let's say you have a "Subscribe" button in your page. When the button is clicked, you want the "Subscribe" label to be changed to "Unsubscribe"; when the button is clicked again it should turn back to "Subscribe." +You can accomplish the above using the **turbo_stream_response** decorator with your view. This will check the output and wrap the response in a **TurboStreamResponse** or **TurboStreamStreamingResponse**: -Our template looks something like this: +.. code-block:: python -.. code-block:: html + from turbo_response import TurboStream + from turbo_response.decorators import turbo_stream_response - {% extends "base.html" %} - {% block content %} -

Welcome to my blog

- {{ blog.description }} - {% if user.is_authenticated %} - - {% include "_subscribe.html" %} - - {% endif %} - {% endblock %} + @turbo_stream_response + def update_cart_item(request, item_id): + # item saved to e.g. session or db + save_cart_item(request, item_id) -Note that we surround the partial template with the ** tags. These will be replaced by Turbo when a Turbo Frame response matching the DOM ID "subscribe" is returned from the server. + # for brevity, assume "total amount" is returned here as a + # correctly formatted string in the correct local currency + total_amount = calc_total_cart_amount(request) -Our partial template, *_subscribe.html* looks like this: + return [ + TurboStream("nav-cart-total").replace.render(total_amount), + TurboStream("cart-summary-total").replace.render(total_amount), + ] -.. code-block:: html +Or using *yield* statements: -
- {% csrf_token %} - -
+.. code-block:: python -Note that the button uses a POST form to handle the toggle. As it's a POST we also need to include the CSRF token, or we'll get a 403 error. + @turbo_stream_response + def update_cart_item(request, item_id): + # item saved to e.g. session or db + save_cart_item(request, item_id) + + # for brevity, assume "total amount" is returned here as a + # correctly formatted string in the correct local currency + total_amount = calc_total_cart_amount(request) + yield TurboStream("nav-cart-total").replace.render(total_amount) + yield TurboStream("cart-summary-total").replace.render(total_amount) -Here are the views: -.. code-block:: python +If you return an HttpResponse subclass from your view (e.g. an HttpResponseRedirect, TemplateResponse or a TurboStreamResponse) this will be ignored by the decorator and returned as normal. - from django.contrib.auth.decorators import login_required - from django.template.response import TemplateResponse - from django.shortcuts import get_object_or_404 - - from turbo_response import TurboFrame - - from myapp.blogs.models import Blog - - def blog_detail(request, blog_id): - blog = get_object_or_404(Blog, pk=blog_id) - is_subscribed = blog.is_subscribed(request.user) - return TemplateResponse( - request, - "blogs/detail.html", - {"blog": blog, "is_subscribed": is_subscribed} - ) - - @login_required - def subscribe(request, blog_id): - blog = get_object_or_404(Blog, pk=blog_id) - is_subscribed = blog.toggle_subscribe(request.user) - return TurboFrame("subscribe").template( - "blogs/_subscribe.html", - {"blog": blog, "is_subscribed": is_subscribed}, - ).response(request) +================== +Using Turbo Frames +================== -The *subscribe* view returns a response wrapped in the ** tag with the DOM id "subscribe". Turbo will look for a corresponding frame in the HTML body with the matching ID, and replace the frame with the one returned from the server. Unlike a full Turbo visit, we don't need to return the entire body - just the snippet we want to update. +Turbo frames are straightforward using the **TurboFrame** class. -If we wanted to use CBVs instead: -.. code-block:: python +For example, suppose we want to render some content inside a frame with the ID "content": + +.. code-block:: html - from django.contrib.auth.mixins import LoginRequiredMixin - from django.views.generic.detail import DetailView, SingleObjectMixin +
+ add something here! - from turbo_response.views import TurboFrameTemplateView - from myapp.blogs.models import Blog +The view looks like this: - class BlogDetail(DetailView): - model = Blog - template_name = "blogs/detail.html" +.. code-block:: python - def get_context_data(self, **context): - return { - **context, - "is_subscribed": blog.is_subscribed(request.user) - } + def my_view(request): + return TurboFrame("content").response("hello") - class Subscribe(LoginRequiredMixin, - SingleObjectMixin, - TurboFrameTemplateView): - turbo_frame_dom_id = "subscribe" - template_name = "blogs/_subscribe.html" +As with streams, you can also render a template: - def post(request, pk): - blog = self.get_object() - is_subscribed = blog.toggle_subscribe(request.user) +.. code-block:: python - return self.render_to_response( - {"blog": blog, "is_subscribed": is_subscribed}, - ) + def my_view(request): + return TurboFrame("content").template("_content.html", {"message": "hello"}).response(request) ==========================