Repository: jazzband/django-debug-toolbar

Issue:
New AJAX request resets whole view if HistoryPanel is enabled.
Maybe I am doing something wrong, but I find working with DDT with HistoryPanel enabled quite annoying.

I have notifications on my website which makes request every ~5 seconds if there is anything new.

If I have HistoryPanel enabled in DDT this means that if I am exploring some request (from history or just the last one) and this AJAX notification request is made I loose everything I am seeing and the whole DDT resets to the newest (notification) request.

Would it be possible to set DDT so that it switches the request only if I explicitly select it from history?


Discussion:
Makes sense to me. I have also been annoyed by this behavior at times. I think automatically refreshing the history panel should stay, but it shouldn't automatically switch to the newest request in case the newest request used XHR/fetch or something. That's just my opinion though.

If someone uses something like htmx/hotwire/turbolinks for navigation (https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#htmx) we should have a solution there as well, so that those users still get the auto update if possible. Maybe they have to opt in to updating to always the last request if distinguishing htmx boosts and other fetch-es isn't possible, but that would be acceptable.
I like this improvement. I've tolerated it as well. I like the idea of putting it in local storage to keep track of what was configured. 
@PetrDlouhy do you have the availability implement a solution for this?
I don't have very much time right now, but I might get to it some time. Probably next time I would bump into this issue.

Resolving patch:
diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py
--- a/debug_toolbar/settings.py
+++ b/debug_toolbar/settings.py
@@ -42,6 +42,7 @@
     "SQL_WARNING_THRESHOLD": 500,  # milliseconds
     "OBSERVE_REQUEST_CALLBACK": "debug_toolbar.toolbar.observe_request",
     "TOOLBAR_LANGUAGE": None,
+    "UPDATE_ON_FETCH": False,
 }
 
 


Test patch:
diff --git a/tests/templates/ajax/ajax.html b/tests/templates/ajax/ajax.html
new file mode 100644
--- /dev/null
+++ b/tests/templates/ajax/ajax.html
@@ -0,0 +1,21 @@
+{% extends "base.html" %}
+{% block content %}
+  <div id="click_for_ajax">click for ajax</div>
+
+  <script>
+
+  let click_for_ajax = document.getElementById("click_for_ajax");
+  function send_ajax() {
+    let xhr = new XMLHttpRequest();
+    let url = '/json_view/';
+    xhr.open("GET", url, true);
+    xhr.onreadystatechange = function () {
+        if (this.readyState == 4 && this.status == 200) {
+            console.log(this.responseText);
+        }
+    }
+    xhr.send();
+  }
+  document.addEventListener("click", (event) => {send_ajax()});
+  </script>
+{% endblock %}
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -1,5 +1,6 @@
 import os
 import re
+import time
 import unittest
 
 import html5lib
@@ -749,3 +750,24 @@ def test_toolbar_language_will_render_to_locale_when_set_both(self):
         )
         self.assertIn("Query", table.text)
         self.assertIn("Action", table.text)
+
+    def test_ajax_dont_refresh(self):
+        self.get("/ajax/")
+        make_ajax = self.selenium.find_element(By.ID, "click_for_ajax")
+        make_ajax.click()
+        history_panel = self.selenium.find_element(By.ID, "djdt-HistoryPanel")
+        self.assertIn("/ajax/", history_panel.text)
+        self.assertNotIn("/json_view/", history_panel.text)
+
+    @override_settings(DEBUG_TOOLBAR_CONFIG={"UPDATE_ON_FETCH": True})
+    def test_ajax_refresh(self):
+        self.get("/ajax/")
+        make_ajax = self.selenium.find_element(By.ID, "click_for_ajax")
+        make_ajax.click()
+        # Need to wait until the ajax request is over and json_view is displayed on the toolbar
+        time.sleep(2)
+        history_panel = self.wait.until(
+            lambda selenium: self.selenium.find_element(By.ID, "djdt-HistoryPanel")
+        )
+        self.assertNotIn("/ajax/", history_panel.text)
+        self.assertIn("/json_view/", history_panel.text)
diff --git a/tests/urls.py b/tests/urls.py
--- a/tests/urls.py
+++ b/tests/urls.py
@@ -21,6 +21,7 @@
     path("cached_low_level_view/", views.cached_low_level_view),
     path("json_view/", views.json_view),
     path("redirect/", views.redirect_view),
+    path("ajax/", views.ajax_view),
     path("login_without_redirect/", LoginView.as_view(redirect_field_name=None)),
     path("admin/", admin.site.urls),
     path("__debug__/", include("debug_toolbar.urls")),
diff --git a/tests/views.py b/tests/views.py
--- a/tests/views.py
+++ b/tests/views.py
@@ -58,3 +58,7 @@ def listcomp_view(request):
 
 def redirect_view(request):
     return HttpResponseRedirect("/regular/redirect/")
+
+
+def ajax_view(request):
+    return render(request, "ajax/ajax.html")