Repository: django-oscar/django-oscar

Issue:
Add is_ajax helper to utils.py
Django 3.1 deprecated the `HttpRequest.is_ajax` method as it relies on a jQuery-specific way of signifying AJAX calls.

For most Oscar projects that's probably ok though, as they only use jQuery.

I see that in the current codebase `self.request.is_ajax()` was replaced by the less readable
```
self.request.headers.get('x-requested-with') == 'XMLHttpRequest'
```

I suggest adding a helper to `core/compat.py` to restore the old behaviour. Apart from readability that would enable oscar projects to check for AJAX requests in a consistent way with the built-in code.

I'm thinking of
```
def is_ajax(request):
    """
    Django 3.1 deprecated the  HttpRequest.is_ajax method as it relies on
    a jQuery-specific way of signifying AJAX calls.

    https://docs.djangoproject.com/en/3.1/releases/3.1/#id2
    """
    return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'
```

I'm happy to submit a PR for this ✌️ 


Discussion:
@SaturnFromTitan This sounds like a good idea. Please make the PR, adding the helper to `core/utils.py` (`core/compat.py` is for stuff we ideally want to remove one day).

Resolving patch:
diff --git a/src/oscar/apps/basket/views.py b/src/oscar/apps/basket/views.py
--- a/src/oscar/apps/basket/views.py
+++ b/src/oscar/apps/basket/views.py
@@ -14,7 +14,7 @@
 from oscar.core import ajax
 from oscar.core.compat import url_has_allowed_host_and_scheme
 from oscar.core.loading import get_class, get_classes, get_model
-from oscar.core.utils import redirect_to_referrer, safe_referrer
+from oscar.core.utils import is_ajax, redirect_to_referrer, safe_referrer
 
 Applicator = get_class('offer.applicator', 'Applicator')
 (BasketLineForm, AddToBasketForm, BasketVoucherForm, SavedLineForm) = get_classes(
@@ -186,7 +186,7 @@ def formset_valid(self, formset):
             response = super().formset_valid(formset)
 
         # If AJAX submission, don't redirect but reload the basket content HTML
-        if self.request.headers.get('x-requested-with') == 'XMLHttpRequest':
+        if is_ajax(self.request):
             # Reload basket and apply offers again
             self.request.basket = get_model('basket', 'Basket').objects.get(
                 id=self.request.basket.id)
@@ -247,7 +247,7 @@ def formset_invalid(self, formset):
             "Your basket has got some issues. "
             "Please correct any validation errors below."))
 
-        if self.request.headers.get('x-requested-with') == 'XMLHttpRequest':
+        if is_ajax(self.request):
             ctx = self.get_context_data(formset=formset,
                                         basket=self.request.basket)
             return self.json_response(ctx, flash_messages)
diff --git a/src/oscar/core/utils.py b/src/oscar/core/utils.py
--- a/src/oscar/core/utils.py
+++ b/src/oscar/core/utils.py
@@ -183,3 +183,15 @@ def round_half_up(money):
     Decimal('1.01')
     """
     return money.quantize(decimal.Decimal('0.01'), decimal.ROUND_HALF_UP)
+
+
+def is_ajax(request):
+    """
+    Django 3.1 deprecated the  HttpRequest.is_ajax method as it relies on
+    a jQuery-specific way of signifying AJAX calls.
+
+    https://docs.djangoproject.com/en/3.1/releases/3.1/#id2
+
+    For Oscar projects that's usually not a concern though.
+    """
+    return request.META.get("HTTP_X_REQUESTED_WITH") == "XMLHttpRequest"


Test patch: