diff --git a/.eslintrc.json b/.eslintrc.js
similarity index 83%
rename from .eslintrc.json
rename to .eslintrc.js
index 8a2452b7a..b0c799d88 100644
--- a/.eslintrc.json
+++ b/.eslintrc.js
@@ -1,7 +1,9 @@
-{
+module.exports = {
+ root: true,
"env": {
"browser": true,
- "es6": true
+ "es6": true,
+ node: true,
},
"extends": "eslint:recommended",
"parserOptions": {
@@ -17,4 +19,4 @@
"prefer-const": "error",
"semi": "error"
}
-}
+};
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c3cb00937..2059d37f8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -11,12 +11,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v4
with:
python-version: 3.8
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 9dece68ef..cb28e217e 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,7 +14,7 @@ jobs:
fail-fast: false
max-parallel: 5
matrix:
- python-version: ['3.8', '3.9', '3.10', '3.11']
+ python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
services:
mariadb:
@@ -30,12 +30,13 @@ jobs:
- 3306:3306
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
- name: Get pip cache dir
id: pip-cache
@@ -77,14 +78,16 @@ jobs:
fail-fast: false
max-parallel: 5
matrix:
- python-version: ['3.8', '3.9', '3.10', '3.11']
+ python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
database: [postgresql, postgis]
- # Add psycopg3 to our matrix for 3.10 and 3.11
+ # Add psycopg3 to our matrix for modern python versions
include:
- python-version: '3.10'
database: psycopg3
- python-version: '3.11'
database: psycopg3
+ - python-version: '3.12'
+ database: psycopg3
services:
postgres:
@@ -102,12 +105,13 @@ jobs:
--health-retries 5
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
- name: Get pip cache dir
id: pip-cache
@@ -152,15 +156,16 @@ jobs:
fail-fast: false
max-parallel: 5
matrix:
- python-version: ['3.8', '3.9', '3.10', '3.11']
+ python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
+ allow-prereleases: true
- name: Get pip cache dir
id: pip-cache
@@ -198,7 +203,7 @@ jobs:
runs-on: "ubuntu-latest"
needs: [sqlite, mysql, postgres]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
# Use latest, so it understands all syntax.
@@ -230,7 +235,7 @@ jobs:
fail-fast: false
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 001b07e34..c2f93ac73 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.4.0
+ rev: v4.5.0
hooks:
- id: check-toml
- id: check-yaml
@@ -14,7 +14,7 @@ repos:
hooks:
- id: doc8
- repo: https://github.com/adamchainz/django-upgrade
- rev: 1.14.0
+ rev: 1.15.0
hooks:
- id: django-upgrade
args: [--target-version, "3.2"]
@@ -24,14 +24,15 @@ repos:
- id: rst-backticks
- id: rst-directive-colons
- repo: https://github.com/pre-commit/mirrors-prettier
- rev: v3.0.1
+ rev: v4.0.0-alpha.8
hooks:
- id: prettier
+ entry: env PRETTIER_LEGACY_CLI=1 prettier
types_or: [javascript, css]
args:
- - --trailing-comma=es5
+ - --trailing-comma=es5
- repo: https://github.com/pre-commit/mirrors-eslint
- rev: v8.46.0
+ rev: v8.56.0
hooks:
- id: eslint
files: \.js?$
@@ -39,21 +40,16 @@ repos:
args:
- --fix
- repo: https://github.com/astral-sh/ruff-pre-commit
- rev: 'v0.0.282'
+ rev: 'v0.1.11'
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
-- repo: https://github.com/psf/black
- rev: 23.7.0
- hooks:
- - id: black
- language_version: python3
- entry: black --target-version=py38
+ - id: ruff-format
- repo: https://github.com/tox-dev/pyproject-fmt
- rev: 0.13.0
+ rev: 1.5.3
hooks:
- id: pyproject-fmt
- repo: https://github.com/abravalheri/validate-pyproject
- rev: v0.13
+ rev: v0.15
hooks:
- id: validate-pyproject
diff --git a/README.rst b/README.rst
index b10a9ad91..0eaaa6bd3 100644
--- a/README.rst
+++ b/README.rst
@@ -47,6 +47,9 @@ contributed by the community.
The current stable version of the Debug Toolbar is 4.1.0. It works on
Django ≥ 3.2.4.
+The Debug Toolbar does not currently support `Django's asynchronous views
+`_.
+
Documentation, including installation and configuration instructions, is
available at https://django-debug-toolbar.readthedocs.io/.
diff --git a/debug_toolbar/__init__.py b/debug_toolbar/__init__.py
index dbe08451f..9a8c2b24f 100644
--- a/debug_toolbar/__init__.py
+++ b/debug_toolbar/__init__.py
@@ -4,7 +4,7 @@
# Do not use pkg_resources to find the version but set it here directly!
# see issue #1446
-VERSION = "4.2.0"
+VERSION = "4.3.0"
# Code that discovers files or modules in INSTALLED_APPS imports this module.
urls = "debug_toolbar.urls", APP_NAME
diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py
index 9d10229ad..2b7742400 100644
--- a/debug_toolbar/panels/profiling.py
+++ b/debug_toolbar/panels/profiling.py
@@ -42,10 +42,14 @@ def is_project_func(self):
"""
if hasattr(settings, "BASE_DIR"):
file_name, _, _ = self.func
- return (
- str(settings.BASE_DIR) in file_name
- and "/site-packages/" not in file_name
- and "/dist-packages/" not in file_name
+ base_dir = str(settings.BASE_DIR)
+
+ file_name = os.path.normpath(file_name)
+ base_dir = os.path.normpath(base_dir)
+
+ return file_name.startswith(base_dir) and not any(
+ directory in file_name.split(os.path.sep)
+ for directory in ["site-packages", "dist-packages"]
)
return None
diff --git a/debug_toolbar/panels/sql/tracking.py b/debug_toolbar/panels/sql/tracking.py
index 0c53dc2c5..b5fc81234 100644
--- a/debug_toolbar/panels/sql/tracking.py
+++ b/debug_toolbar/panels/sql/tracking.py
@@ -109,21 +109,6 @@ class NormalCursorMixin(DjDTCursorWrapperMixin):
Wraps a cursor and logs queries.
"""
- def _quote_expr(self, element):
- if isinstance(element, str):
- return "'%s'" % element.replace("'", "''")
- else:
- return repr(element)
-
- def _quote_params(self, params):
- if not params:
- return params
- if isinstance(params, dict):
- return {key: self._quote_expr(value) for key, value in params.items()}
- if isinstance(params, tuple):
- return tuple(self._quote_expr(p) for p in params)
- return [self._quote_expr(p) for p in params]
-
def _decode(self, param):
if PostgresJson and isinstance(param, PostgresJson):
# psycopg3
@@ -157,9 +142,7 @@ def _last_executed_query(self, sql, params):
# process during the .last_executed_query() call.
self.db._djdt_logger = None
try:
- return self.db.ops.last_executed_query(
- self.cursor, sql, self._quote_params(params)
- )
+ return self.db.ops.last_executed_query(self.cursor, sql, params)
finally:
self.db._djdt_logger = self.logger
diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py
index 72565f016..f8c9242ca 100644
--- a/debug_toolbar/panels/templates/panel.py
+++ b/debug_toolbar/panels/templates/panel.py
@@ -83,58 +83,11 @@ def _store_template_info(self, sender, **kwargs):
if is_debug_toolbar_template:
return
- context_list = []
- for context_layer in context.dicts:
- if hasattr(context_layer, "items") and context_layer:
- # Check if the layer is in the cache.
- pformatted = None
- for key_values, _pformatted in self.pformat_layers:
- if key_values == context_layer:
- pformatted = _pformatted
- break
-
- if pformatted is None:
- temp_layer = {}
- for key, value in context_layer.items():
- # Replace any request elements - they have a large
- # Unicode representation and the request data is
- # already made available from the Request panel.
- if isinstance(value, http.HttpRequest):
- temp_layer[key] = "<>"
- # Replace the debugging sql_queries element. The SQL
- # data is already made available from the SQL panel.
- elif key == "sql_queries" and isinstance(value, list):
- temp_layer[key] = "<>"
- # Replace LANGUAGES, which is available in i18n context
- # processor
- elif key == "LANGUAGES" and isinstance(value, tuple):
- temp_layer[key] = "<>"
- # QuerySet would trigger the database: user can run the
- # query from SQL Panel
- elif isinstance(value, (QuerySet, RawQuerySet)):
- temp_layer[key] = "<<{} of {}>>".format(
- value.__class__.__name__.lower(),
- value.model._meta.label,
- )
- else:
- token = allow_sql.set(False) # noqa: FBT003
- try:
- saferepr(value) # this MAY trigger a db query
- except SQLQueryTriggered:
- temp_layer[key] = "<>"
- except UnicodeEncodeError:
- temp_layer[key] = "<>"
- except Exception:
- temp_layer[key] = "<>"
- else:
- temp_layer[key] = value
- finally:
- allow_sql.reset(token)
- pformatted = pformat(temp_layer)
- self.pformat_layers.append((context_layer, pformatted))
- context_list.append(pformatted)
-
- kwargs["context"] = context_list
+ kwargs["context"] = [
+ context_layer
+ for context_layer in context.dicts
+ if hasattr(context_layer, "items") and context_layer
+ ]
kwargs["context_processors"] = getattr(context, "context_processors", None)
self.templates.append(kwargs)
@@ -167,6 +120,63 @@ def enable_instrumentation(self):
def disable_instrumentation(self):
template_rendered.disconnect(self._store_template_info)
+ def process_context_list(self, context_layers):
+ context_list = []
+ for context_layer in context_layers:
+ # Check if the layer is in the cache.
+ pformatted = None
+ for key_values, _pformatted in self.pformat_layers:
+ if key_values == context_layer:
+ pformatted = _pformatted
+ break
+
+ if pformatted is None:
+ temp_layer = {}
+ for key, value in context_layer.items():
+ # Do not force evaluating LazyObject
+ if hasattr(value, "_wrapped"):
+ # SimpleLazyObject has __repr__ which includes actual value
+ # if it has been already evaluated
+ temp_layer[key] = repr(value)
+ # Replace any request elements - they have a large
+ # Unicode representation and the request data is
+ # already made available from the Request panel.
+ elif isinstance(value, http.HttpRequest):
+ temp_layer[key] = "<>"
+ # Replace the debugging sql_queries element. The SQL
+ # data is already made available from the SQL panel.
+ elif key == "sql_queries" and isinstance(value, list):
+ temp_layer[key] = "<>"
+ # Replace LANGUAGES, which is available in i18n context
+ # processor
+ elif key == "LANGUAGES" and isinstance(value, tuple):
+ temp_layer[key] = "<>"
+ # QuerySet would trigger the database: user can run the
+ # query from SQL Panel
+ elif isinstance(value, (QuerySet, RawQuerySet)):
+ temp_layer[
+ key
+ ] = f"<<{value.__class__.__name__.lower()} of {value.model._meta.label}>>"
+ else:
+ token = allow_sql.set(False) # noqa: FBT003
+ try:
+ saferepr(value) # this MAY trigger a db query
+ except SQLQueryTriggered:
+ temp_layer[key] = "<>"
+ except UnicodeEncodeError:
+ temp_layer[key] = "<>"
+ except Exception:
+ temp_layer[key] = "<>"
+ else:
+ temp_layer[key] = value
+ finally:
+ allow_sql.reset(token)
+ pformatted = pformat(temp_layer)
+ self.pformat_layers.append((context_layer, pformatted))
+ context_list.append(pformatted)
+
+ return context_list
+
def generate_stats(self, request, response):
template_context = []
for template_data in self.templates:
@@ -182,8 +192,11 @@ def generate_stats(self, request, response):
info["template"] = template
# Clean up context for better readability
if self.toolbar.config["SHOW_TEMPLATE_CONTEXT"]:
- context_list = template_data.get("context", [])
- info["context"] = "\n".join(context_list)
+ if "context_list" not in template_data:
+ template_data["context_list"] = self.process_context_list(
+ template_data.get("context", [])
+ )
+ info["context"] = "\n".join(template_data["context_list"])
template_context.append(info)
# Fetch context_processors/template_dirs from any template
diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py
index eb6b59209..1df24527d 100644
--- 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,
}
diff --git a/debug_toolbar/static/debug_toolbar/js/history.js b/debug_toolbar/static/debug_toolbar/js/history.js
index b30fcabae..314ddb3ef 100644
--- a/debug_toolbar/static/debug_toolbar/js/history.js
+++ b/debug_toolbar/static/debug_toolbar/js/history.js
@@ -104,3 +104,6 @@ $$.on(djDebug, "click", ".refreshHistory", function (event) {
event.preventDefault();
refreshHistory();
});
+// We don't refresh the whole toolbar each fetch or ajax request,
+// so we need to refresh the history when we open the panel
+$$.onPanelRender(djDebug, "HistoryPanel", refreshHistory);
diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js
index 9546ef27e..199616336 100644
--- a/debug_toolbar/static/debug_toolbar/js/toolbar.js
+++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js
@@ -17,8 +17,10 @@ function getDebugElement() {
const djdt = {
handleDragged: false,
+ needUpdateOnFetch: false,
init() {
const djDebug = getDebugElement();
+ djdt.needUpdateOnFetch = djDebug.dataset.updateOnFetch === "True";
$$.on(djDebug, "click", "#djDebugPanelList li a", function (event) {
event.preventDefault();
if (!this.className) {
@@ -226,7 +228,7 @@ const djdt = {
const handle = document.getElementById("djDebugToolbarHandle");
// set handle position
const handleTop = Math.min(
- localStorage.getItem("djdt.top") || 0,
+ localStorage.getItem("djdt.top") || 265,
window.innerHeight - handle.offsetWidth
);
handle.style.top = handleTop + "px";
@@ -274,7 +276,9 @@ const djdt = {
storeId = encodeURIComponent(storeId);
const dest = `${sidebarUrl}?store_id=${storeId}`;
slowjax(dest).then(function (data) {
- replaceToolbarState(storeId, data);
+ if (djdt.needUpdateOnFetch){
+ replaceToolbarState(storeId, data);
+ }
});
}
diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html
index 5447970af..6f4967f21 100644
--- a/debug_toolbar/templates/debug_toolbar/base.html
+++ b/debug_toolbar/templates/debug_toolbar/base.html
@@ -16,7 +16,7 @@
data-sidebar-url="{{ history_url }}"
{% endif %}
data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}"
- {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>
+ {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}">