1from plain.utils.cache import patch_vary_headers
2
3from .templates import render_template_fragment
4
5
6class HTMXViewMixin:
7 def render_template(self):
8 template = self.get_template()
9 context = self.get_template_context()
10
11 if self.is_htmx_request() and self.get_htmx_fragment_name():
12 return render_template_fragment(
13 template=template._jinja_template,
14 fragment_name=self.get_htmx_fragment_name(),
15 context=context,
16 )
17
18 return template.render(context)
19
20 def get_response(self):
21 response = super().get_response()
22 # Tell browser caching to also consider the fragment header,
23 # not just the url/cookie.
24 patch_vary_headers(
25 response, ["HX-Request", "Plain-HX-Fragment", "Plain-HX-Action"]
26 )
27 return response
28
29 def get_request_handler(self):
30 if self.is_htmx_request():
31 # You can use an htmx_{method} method on views
32 # (or htmx_{method}_{action} for specific actions)
33 method = f"htmx_{self.request.method.lower()}"
34
35 if action := self.get_htmx_action_name():
36 # If an action is specified, we throw an error if
37 # the associated method isn't found
38 return getattr(self, f"{method}_{action}")
39
40 if handler := getattr(self, method, None):
41 # If it's just an htmx post, for example,
42 # we can use a custom method or we can let it fall back
43 # to a regular post method if it's not found
44 return handler
45
46 return super().get_request_handler()
47
48 def is_htmx_request(self):
49 return self.request.headers.get("HX-Request") == "true"
50
51 def get_htmx_fragment_name(self):
52 # A custom header that we pass with the {% htmxfragment %} tag
53 return self.request.headers.get("Plain-HX-Fragment", "")
54
55 def get_htmx_action_name(self):
56 return self.request.headers.get("Plain-HX-Action", "")