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

Handle redirects when htmx == True #91

Open
valberg opened this issue Jul 8, 2021 · 7 comments
Open

Handle redirects when htmx == True #91

valberg opened this issue Jul 8, 2021 · 7 comments

Comments

@valberg
Copy link

valberg commented Jul 8, 2021

In a project I'm working on we logout priviliged users after some time. This has the downside of returning a HttpResponseRedirect when issuing a htmx request, ie. when a user tries to use an already open browser tab the next day.

For now we have solved the problem by writing the following middleware:

class HtmxRedirectMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        if request.htmx and isinstance(response, HttpResponseRedirect):
            # Set the HX-Redirect to the current location to imitate a reload
            response["HX-Redirect"] = response["Location"]
            # htmx only accepts 200's
            response.status_code = 200
        return response

This tells htmx to do a complete reload of the page leading to a redirect to the login page.

I think that the above approach might be a bit too generic - do we want to issue a redirect every time? For now it serves us fine since we are evaluating htmx in a small portion of our project.

The question is whether django-htmx should offer something similar?

@adamchainz
Copy link
Owner

I think that the above approach might be a bit too generic - do we want to issue a redirect every time?

This is my concern too. A server redirect isn't necessarily a call for an HTMX redirect...

Ideally you'd instead update your "if not logged in then redirect" code to generate the correct response for htmx immediately, rather than modifying it after the fact. Of course that's not always easy when you use e.g. django.contrib.auth.views.redirect_to_login.

I think we should let this one sit for a little, to think about it. I also hadn't thought about not-logged-in requests so I will look at working with them in my project.

@valberg
Copy link
Author

valberg commented Jul 8, 2021

Some more food for thought: I just did a quick search in the htmx source, and it looks as if 3xx status codes are ignored.

Ideally you'd instead update your "if not logged in then redirect" code to generate the correct response for htmx immediately, rather than modifying it after the fact.

We are using @login_required in this case, a specialised version of that might be a solution. But I'm not sure if it is the right one.

@mfisco
Copy link

mfisco commented Mar 13, 2022

I'm also working on a project where the majority requests require authentication. For the time being we simply customized django's LoginView

class AccountLoginView(LoginView):
    template_name = "accounts/login.html"

    def get(self, request, *args, **kwargs):
        """
        Triggers client-side redirect for htmx requests redirected to the login page.
        
        - Particularly useful when a user's session has expired but their client 
        is polling for a resource needing authentication. 

        """
        if request.htmx and REDIRECT_FIELD_NAME in request.GET:
            return HttpResponseClientRedirect(settings.LOGIN_URL)
        return super().get(request, *args, **kwargs)

Anyways, has there been any more ideas or thought given to adding this functionality to django-htmx?

@suda
Copy link

suda commented Jun 20, 2022

I was thinking, would it be worth it to add this as a decorator? I think this use case extends beyond just login (although that's exactly what brought me here 😅) and could be applied selectively vs being run on all requests via middleware like so:

def htmx_redirect(func):
    def wrapper(self, request, *args, **kwargs):
        response = func(self, request, *args, **kwargs)
        if request.htmx and isinstance(response, HttpResponseRedirect):
            response['HX-Redirect'] = response['Location']
            response.status_code = 204
        return response
    return wrapper

@itsthejoker
Copy link

itsthejoker commented Jun 20, 2022

I also just discovered this issue, and this what I wrote -- I apply it as a decorator on HTMX-only views and it works quite well for my uses, though doesn't handle the HX-Redirect header. I'm solving a different issue and can't delete my comment, so below is a general guard decorator for single views.

Click to expand!
def htmx_guard_redirect(redirect_name):
    """
    Decorator for guarding HTMX-only function views.

    If a request comes in to this endpoint that did not originate from HTMX,
    respond with a redirect to the requested endpoint.

    Usage:

    @htmx_guard_redirect("homepage")
    def htmx_only_function(request):
        ...

    Takes an optional `test` boolean that bypasses the redirect.
    """
    # https://stackoverflow.com/a/9030358
    def _method_wrapper(view_method: Callable) -> Callable:
        def _arguments_wrapper(
            request: Request, *args, **kwargs
        ) -> HttpResponseRedirect | Callable:
            testing = False
            if "test" in kwargs.keys():
                testing = kwargs.pop("test")
            if not request.htmx and not testing:
                return HttpResponseRedirect(reverse(redirect_name))
            return view_method(request, *args, **kwargs)

        return _arguments_wrapper

    return _method_wrapper

@scur-iolus
Copy link

This blog post highlights that returning a HTTP 303 status code might sometimes be particularly convenient with HTMX. Indeed, when you perform actions using HTTP methods like PUT / DELETE / PATCH and you want to redirect after the action is completed, using a 303 response ensures that the subsequent request after the redirection will be done using a GET method.

I'd just like to emphasize this point, which hasn't yet been mentioned, although it seems to be closely related to the potential new functionality being discussed here.

@BergLucas
Copy link

BergLucas commented Jan 28, 2024

Personally, I'm in favour of having such a mechanism in django-htmx.

I've just started learning HTMX, having already worked with Django for several years, and I was surprised not to find a solution in the documentation for managing authenticated routes like in classic Django.

In addition, since it's a middleware, it lets the user choose whether or not to activate it depending on whether it can work for their project.

Finally, it would allow many Django components to work directly without having to override some methods that are sometimes not designed to be easily overridden, such as handle_no_permission in LoginRequiredMixin for example.

In any case, thanks for the library, it works like a charm!

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

7 participants