https://htmx.org logo
Join Discord
Powered by
# πŸ”₯-django-htmx
  • g

    great-cartoon-12331

    05/12/2023, 5:55 PM
    you will need a custom
    login_required
    decorator that checks whether the request is from htmx, and if so generates a response that will trigger htmx to do a client-side redirect. htmx doesn't do this because it's a server-side concern
  • h

    handsome-gpu-11538

    05/12/2023, 5:57 PM
    hmm ok thanks! It just felt like such a common issue that i'm surprised there isn't a built in handler already...
  • g

    great-cartoon-12331

    05/12/2023, 6:29 PM
    there would be no way for htmx to tell on the client side that it's getting a 303 (or whatever) redirect because the user got logged out. in fact redirects are followed automatically by
    XMLHTTPRequest
    in JavaScript so it's at a layer below htmx even sees.
  • h

    handsome-gpu-11538

    05/12/2023, 6:39 PM
    ok thanks. i implemented the code snippet from the stack overflow link and it functions as I'd expect! Thanks.
  • g

    great-cartoon-12331

    05/12/2023, 6:56 PM
    np. another alternative would be to write a little
    @htmx
    decorator that would sit on top of
    @login_required
    and check if the request is from htmx and if the response is a 3xx redirect, and if so trigger a client-side redirect.
  • p

    proud-librarian-99598

    05/12/2023, 6:57 PM
    I have blogged about fixing this with a Java backend. Don’t know if it might help you get ideas, but here you go: https://www.wimdeblauwe.com/blog/2022/10/04/htmx-authentication-error-handling/
  • g

    great-cartoon-12331

    05/12/2023, 7:05 PM
    e.g.
    Copy code
    python
    def htmx(f):
        '''
        E.g.
    
        @htmx
        @login_required
        def ...
        '''
        def handler(req, *args, **kwargs):
            resp = f(req, *args, **kwargs)
            if "HX-Request" in request.headers and resp.status_code in {301, 302, 303}:
                response.headers['HX-Redirect'] = response.headers['Location']
                del response.headers['Location']
                response.status_code = 200
    
            return resp
    
        return handler
  • f

    future-table-82610

    05/13/2023, 6:21 AM
    Alright, so here's a weird one, sometimes when
    htmx
    returns a response parts of the context seem ot be "skipped". I have some custom middleware that inserts a variable - doesn't happen. Then I started noticing on a few`, request` and
    user
    . It's super weird and I can't find ay reason for it. Has anyone else ever seen anything like that?
  • j

    jolly-breakfast-57126

    05/13/2023, 11:19 AM
    Greetings django/htmx heads. I saw recently somebody asking somewhere (here? twitter? i don't remember!) about Django/HTMX as a PWA. I thought it was interesting and wondered what it might look like, so I whipped up a basic example if anybody's interested in that sort of thing: https://github.com/marknotfound/django-htmx-pwa-demo It even sprinkles in a little hyperscript in for good measure.
  • h

    handsome-shampoo-48908

    05/15/2023, 5:13 PM
    HI I explain you what I need on a basic project for more simplicity. I use HTMX to add collections and tasks to a todo list. I have a problem that I can't solve without reloading the page and I lose the interest of HTMX. When I add a new collection I need to load the link in the address bar, to have the list of collections updated and to have the content of the tasks of the collection I just added. My only solution for the moment is to use this in my view: response["HX-Redirect"] = f"{reverse('home')}?collection={slugify(collection_name)}" With HX-Push I can have the link in the address bar. I tried to use hx-swap-oob but I get nothing successful. views.py
    Copy code
    py
    def index(request):
        context = {}
    
        collection_slug = request.GET.get("collection")
        form_collection = CollectionForm()
        form_task = TasksForm()
    
        if not collection_slug:
            Collection.get_default_collection()
            return redirect(f"{reverse('home')}?collection=_default")
    
        collection = get_object_or_404(Collection, slug=collection_slug)
    
        context["collections"] = Collection.objects.order_by("slug")
        context["collection"] = collection
        context["tasks"] = collection.task_set.order_by("description")
        context["form_collection"] = form_collection
        context["form_task"] = form_task
    
        return render(request, 'tasks/index.html', context=context)
    
    
    def add_collection(request):
        collection_name = escape(request.POST.get("name"))
        collection, created = Collection.objects.get_or_create(name=collection_name, slug=slugify(collection_name))
        if not created:
            return HttpResponse("The collection already exists.", status=409)
    
        response = HttpResponse()
        response["HX-Redirect"] = f"{reverse('home')}?collection={slugify(collection_name)}"
        return response

    https://cdn.discordapp.com/attachments/864934037381971988/1107717283146637412/image.pngβ–Ύ

  • h

    handsome-shampoo-48908

    05/15/2023, 5:13 PM
    HTML
    Copy code
    html
                        <form method="POST" class="d-flex gap-2 mt-2">
                            {% csrf_token %}
                            {{ form_collection.name }}
                            <button class="btn btn-success"
                                    hx-post="{% url 'add-collection' %}"
                                    hx-target="#collections"
                                    hx-swap="beforeend"
                                    type="submit">Add
                            </button>
                        </form>
    Tell me if you want more info. Thanks in advance for your reply and your help.
  • b

    bitter-machine-55943

    05/15/2023, 11:10 PM
    How do you think about
    django-components
    compared to React/Vue components? Is the idea to make small components and piece them together to make more complex components (like in React/Vue/etc)?
  • r

    ripe-action-67367

    05/16/2023, 7:42 AM
    If I understood your problem correctly, you need to return the same
    Copy code
    render(request, 'tasks/index.html', context=context)
    from
    add_collection
    method. Set
    context["collection"]
    to your newly created collection
  • h

    handsome-shampoo-48908

    05/16/2023, 8:33 AM
    Thanks for your reply. The problem is not to add the collection is when i add the collection i want to have redirect to the page of the new collection but without page refresh.
  • r

    ripe-action-67367

    05/16/2023, 8:34 AM
    Combine this with
    response["HX-Push"] = f"{reverse('home')}?collection={slugify(collection_name)}"
  • r

    ripe-action-67367

    05/16/2023, 8:45 AM
    Apologies, it's
    HX-Push-Url
    https://htmx.org/headers/hx-push-url/
  • h

    handsome-shampoo-48908

    05/16/2023, 6:16 PM
    Yes but this is for push the url in the address bar. When i use it i have the url in the address bar but i don't see the new collection in the menu, it is not selected and i don't have the task associate to the collection on the current page.
  • r

    ripe-action-67367

    05/16/2023, 6:55 PM
    This is what I said in my previous message. You need to return the view the same way you do in
    index
    method. Make sure your form uses the same target and swap
  • h

    handsome-shampoo-48908

    05/17/2023, 9:22 AM
    Yes but if I do that it adds my new collection to the list. But it doesn't select it automatically. I want when I add a collection to automatically go to that new collection and have my corresponding task list. That's why for the moment I only found the solution with HX-Redirect but it completely reloads the page
  • r

    ripe-action-67367

    05/17/2023, 9:27 AM
    Do you set these values to the values from you newly created collection?
    Copy code
    context["collection"] = collection
        context["tasks"] = collection.task_set.order_by("description")
    I assume the
    "collection"
    value represents current collection. Make sure to set it to newly created one. Same for
    "tasks"
    . If you do that but it doesn't work, there must be something I'm missing with how your template is rendered
  • h

    handsome-shampoo-48908

    05/17/2023, 9:44 AM
    I can share more code but I don't know if it will help you understand the problem better. view.py
    Copy code
    py
    def index(request):
        context = {}
    
        collection_slug = request.GET.get("collection")
        form_collection = CollectionForm()
        form_task = TasksForm()
    
        if not collection_slug:
            Collection.get_default_collection()
            return redirect(f"{reverse('home')}?collection=_default")
    
        collection = get_object_or_404(Collection, slug=collection_slug)
    
        context["collections"] = Collection.objects.order_by("slug")
        context["collection"] = collection
        context["tasks"] = collection.task_set.order_by("description")
        context["form_collection"] = form_collection
        context["form_task"] = form_task
    
        return render(request, 'tasks/index.html', context=context)
    
    
    def add_collection(request):
        collection_name = escape(request.POST.get("name"))
        collection, created = Collection.objects.get_or_create(name=collection_name, slug=slugify(collection_name))
        if not created:
            return HttpResponse("The collection already exists.", status=409)
    
        response = HttpResponse()
        response["HX-Redirect"] = f"{reverse('home')}?collection={slugify(collection_name)}"
        return response
    
    
    def add_task(request):
        collection = Collection.objects.get(slug=request.POST.get("collection"))
        description = escape(request.POST.get("description"))
        task = Task.objects.create(description=description, collection=collection)
    
        return render(request, 'tasks/task.html', context={"task": task})
  • h

    handsome-shampoo-48908

    05/17/2023, 9:44 AM
    Copy code
    py
    def delete_task(request, task_pk):
        task = get_object_or_404(Task, pk=task_pk)
        task.delete()
    
        return HttpResponse("")
    
    
    def delete_collection(request, collection_pk):
        collection = get_object_or_404(Collection, pk=collection_pk)
        collection.delete()
    
        response = HttpResponse()
        response["HX-Redirect"] = f"{reverse('home')}?collection=_default"
        return response
    
    
    def get_tasks(request, collection_pk):
        collection = get_object_or_404(Collection, pk=collection_pk)
        tasks = collection.task_set.order_by("description")
    
        return render(request, 'tasks/tasks.html', context={"tasks": tasks, "collection": collection})
  • h

    handsome-shampoo-48908

    05/17/2023, 9:45 AM
    html
    Copy code
    html
    <div class="container mx-auto mt-5">
        <header class="d-flex align-items-center gap-2 mb-5">
            <img src="https://cdn-icons-png.flaticon.com/128/2666/2666436.png" alt="ToDo List">
            <h1 class="mb-0">To Do List</h1>
        </header>
    
        <hr>
        <br>
    
        <div class="row justify-content-around">
            <div class="card col-sm-6 col-md-4 col-lg-4 mb-5">
                <h5 class="card-header">Collections</h5>
                <div class="card-body">
                    <nav id="collections" class="list-group">
                        {% for collection in collections %}
                        {% include 'tasks/collection.html' with collection=collection %}
                        {% endfor %}
                    </nav>
                        <form method="POST" class="d-flex gap-2 mt-2">
                            {% csrf_token %}
                            {{ form_collection.name }}
                            <button class="btn btn-success"
                                    hx-post="{% url 'add-collection' %}"
                                    hx-target="#collections"
                                    hx-swap="beforeend"
                                    type="submit">Add
                            </button>
                        </form>
                </div>
            </div>
  • h

    handsome-shampoo-48908

    05/17/2023, 9:45 AM
    Copy code
    html
          <div class="card col-sm-6 col-md-6 col-lg-6 mb-5">
                  <h5 class="card-header">Tasks</h5>
                <div class="card-body">
                    <form method="POST" name="task-form" class="d-flex gap-2 mt-2" id="task_form">
                        {% csrf_token %}
                        {{ form_task.description }}
                        <button class="btn btn-success"
                                hx-post="{% url 'add-task' %}"
                                hx-target="#tasks"
                                hx-vals='js:{collection: getCollectionFromURL()}'
                                hx-swap="beforeend"
                                type="submit"
                        >Add
                        </button>
                    </form>
                    <section id="tasks-container">
                        {% include 'tasks/tasks.html' with tasks=tasks collection=collection %}
                    </section>
                </div>
            </div>
        </div>
    </div>
  • h

    handsome-shampoo-48908

    05/17/2023, 9:45 AM
    Copy code
    html
    <script>
        document.body.addEventListener("htmx:afterRequest", (event) => {
            document.querySelector(".reset-field-collection-form").value = '';
        })
    
        document.body.addEventListener("htmx:afterRequest", (event) => {
            document.querySelector(".reset-field-task-form").value = '';
        })
    
        document.body.addEventListener("htmx:responseError", (event) => {
            alert(evt.detail.xhr.responseText);
        })
    
        function getCollectionFromURL() {
            let url = new URL(window.location.href);
            let searchParams = url.searchParams;
            return searchParams.get("collection");
        }
    
        document.body.addEventListener('htmx:configRequest', (event) => {
            event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
        })
    
    
    </script>
  • m

    mammoth-family-48524

    05/17/2023, 10:08 AM
    I made something which I think is pretty cool https://github.com/benopotamus/okayjack-htmx. It's an HTMX extension + Django middleware and HttpResponse classes. It lets me specify different hx attributes for success and error, and also specify the path to a file or template block for success and error responses. Then in the Django view, I can just say whether a response should be treated as an error or success, and HTMX will update the page accordingly. Fair warning though, I'm just hacking away at things and I haven't tested it extensively - just a bit πŸ˜„
  • w

    white-motorcycle-95262

    05/17/2023, 2:14 PM
    Looking for some feedback/suggestions on a pattern I'm trying to implement. The concept is a "dashboard" that has easy to plug in "widgets." Currently, each widget is a template fragment based around Bootstrap `.card`s. Each widget has
    hx-
    attributes that listen for a particular
    change
    event (all coming from a single form, usually just has two fields: a select for the "region" and a toggle for the "timespan", short or long term). All widgets are tied to a class based "WidgetView" that has methods for generating the HTML associated with each type of widget. The idea is that to create a dashboard, all I have to do is create a base template with columns/rows and put
    {% include 'widgets/widget_name.html' %}
    where I want my widgets to go. This works, but it results in a single request for each widget, which I'm not opposed to, but in reality the widgets are quite coupled together: they always change all at the same time, and there's a small amount of "setup" code+database hits that end up being duplicated each request. I suppose the alternate way would be to put the
    hx-
    attributes on the form and do OOB swaps, which would result in a single request. My question is: what can I do on the form to communicate to the server which widgets are present on the dashboard? The only thing I've thought up involves hyperscript:
    Copy code
    <form
      id="control-panel
      hx-get="/widgets"
      _="
      init
        set widgets to {}
        for widget in <[data-widget]/>
          set widgets[widget's @id] to widget's @data-widget
        end
        set my @hx-vals to widgets as JSON
      end
    >[...]</form>
    where each widget would have a
    data-widget
    attribute that specifies the widget's "type" (e.g., map, graph, table, etc). Any thoughts or criticisms on this approach would be appreciated πŸ™‚
  • b

    brave-dog-98297

    05/17/2023, 9:27 PM
    If your template has which widget goes where, all you need is to send the correct data from the backend along with your base and Django will render it in one request. That's if I'm understanding correctly and they will update at the same time. I'd setup a div in the body with all the hx attributes and post/get from there.
  • w

    white-motorcycle-95262

    05/17/2023, 11:30 PM
    Hmm, but if I have multiple different views that have different layouts for the widgets (and potentially different choices of widgets for each view), how will the different views know how to render each widget (e.g. process the necessary context for each template). Currently, each view is just responsible for rendering the base template (e.g, which widgets and where), then after all those empty widgets load, htmx fetches the (non empty) widgets from my WidgetView.
  • b

    brave-dog-98297

    05/17/2023, 11:38 PM
    If they are all updating simultaneously I would push the heavy lifting to the template engine where possible* to avoid too many requests. But this may not work in your case. Your template can include other templates, as long as the data gets to the base template it should work
1...96979899100Latest