Skip to content

Commit

Permalink
Add HttpResponseLocation to send response with HX-Location (#239)
Browse files Browse the repository at this point in the history
Co-authored-by: Adam Johnson <me@adamj.eu>
  • Loading branch information
gone and adamchainz authored Nov 10, 2022
1 parent df91059 commit 04d89c1
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 0 deletions.
4 changes: 4 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ History

* Add ``django_htmx.http.retarget()`` for setting the ``HX-Retarget`` header added in `htmx 1.6.1 <https://htmx.org/posts/2021-11-22-htmx-1.6.1-is-released/>`__.

* Add ``HttpResponseLocation`` for sending a response with the ``HX-Location`` header.

Thanks to Ben Beecher in `PR #239 <https://github.com/adamchainz/django-htmx/pull/239>`__.

1.12.2 (2022-08-31)
-------------------

Expand Down
34 changes: 34 additions & 0 deletions docs/http.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ HTTP

.. currentmodule:: django_htmx.http

Response classes
----------------

.. autoclass:: HttpResponseClientRedirect

htmx can trigger a client side redirect when it receives a response with the |HX-Redirect header|__.
Expand Down Expand Up @@ -44,6 +47,34 @@ HTTP
return HttpResponseClientRefresh()
...
.. autoclass:: HttpResponseLocation

An HTTP response class for sending the |HX-Location header|__.
This header makes htmx make a client-side “boosted” request, acting like a client side redirect with a page reload.

.. |HX-Location header| replace:: ``HX-Location`` header
__ https://htmx.org/headers/hx-location/

``redirect_to`` should be the URL to redirect to, as per Django’s |HttpResponseRedirect|__.

.. |HttpResponseRedirect| replace:: ``HttpResponseRedirect``
__ https://docs.djangoproject.com/en/stable/ref/request-response/#django.http.HttpResponseRedirect

``source``, ``event``, ``target``, ``swap``, ``values``, and ``headers`` are all optional, with meaning as `documented by htmx <https://htmx.org/headers/hx-location/>`__.

For example:

.. code-block:: python
from django_htmx.http import HttpResponseLocation
def wait_for_completion(request, action_id):
...
if action.completed:
return HttpResponseLocation(f"/action/{action.id}/completed/")
...
.. autoclass:: HttpResponseStopPolling

When using a `polling trigger <https://htmx.org/docs/#polling>`__, htmx will stop polling when it encounters a response with the special HTTP status code 286.
Expand Down Expand Up @@ -80,6 +111,9 @@ HTTP
return render("event-finished.html", status=HTMX_STOP_POLLING)
...
Response modifying functions
----------------------------

.. autofunction:: push_url

Set the |HX-Push-Url header|__ of ``response`` and return it.
Expand Down
45 changes: 45 additions & 0 deletions src/django_htmx/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,51 @@ def __init__(self) -> None:
self["HX-Refresh"] = "true"


class HttpResponseLocation(HttpResponseRedirectBase):
status_code = 200

def __init__(
self,
redirect_to: str,
*args: Any,
source: str | None = None,
event: str | None = None,
target: str | None = None,
swap: Literal[
"innerHTML",
"outerHTML",
"beforebegin",
"afterbegin",
"beforeend",
"afterend",
"delete",
"none",
None,
] = None,
values: dict[str, str] | None = None,
headers: dict[str, str] | None = None,
**kwargs: Any,
) -> None:
super().__init__(redirect_to, *args, **kwargs)
spec: dict[str, str | dict[str, str]] = {
"path": self["Location"],
}
del self["Location"]
if source is not None:
spec["source"] = source
if event is not None:
spec["event"] = event
if target is not None:
spec["target"] = target
if swap is not None:
spec["swap"] = swap
if headers is not None:
spec["headers"] = headers
if values is not None:
spec["values"] = values
self["HX-Location"] = json.dumps(spec)


_HttpResponse = TypeVar("_HttpResponse", bound=HttpResponseBase)


Expand Down
36 changes: 36 additions & 0 deletions tests/test_http.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import json
from uuid import UUID

import pytest
Expand All @@ -9,6 +10,7 @@

from django_htmx.http import HttpResponseClientRedirect
from django_htmx.http import HttpResponseClientRefresh
from django_htmx.http import HttpResponseLocation
from django_htmx.http import HttpResponseStopPolling
from django_htmx.http import push_url
from django_htmx.http import reswap
Expand Down Expand Up @@ -41,6 +43,40 @@ def test_repr(self):
)


class HttpResponseLocationTests(SimpleTestCase):
def test_success(self):
response = HttpResponseLocation("/home/")

assert response.status_code == 200
assert "Location" not in response
spec = json.loads(response["HX-Location"])
assert spec == {"path": "/home/"}

def test_success_complete(self):
response = HttpResponseLocation(
"/home/",
source="#button",
event="doubleclick",
target="#main",
swap="innerHTML",
headers={"year": "2022"},
values={"banner": "true"},
)

assert response.status_code == 200
assert "Location" not in response
spec = json.loads(response["HX-Location"])
assert spec == {
"path": "/home/",
"source": "#button",
"event": "doubleclick",
"target": "#main",
"swap": "innerHTML",
"headers": {"year": "2022"},
"values": {"banner": "true"},
}


class HttpResponseClientRefreshTests(SimpleTestCase):
def test_success(self):
response = HttpResponseClientRefresh()
Expand Down

0 comments on commit 04d89c1

Please sign in to comment.