Deprecate HttpRequest.is_ajax

763 views
Skip to first unread message

Adam Johnson

unread,
Nov 16, 2019, 11:09:18 AM11/16/19
to django-d...@googlegroups.com
Django's HttpRequest.is_ajax method determines whether the request was made with the JS API XMLHttpRequest https://docs.djangoproject.com/en/2.2/ref/request-response/#django.http.HttpRequest.is_ajax . It does so by checking the X-Requested-With header.

The new way of making "AJAX" requests from the browser is the JavaScript fetch() API : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API .

I think the  is_ajax() documentation is at least a little misleading in pretending XMLHttpRequest is the only JS API. There also aren't any special headers set by fetch() so it's not possible to detect its requests.

I propose deprecating is_ajax() with no replacement.

Thoughts?

--
Adam

Tom Forbes

unread,
Nov 16, 2019, 11:16:15 AM11/16/19
to django-d...@googlegroups.com
I would agree. Flask has done the same:

DeprecationWarning: Request.is_xhr is deprecated. Given that the X-Requested-With header is not a part of any spec, it is not reliable

In my opinion there are not many good reasons to have to change behaviour if a request is made via XHR. I think the most common usage is to have a single view that returns a JSON response or a HTML response depending on if XHR is used (https://github.com/search?l=Python&q=request.is_ajax&type=Code), which isn’t great and isn’t reliable.


--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/CAMyDDM0i-p0ZxBj-fSheGs-2pMXH7K7Oka%3DCjy1YXx-emBu3mw%40mail.gmail.com.

Adam Johnson

unread,
Nov 17, 2019, 3:00:26 AM11/17/19
to django-d...@googlegroups.com
Right - Flask's error message also points to something I was mistaken about. XMLHttpRequest does not set this header. jQuery adds it ( https://api.jquery.com/jquery.ajax/#jQuery-ajax-settings ), and presumably some other JS libraries.

In my opinion there are not many good reasons to have to change behaviour if a request is made via XHR. I think the most common usage is to have a single view that returns a JSON response or a HTML response depending on if XHR is used (https://github.com/search?l=Python&q=request.is_ajax&type=Code), which isn’t great and isn’t reliable.

Riight too. A better way would be to check the Accept header. DRF does this: https://www.django-rest-framework.org/api-guide/content-negotiation/ . Django doesn't provide any tools at the moment for parsing Accept. We could add an API like https://pypi.org/project/django-accept-header/ ?



--
Adam

Jani Tiainen

unread,
Nov 17, 2019, 4:01:40 AM11/17/19
to django-d...@googlegroups.com
Hi.

I would be really favorable suggested approach which would open up more possibilities to responses. And in general sounds good direction.

Asif Saif Uddin

unread,
Nov 17, 2019, 6:32:50 AM11/17/19
to Django developers (Contributions to Django itself)


On Sunday, November 17, 2019 at 2:00:26 PM UTC+6, Adam Johnson wrote:
Right - Flask's error message also points to something I was mistaken about. XMLHttpRequest does not set this header. jQuery adds it ( https://api.jquery.com/jquery.ajax/#jQuery-ajax-settings ), and presumably some other JS libraries.

In my opinion there are not many good reasons to have to change behaviour if a request is made via XHR. I think the most common usage is to have a single view that returns a JSON response or a HTML response depending on if XHR is used (https://github.com/search?l=Python&q=request.is_ajax&type=Code), which isn’t great and isn’t reliable.

Riight too. A better way would be to check the Accept header. DRF does this: https://www.django-rest-framework.org/api-guide/content-negotiation/ . Django doesn't provide any tools at the moment for parsing Accept. We could add an API like https://pypi.org/project/django-accept-header/ ?

On Sat, 16 Nov 2019 at 16:16, Tom Forbes <t...@tomforb.es> wrote:
I would agree. Flask has done the same:

DeprecationWarning: Request.is_xhr is deprecated. Given that the X-Requested-With header is not a part of any spec, it is not reliable

In my opinion there are not many good reasons to have to change behaviour if a request is made via XHR. I think the most common usage is to have a single view that returns a JSON response or a HTML response depending on if XHR is used (https://github.com/search?l=Python&q=request.is_ajax&type=Code), which isn’t great and isn’t reliable.

On 16 Nov 2019, at 16:08, Adam Johnson <m...@adamj.eu> wrote:

Django's HttpRequest.is_ajax method determines whether the request was made with the JS API XMLHttpRequest https://docs.djangoproject.com/en/2.2/ref/request-response/#django.http.HttpRequest.is_ajax . It does so by checking the X-Requested-With header.

The new way of making "AJAX" requests from the browser is the JavaScript fetch() API : https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API .

I think the  is_ajax() documentation is at least a little misleading in pretending XMLHttpRequest is the only JS API. There also aren't any special headers set by fetch() so it's not possible to detect its requests.

I propose deprecating is_ajax() with no replacement.

Thoughts?

--
Adam

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-d...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-d...@googlegroups.com.


--
Adam

Claude Paroz

unread,
Nov 17, 2019, 7:56:45 AM11/17/19
to Django developers (Contributions to Django itself)
I'm afraid that implementing a whole content negociation framework is a bit ambitious, unless someone has much time to devote to that.

We could start smaller, similar to django-accept-header. I quickly sketched what it could look like in:

Claude

Tom Forbes

unread,
Nov 17, 2019, 3:33:09 PM11/17/19
to django-d...@googlegroups.com
I think this is a good starting point. What do we think about adding a “accepts_json” helper of some kind? It seems that the vast, vast majority of usages I can find of “is_ajax” is to return a JSON response, which I feel could be served nicely with a helper. There are also, annoyingly, two different JSON content types that are sent (the correct application/json and the incorrect text/json), which a helper could take care of. 


--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/7aff16cc-aeff-4b5e-b402-e4d587bc9315%40googlegroups.com.

Matthew Pava

unread,
Nov 18, 2019, 10:19:45 AM11/18/19
to django-d...@googlegroups.com

“In my opinion there are not many good reasons to have to change behaviour if a request is made via XHR. I think the most common usage is to have a single view that returns a JSON response or a HTML response depending on if XHR is used (https://github.com/search?l=Python&q=request.is_ajax&type=Code), which isn’t great and isn’t reliable.”

 

I do this. What would the best way to handle this? Perhaps the proper practice should be documented when it is deprecated?

Tom Forbes

unread,
Nov 18, 2019, 10:28:44 AM11/18/19
to django-d...@googlegroups.com
What I meant by that is it’s not an approach that scales well to lots of views. It might be better to have separate endpoints to return JSON (e.g adding a /json suffix), and in the past this has made services I’ve worked on a lot more maintainable and easy to understand. But it’s not as quick to do as `if request.is_ajax()` and requires a bit more upfront work. If you find you need to do this a lot then maybe something more structured like Django Rest Framework will be a better choice, which also handles content negotiation really well (it can produce XML, CSV, JSON, etc etc).

Matemática A3K

unread,
Nov 19, 2019, 12:06:19 AM11/19/19
to django-d...@googlegroups.com

I agree with Adam that it should be deprecated with no replacement.

The content negotiation is something that should be in but not as a replacement of it, as a general improvement.

I think there shouldn't be a replacement because "is_ajax" asks whether it came from a ""regular"" browser instead of jQuery and with the content negotiation you ask if the requester accepts a type - which can also lead to errors because the client may also accept other types (no example coming to my mind), and if so, it will lead to undesired behavior.

The right approach would be making AJAX requests request JSON output explicitly, by using a dedicated endpoint or by appending something that manifests their intention - like in https://docs.djangoproject.com/en/2.2/topics/class-based-views/mixins/#more-than-just-html is done with a get parameter. Not decide the response type by where it came from as it is unreliable as stated before, it provides convenience in some use cases but can lead to errors.

Seems better to me to refactor the view code so you can write a different view for Ajax requests that returns a JSON without code duplication.

As a shortcut, something like "For simple AJAX endpoints wrap your view with (something like) a "jsonview" decorator which will check the accept header (with something like Claude's code), return the appropriate error code if not, set the response type accordingly, and you should return a dict of strings (you have to take care of the serialization, i.e with https://docs.djangoproject.com/en/2.2/topics/serialization/#serialization-formats-json).

Jure Erznožnik

unread,
Nov 19, 2019, 8:29:55 AM11/19/19
to django-d...@googlegroups.com

Sorry for barging in like this, but this is actually a problem I have been dealing with quite a bit lately, so:

In my work I very often have to decide, depending on what's calling, what the rendered output might be. Ultimately I went with DRF and its content negotiation, though even that one - as implemented - sometimes isn't sufficient.

See, the problem sometimes isn't that you request JSON and then get JSON, request HTML and get HTML. You also have to cater for exceptions. Maybe a 4xx would return additional objects to insert into the DOM while a 200 would be fine with a JSON or even without data. What about 500?

I'm currently handling this with custom headers and the caller (the browser) tells the server what kind of outputs it can handle in different types of output.

The server then performs the branching at certain code points, specifically the ones mentioned above. DRF allows me to choose the appropriate renderer. Though I should mention here, that the data is already serialised at that point: sometimes this creates issues for renderers that might desire more information to do their work. Just mentioning that render stages need to be accounted for too. This may not be a problem for core Django as it doesn't have stages.

Again, sorry, but still hoping this helped in some way.

LP,
Jure

Matemática A3K

unread,
Nov 19, 2019, 9:20:36 PM11/19/19
to django-d...@googlegroups.com
On Tue, Nov 19, 2019 at 1:29 PM Jure Erznožnik <jure.er...@gmail.com> wrote:

Sorry for barging in like this, but this is actually a problem I have been dealing with quite a bit lately, so:

In my work I very often have to decide, depending on what's calling, what the rendered output might be. Ultimately I went with DRF and its content negotiation, though even that one - as implemented - sometimes isn't sufficient.

See, the problem sometimes isn't that you request JSON and then get JSON, request HTML and get HTML.

I think content negotiation is about giving the option to request the content in different formats, not rendering different content based on which format is requested.

You also have to cater for exceptions. Maybe a 4xx would return additional objects to insert into the DOM while a 200 would be fine with a JSON or even without data. What about 500?

This (and below) is about how to design a particular API for your needs, I think it is out of the scope of the problem discussed. The problem discussed is that is_ajax is not a reliable way to determine the origin of a request (and then format the content of a response)

Matemática A3K

unread,
Nov 20, 2019, 1:17:28 AM11/20/19
to django-d...@googlegroups.com
On Tue, Nov 19, 2019 at 9:20 PM Matemática A3K <matemat...@gmail.com> wrote:


On Tue, Nov 19, 2019 at 1:29 PM Jure Erznožnik <jure.er...@gmail.com> wrote:

Sorry for barging in like this, but this is actually a problem I have been dealing with quite a bit lately, so:

In my work I very often have to decide, depending on what's calling, what the rendered output might be. Ultimately I went with DRF and its content negotiation, though even that one - as implemented - sometimes isn't sufficient.

See, the problem sometimes isn't that you request JSON and then get JSON, request HTML and get HTML.

I think content negotiation is about giving the option to request the content in different formats, not rendering different content based on which format is requested.

You also have to cater for exceptions. Maybe a 4xx would return additional objects to insert into the DOM while a 200 would be fine with a JSON or even without data. What about 500?

This (and below) is about how to design a particular API for your needs, I think it is out of the scope of the problem discussed. The problem discussed is that is_ajax is not a reliable way to determine the origin of a request (and then format the content of a response)

(let me try to expand myself a bit so we can refocus)

If it is deprecated, then the question that arises naturally is "What would be the proper/new way of doing it?" because it is a pattern that have been applied previously (i.e. https://docs.djangoproject.com/en/2.2/topics/class-based-views/generic-editing/#ajax-example), then decorators have been proposed and refactors.

It seems to me that if there is no reliable way of determining it from the back-end side, then in the end it will be a convention between the front and the back. This could be a GET parameter, a "ClientWants: JSONOrNothing" header, or whatever convention you like to make, but not rely on a convention which seems to be fading out.

You can ensure the actual convention by setting the header manually (as stated in the is_ajax doc) - as you do with the CSRF token. Another convention could be better (i.e. "accepts json")

So far, is what the discussion went through to my understanding :)

Jure Erznožnik

unread,
Nov 20, 2019, 5:05:11 AM11/20/19
to django-d...@googlegroups.com

Agree.

I understood that the discussion already turned to "If it is deprecated, then the question that arises naturally is "What would be the proper/new way of doing it?"".

Also, to clarify:

What I wrote wasn't trying to impose a particular solution. I was just hoping to point out the challenges involved.

LP,
Jure

Manoj Kiraula

unread,
Nov 20, 2019, 6:49:14 AM11/20/19
to django-d...@googlegroups.com
I want ask a question regarding creating a string,why while creating a string where user give the input,there are different references of that object

Curtis Maloney

unread,
Nov 20, 2019, 4:47:04 PM11/20/19
to 'Mike Hansen' via Django developers (Contributions to Django itself)
My reading of this discussion boils down to "if we get rid of is_ajax (a buy-in convention nobody can rely on), how do people decide if it's an AJAX call or not?". To my mind, the "content negotiation" advocates have it right: HTTP has a mechanism for determining what response format is desired.

If Django were to provide a solid API for parsing Accept headers [not as easy as it sounds] and selecting a preferred response type [based on accept preference weights] ... would that take care of the bulk of "content negotiation"?

I could imagine a view progressing past the boiler plate [verifying the target object  exists, etc], doing its "work", then checking what response form to use, and branching there.

--
Curtis

Claude Paroz

unread,
Nov 20, 2019, 4:53:17 PM11/20/19
to django-d...@googlegroups.com
Le 20.11.19 à 22:46, Curtis Maloney a écrit :
> My reading of this discussion boils down to "if we get rid of is_ajax (a
> buy-in convention nobody can rely on), how do people decide if it's an
> AJAX call or not?". To my mind, the "content negotiation" advocates have
> it right: HTTP has a mechanism for determining what response format is
> desired.

It doesn't cover all use cases. For example, I have a use case where an
AJAX call only returns a partial HTML snippet while a non-AJAX call is
returning a full HTML page (same content type in both cases)
.
But in that case, I agree with a previous poster that it's a matter of
convention between client and server. I can rather easily create my own
`is_ajax(request)` utility depending on the convention I choose.

Claude
--
www.2xlibre.net

Tom Forbes

unread,
Nov 20, 2019, 5:25:30 PM11/20/19
to django-d...@googlegroups.com
If Django were to provide a solid API for parsing Accept headers [not as easy as it sounds] and selecting a preferred response type [based on accept preference weights] ... would that take care of the bulk of "content negotiation”?

I could imagine a view progressing past the boiler plate [verifying the target object  exists, etc], doing its "work", then checking what response form to use, and branching there.

I had a look at how DRF handles parses Accept headers, and it honestly doesn’t look too tricky: https://github.com/encode/django-rest-framework/blob/39876e66070c1d6f97740789d5c6f6a0a3ea06cf/rest_framework/utils/mediatypes.py#L52-L62. The precedence logic is a bit complex, but all in all it’s under ~30 lines of actual implementation code.

But providing the ability to parse accept headers and branch is simple enough to match a lot of the current “is_ajax” usages without too many changes. I don’t see much value in trying to tackle more complex/structured content negotiation handing.


Curtis Maloney

unread,
Nov 20, 2019, 6:44:54 PM11/20/19
to 'Mike Hansen' via Django developers (Contributions to Django itself)


On Thu, 21 Nov 2019, at 09:25, Tom Forbes wrote:
If Django were to provide a solid API for parsing Accept headers [not as easy as it sounds] and selecting a preferred response type [based on accept preference weights] ... would that take care of the bulk of "content negotiation”?

I could imagine a view progressing past the boiler plate [verifying the target object  exists, etc], doing its "work", then checking what response form to use, and branching there.

I had a look at how DRF handles parses Accept headers, and it honestly doesn’t look too tricky: https://github.com/encode/django-rest-framework/blob/39876e66070c1d6f97740789d5c6f6a0a3ea06cf/rest_framework/utils/mediatypes.py#L52-L62. The precedence logic is a bit complex, but all in all it’s under ~30 lines of actual implementation code.

Yeah, I expected DRF had this "solved" already. From my own experimentation, mapping `cgi.parse_header` over the the "Accept" header value, split by comma, gets a usable result; then sort that list by 'q' (defaulting to 1.0) and you have your priority.

But knowing that's the right way to parse Accept headers, and asking for match or quality measures of your response options, feel like an abstraction Django could/should provide.

But providing the ability to parse accept headers and branch is simple enough to match a lot of the current “is_ajax” usages without too many changes. I don’t see much value in trying to tackle more complex/structured content negotiation handing.

This was my hope - an 80/20 solution that in no way precluded other solutions.

--
C

James Bennett

unread,
Nov 20, 2019, 6:52:12 PM11/20/19
to django-d...@googlegroups.com
On Wed, Nov 20, 2019 at 3:44 PM Curtis Maloney <cur...@tinbrain.net> wrote:

Yeah, I expected DRF had this "solved" already. From my own experimentation, mapping `cgi.parse_header` over the the "Accept" header value, split by comma, gets a usable result; then sort that list by 'q' (defaulting to 1.0) and you have your priority.

Both the original and forks seem to've been abandoned now, but the mimeparse library encapsulated this into a nice little function that took an Accept header, and a list of content-types you could support in the response, and told you which one was the best match. The code's still out there and under a permissive license if somebody wants to pick it up again.

Asif Saif Uddin

unread,
Nov 20, 2019, 11:29:40 PM11/20/19
to Django developers (Contributions to Django itself)
Not so sure though, but would like to know if https://github.com/python-hyper these libraries are of any use for django http?

Matemática A3K

unread,
Nov 21, 2019, 1:23:07 AM11/21/19
to django-d...@googlegroups.com
I think now that providing alternatives is the way to go :)

When 'is_ajax' is used for content negotiation, then proper content negotiation via the Accept headers should be done.

When 'is_ajax' is used for different content delivery (like routing), then make the convention explicit or refactoring should be done.

In the case of content negotiation - https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation - this is only about media types (Accept-Language is already parsed in i18n).

Just parsing them it and make them available in the request - filtered by the supported ones - as an ordered list ("accepted_media_types") and the preferred one ("preferred_media_type") seems sufficient to me.

For functional views, it would be up to you to decide which one you support and how it is delivered with something like "if 'json' in request.preferred_media_type:".  We can add decorators for setting the response type for a specific media type and optionally returning a 406.

For CBVs, a mixin should be done - something like ContentNegotiationMixin - where you define the the types you want to support in it (or otherwise use the settings) and you should define or override methods like "to_JSON", "to_XML", "to_LABEL" that will serialize your context into the media type that is the best match for your options.

As more than one media type may correspond to one format, if a dict that labels the supported types is defined, something like:

SUPPORTED_MEDIA_TYPES_LABELS = {
  "application/json": "JSON",
  "text/json": "JSON",
  "application/pdf": "PDF",
  "text/html": "HTML",
}

All the filtering can be easily done.

If so, the deprecation warning should be something like:
DeprecationWarning: Request.is_ajax is deprecated. Given that the X-Requested-With header is not a part of any spec, it is not reliable. If you are doing content negotiation, see ..docs/media_type_negotiation. If you are serving different content if the requests are made via AJAX, choose a convention for *your* project to discern them or consider refactoring your code (making your views specific to one intent for each verb).

If you agree - in general - with this direction, I can find time to implement it.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.

Jure Erznožnik

unread,
Nov 21, 2019, 3:03:51 AM11/21/19
to django-d...@googlegroups.com

If possible, could the logic determining "the best match for your options" be overridable?

That way standard implementation would cater for 80/20 and everyone would still have an option to customise further.

LP,
Jure

Matemática A3K

unread,
Nov 22, 2019, 1:04:44 AM11/22/19
to django-d...@googlegroups.com
On Thu, Nov 21, 2019 at 3:03 AM Jure Erznožnik <jure.er...@gmail.com> wrote:

If possible, could the logic determining "the best match for your options" be overridable?

Actually it is quite straightforward, the client set in the header of its request all the media types it wants ("accepts as a response") with a preference parameter, i.e. "Accepts:JSON;q=0.9,XML;q=1,*/*;q=0.8" for wanting XML, if not JSON, if not anything else (the client's demand preferences).

You previously decide which one you support on your view, i.e. this view will deliver its content in HTML and JSON - it doesn't matter which one, that's what is available as a delivery (your offering).

Then you have to match which one of your offerings will better satisfy the client demand expressed by its preferences. What's best for you is already set when you express your preferences for the request in the header, the problem is that you may not have what is best for the client - but you may have something that satisfies it better than others: you don't have XML but you have JSON which it preffers to the rest.

It is the best match in that sense: given the client request preferences, respond with the more "satisfying" offering available.

This is done (mostly) to avoid a request, instead of doing a request for the available formats and do another with the choosen one, you request with the ones that makes you happy and then let the server find the best way to serve you, and for the spec writers resembles a negotiation ("I want A, B, C or D - I can give you B or D - Then D - OK"). 

If you filter the client's demand with your offering (you intersect them), i.e. "Accepts:JSON;q=0.9,XML;q=1,*/*;q=0.8" \Int "JSON, HTML" -> "Accepts:JSON;q=0.9,HTML;q=0.8", and then you order them, the one that you should serve would be always the first one, because if there is a tie, it is the same for the client - the client chose that the server choose between them (though you can specify a default media serving ordering that will handle also no preferences).

That's why "Just parsing them it and make them available in the request - filtered by the supported ones - as an ordered list ("accepted_media_types") and the preferred one ("preferred_media_type") seems sufficient to me."

With that API, you could implement easily content negotiation, both in functional views and CBVs. In CBVs, you can go further and use metaprograming techniques (like the ones used for "get_FOO_display()") to make the methods available "on the fly": "to_LABEL" and have the dispatcher choose the method according to "preferred_media_type"  - although making that explicit by the user may not be bad at all.

Note that this is mostly needed in REST APIs, where an endpoint is exposed to several clients which may have different requirements - i.e. a table in CSV, XML, etc. But for many Django applications this would be between browsers and the server, and between HTML and JSON if AJAX.

As converting to JSON it may seems straightforward, it may not - that's why DRF exists and addresses this problem in an excellent way (IMO :), so the user in "plain" Django will have to deal with serialization (https://docs.djangoproject.com/en/2.2/topics/serialization/) and not much besides defaulting to DjangoJsonEncoder for the context in CVB and for functional views (like https://github.com/jsocol/django-jsonview) can be done - at least that comes to my mind - for giving convenience :)

Jure Erznožnik

unread,
Nov 22, 2019, 2:49:14 AM11/22/19
to django-d...@googlegroups.com
>> "I want A, B, C or D - I can give you B or D - Then D - OK"

Yes, this. As long as I have the chance to determine B & D BEFORE the
content negotiation is complete. As opposed to having them fixed in code
by e.g. specifying to_json & to_html methods.

LP,
Jure
Reply all
Reply to author
Forward
0 new messages