Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

requires_HTMX decorator #205

Closed
valberg opened this issue Mar 13, 2022 · 6 comments
Closed

requires_HTMX decorator #205

valberg opened this issue Mar 13, 2022 · 6 comments

Comments

@valberg
Copy link

valberg commented Mar 13, 2022

Description

I've implemented the following decorator and found it quite useful and just wanted to share.

The idea is to replicate the requires_http_methods variants like requires_POST, and redirect the user to a more relevant page if they (or their browser) somehow accidentally try to access a HTMX only endpoint. I've at least had some oddities of somehow getting there.

The decorator is pretty straight forward:

from functools import wraps

def requires_HTMX(redirect_url):
    def decorator(func):
        @wraps(func)
        def inner(request, *args, **kwargs):
            if not request.htmx:
                _redirect_url = (
                    redirect_url(request, *args, **kwargs)
                    if callable(redirect_url) else redirect_url
                )
                return redirect(_redirect_url)

            return func(request, *args, **kwargs)

        return inner

    return decorator

Basic usage:

@requires_HTMX(redirect_url=reverse("some-default-page"))
def htmx_only_view(request):
    return render(request, "my_awesome_htmx_template.html")

but can also be used to redirect the user to a page which is relevant to the view:

def _get_post_url(request, post_id):
    return reverse("post-detail", kwargs={"post_id": post_id})

@requires_HTMX(redirect_url=_get_post_url)
def render_post_form(request, post_id):
    ...
    return render(request, "post_form.html", context=context)

which is quite useful when dealing with many HTMX only views tied together.

If this makes sense and you could see it work in django-htmx, I would love to do a PR with tests and documentation @adamchainz - if it is out of scope for the project that is also fine :)

@adamchainz
Copy link
Owner

I don't think I would personally use this. Being able to load up a view that returns a fragment for htmx can be useful for debugging at least. And I can't think of a common way that users would accidentally hit such URL's, since they're only mentioned in hx-* attrs normally? I've never seen a JSON API that analogously detects you're visiting from a browser and redirects you.

Can you explain more of what lead you to think of this?

@valberg
Copy link
Author

valberg commented Mar 13, 2022

I've had some experiences with my browser showing the raw output from a HTMX request when for instance accessing a page again after the browser being closed. It might have something to do with wrong usage of hx-push-url. My reasoning was that if I could just block of that kind of direct access, I would minimise those kinds of weird "accidental behaviour".

The idea with the decorator is to mimic the django builtin require_http_methods decorator(s), in that it limits/guards the view from being accessed in a way that is not intended.

I also thought of including a call to django.utils.log.log_response (like in require_http_methods), to log if the decorated views are "used incorrectly".

@adamchainz
Copy link
Owner

Indeed you shouldn't push URL's into the location history that the user cannot load directly. Such URL's may be loaded in many situations: when the browser restarts, or the internet goes offline and returns, or if the user hits refresh. Displaying "fake" not-directly-loadable URL's seems like an anti-pattern to me.

Because of this I do not see the utility of the decorator.

@valberg
Copy link
Author

valberg commented Mar 13, 2022

Oh but it was not non-directly-loadable URLs I was pushing. I had just guessing that hx-push-url might be the reason - I'm not sure if it was.

Those random encounters (which I'll have to see if I can replicate somehow) were reason for the decorator. But I think that it has more merit than just avoiding that case - in the same way as require_http_methods marks correct usage of a view.

@adamchainz
Copy link
Owner

Oh but it was not non-directly-loadable URLs I was pushing. I had just guessing that hx-push-url might be the reason - I'm not sure if it was.

Please come back with more info then :)

I think there's a key difference with require_http_methods - for wrong methods, there isn't anything sensible to return. But HTML fragments are sensible and very useful for debugging, scraping, etc.

@valberg
Copy link
Author

valberg commented Mar 14, 2022

Will do!

The decorator could configured to be disabled if DEBUG=True - also as an opt-in it is/would be just a safe-guard against wrong usage if one chooses to use it. Having it redirect could also be optional, making it more akin to require_http_methods.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants