diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 73733c293..7dd8409a8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,10 +30,10 @@ jobs: - 3306:3306 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -43,7 +43,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: @@ -71,7 +71,7 @@ jobs: DB_PORT: 3306 - name: Upload coverage data - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: coverage-data path: ".coverage.*" @@ -101,10 +101,10 @@ jobs: --health-retries 5 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -114,7 +114,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: @@ -140,7 +140,7 @@ jobs: DB_PORT: 5432 - name: Upload coverage data - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: coverage-data path: ".coverage.*" @@ -154,10 +154,10 @@ jobs: python-version: ['3.7', '3.8', '3.9', '3.10'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -167,7 +167,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: @@ -187,7 +187,7 @@ jobs: DB_NAME: ":memory:" - name: Upload coverage data - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: coverage-data path: ".coverage.*" @@ -197,8 +197,8 @@ jobs: runs-on: "ubuntu-latest" needs: [sqlite, mysql, postgres] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: # Use latest, so it understands all syntax. python-version: "3.10" @@ -206,7 +206,7 @@ jobs: - run: python -m pip install --upgrade coverage - name: Download coverage data. - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: name: coverage-data @@ -217,7 +217,7 @@ jobs: python -m coverage report - name: Upload HTML report if check failed. - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: html-report path: htmlcov @@ -229,10 +229,10 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.8 @@ -242,7 +242,7 @@ jobs: echo "::set-output name=dir::$(pip cache dir)" - name: Cache - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ${{ steps.pip-cache.outputs.dir }} key: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a0e179ad..36baad66d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,20 +7,20 @@ repos: - id: trailing-whitespace - id: mixed-line-ending - repo: https://github.com/pycqa/flake8 - rev: 4.0.1 + rev: 5.0.4 hooks: - id: flake8 - repo: https://github.com/pycqa/doc8 - rev: 0.11.2 + rev: v1.0.0 hooks: - id: doc8 - repo: https://github.com/asottile/pyupgrade - rev: v2.34.0 + rev: v2.37.3 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/adamchainz/django-upgrade - rev: 1.7.0 + rev: 1.8.0 hooks: - id: django-upgrade args: [--target-version, "3.2"] @@ -43,7 +43,7 @@ repos: - id: prettier types_or: [javascript, css] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.18.0 + rev: v8.22.0 hooks: - id: eslint files: \.js?$ @@ -51,7 +51,7 @@ repos: args: - --fix - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black language_version: python3 diff --git a/README.rst b/README.rst index 2c1ba9730..c7ea51bd6 100644 --- a/README.rst +++ b/README.rst @@ -44,8 +44,8 @@ Here's a screenshot of the toolbar in action: In addition to the built-in panels, a number of third-party panels are contributed by the community. -The current stable version of the Debug Toolbar is 3.5.0. It works on -Django ≥ 3.2. +The current stable version of the Debug Toolbar is 3.6.0. It works on +Django ≥ 3.2.4. 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 c9834b8e3..17f1f9e69 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 = "3.5.0" +VERSION = "3.6.0" # Code that discovers files or modules in INSTALLED_APPS imports this module. urls = "debug_toolbar.urls", APP_NAME diff --git a/debug_toolbar/decorators.py b/debug_toolbar/decorators.py index 2abfb22f9..8114b05d7 100644 --- a/debug_toolbar/decorators.py +++ b/debug_toolbar/decorators.py @@ -1,6 +1,6 @@ import functools -from django.http import Http404, HttpResponseBadRequest +from django.http import Http404 def require_show_toolbar(view): @@ -15,21 +15,3 @@ def inner(request, *args, **kwargs): return view(request, *args, **kwargs) return inner - - -def signed_data_view(view): - """Decorator that handles unpacking a signed data form""" - - @functools.wraps(view) - def inner(request, *args, **kwargs): - from debug_toolbar.forms import SignedDataForm - - data = request.GET if request.method == "GET" else request.POST - signed_form = SignedDataForm(data) - if signed_form.is_valid(): - return view( - request, *args, verified_data=signed_form.verified_data(), **kwargs - ) - return HttpResponseBadRequest("Invalid signature") - - return inner diff --git a/debug_toolbar/forms.py b/debug_toolbar/forms.py index 3c7a45a07..1263c3aff 100644 --- a/debug_toolbar/forms.py +++ b/debug_toolbar/forms.py @@ -21,7 +21,6 @@ class PanelForm(forms.Form): panel_form = PanelForm(signed_form.verified_data) if panel_form.is_valid(): # Success - Or wrap the FBV with ``debug_toolbar.decorators.signed_data_view`` """ salt = "django_debug_toolbar" diff --git a/debug_toolbar/panels/history/panel.py b/debug_toolbar/panels/history/panel.py index 596bcfb4a..2e637083a 100644 --- a/debug_toolbar/panels/history/panel.py +++ b/debug_toolbar/panels/history/panel.py @@ -24,7 +24,7 @@ def get_headers(self, request): observe_request = self.toolbar.get_observe_request() store_id = getattr(self.toolbar, "store_id") if store_id and observe_request(request): - headers["DJDT-STORE-ID"] = store_id + headers["djdt-store-id"] = store_id return headers @property diff --git a/debug_toolbar/panels/sql/utils.py b/debug_toolbar/panels/sql/utils.py index be607cec6..0fbba3e90 100644 --- a/debug_toolbar/panels/sql/utils.py +++ b/debug_toolbar/panels/sql/utils.py @@ -69,7 +69,7 @@ def simplify(sql): def contrasting_color_generator(): """ - Generate constrasting colors by varying most significant bit of RGB first, + Generate contrasting colors by varying most significant bit of RGB first, and then vary subsequent bits systematically. """ diff --git a/debug_toolbar/panels/sql/views.py b/debug_toolbar/panels/sql/views.py index 49ffee515..fabca7a57 100644 --- a/debug_toolbar/panels/sql/views.py +++ b/debug_toolbar/panels/sql/views.py @@ -2,15 +2,27 @@ from django.template.loader import render_to_string from django.views.decorators.csrf import csrf_exempt -from debug_toolbar.decorators import require_show_toolbar, signed_data_view +from debug_toolbar.decorators import require_show_toolbar +from debug_toolbar.forms import SignedDataForm from debug_toolbar.panels.sql.forms import SQLSelectForm +def get_signed_data(request): + """Unpack a signed data form, if invalid returns None""" + data = request.GET if request.method == "GET" else request.POST + signed_form = SignedDataForm(data) + if signed_form.is_valid(): + return signed_form.verified_data() + return None + + @csrf_exempt @require_show_toolbar -@signed_data_view -def sql_select(request, verified_data): +def sql_select(request): """Returns the output of the SQL SELECT statement""" + verified_data = get_signed_data(request) + if not verified_data: + return HttpResponseBadRequest("Invalid signature") form = SQLSelectForm(verified_data) if form.is_valid(): @@ -35,9 +47,11 @@ def sql_select(request, verified_data): @csrf_exempt @require_show_toolbar -@signed_data_view -def sql_explain(request, verified_data): +def sql_explain(request): """Returns the output of the SQL EXPLAIN on the given query""" + verified_data = get_signed_data(request) + if not verified_data: + return HttpResponseBadRequest("Invalid signature") form = SQLSelectForm(verified_data) if form.is_valid(): @@ -71,9 +85,11 @@ def sql_explain(request, verified_data): @csrf_exempt @require_show_toolbar -@signed_data_view -def sql_profile(request, verified_data): +def sql_profile(request): """Returns the output of running the SQL and getting the profiling statistics""" + verified_data = get_signed_data(request) + if not verified_data: + return HttpResponseBadRequest("Invalid signature") form = SQLSelectForm(verified_data) if form.is_valid(): diff --git a/debug_toolbar/panels/templates/views.py b/debug_toolbar/panels/templates/views.py index 8d6d634d3..134b3d476 100644 --- a/debug_toolbar/panels/templates/views.py +++ b/debug_toolbar/panels/templates/views.py @@ -3,7 +3,7 @@ from django.template import Origin, TemplateDoesNotExist from django.template.engine import Engine from django.template.loader import render_to_string -from django.utils.safestring import mark_safe +from django.utils.html import format_html, mark_safe from debug_toolbar.decorators import require_show_toolbar @@ -50,12 +50,11 @@ def template_source(request): from pygments import highlight from pygments.formatters import HtmlFormatter from pygments.lexers import HtmlDjangoLexer - + except ModuleNotFoundError: + source = format_html("{}", source) + else: source = highlight(source, HtmlDjangoLexer(), HtmlFormatter()) source = mark_safe(source) - source.pygmentized = True - except ImportError: - pass content = render_to_string( "debug_toolbar/panels/template_source.html", diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 860c72110..1c06be7fa 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -264,8 +264,13 @@ const djdt = { const origOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function () { this.addEventListener("load", function () { - let store_id = this.getResponseHeader("djdt-store-id"); - if (store_id !== null) { + // Chromium emits a "Refused to get unsafe header" uncatchable warning + // when the header can't be fetched. While it doesn't impede execution + // it's worrisome to developers. + if ( + this.getAllResponseHeaders().indexOf("djdt-store-id") >= 0 + ) { + let store_id = this.getResponseHeader("djdt-store-id"); store_id = encodeURIComponent(store_id); const dest = `${sidebar_url}?store_id=${store_id}`; slowjax(dest).then(function (data) { diff --git a/debug_toolbar/templates/debug_toolbar/panels/template_source.html b/debug_toolbar/templates/debug_toolbar/panels/template_source.html index 229ea83e4..397c44b24 100644 --- a/debug_toolbar/templates/debug_toolbar/panels/template_source.html +++ b/debug_toolbar/templates/debug_toolbar/panels/template_source.html @@ -5,10 +5,6 @@

{% trans "Template source:" %} {{ template_name }}

- {% if not source.pygmentized %} - {{ source }} - {% else %} - {{ source }} - {% endif %} + {{ source }}
diff --git a/docs/changes.rst b/docs/changes.rst index 25ef409fc..df6be99f2 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -1,6 +1,23 @@ Change log ========== +Pending +------- + +3.6.0 (2022-08-17) +------------------ + +* Remove decorator ``signed_data_view`` as it was causing issues with + `django-urlconfchecks `__. +* Added pygments to the test environment and fixed a crash when using the + template panel with Django 4.1 and pygments installed. +* Stayed on top of pre-commit hook and GitHub actions updates. +* Added some workarounds to avoid a Chromium warning which was worrisome to + developers. +* Avoided using deprecated Selenium methods to find elements. +* Raised the minimum Django version from 3.2 to 3.2.4 so that we can take + advantage of backported improvements to the cache connection handler. + 3.5.0 (2022-06-23) ------------------ diff --git a/docs/conf.py b/docs/conf.py index 374acd1d2..476a055de 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ copyright = copyright.format(datetime.date.today().year) # The full version, including alpha/beta/rc tags -release = "3.5.0" +release = "3.6.0" # -- General configuration --------------------------------------------------- diff --git a/docs/configuration.rst b/docs/configuration.rst index 7577be62d..6f4084ad5 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -142,7 +142,7 @@ Toolbar options * ``OBSERVE_REQUEST_CALLBACK`` - Default: ``'debug_toolbar.middleware.observe_request'`` + Default: ``'debug_toolbar.toolbar.observe_request'`` This is the dotted path to a function used for determining whether the toolbar should update on AJAX requests or not. The default checks are that diff --git a/docs/installation.rst b/docs/installation.rst index ad36fc2c8..d343849e4 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -138,7 +138,6 @@ option. If using Docker the following will set your ``INTERNAL_IPS`` correctly in Debug mode:: if DEBUG: - import os # only if you haven't already imported this import socket # only if you haven't already imported this hostname, _, ips = socket.gethostbyname_ex(socket.gethostname()) INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + ["127.0.0.1", "10.0.2.2"] diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index 0ca7eb8b0..ee911963f 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -1,5 +1,6 @@ backend backends +backported checkbox contrib django @@ -30,9 +31,12 @@ pylibmc Pympler querysets refactoring +resizing +spellchecking spooler stacktrace stacktraces +startup timeline tox Transifex diff --git a/example/screenshot.py b/example/screenshot.py index 8c1135eb2..129465d79 100644 --- a/example/screenshot.py +++ b/example/screenshot.py @@ -5,6 +5,7 @@ import subprocess from time import sleep +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait @@ -54,7 +55,7 @@ def set_viewport_size(selenium, width, height): def submit_form(selenium, data): url = selenium.current_url for name, value in data.items(): - el = selenium.find_element_by_name(name) + el = selenium.find_element(By.NAME, name) el.send_keys(value) el.send_keys(Keys.RETURN) WebDriverWait(selenium, timeout=5).until(EC.url_changes(url)) @@ -72,12 +73,12 @@ def main(): selenium.get("http://localhost:8000/admin/auth/user/") # Check if SQL Panel is already visible: - sql_panel = selenium.find_element_by_id("djdt-SQLPanel") + sql_panel = selenium.find_element(By.ID, "djdt-SQLPanel") if not sql_panel: # Open the admin sidebar. - el = selenium.find_element_by_id("djDebugToolbarHandle") + el = selenium.find_element(By.ID, "djDebugToolbarHandle") el.click() - sql_panel = selenium.find_element_by_id("djdt-SQLPanel") + sql_panel = selenium.find_element(By.ID, "djdt-SQLPanel") # Open the SQL panel. sql_panel.click() diff --git a/setup.cfg b/setup.cfg index 2fe12e38d..5daaee0bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = django-debug-toolbar -version = 3.5.0 +version = 3.6.0 description = A configurable set of panels that display various debug information about the current request/response. long_description = file: README.rst long_description_content_type = text/x-rst @@ -16,6 +16,7 @@ classifiers = Framework :: Django Framework :: Django :: 3.2 Framework :: Django :: 4.0 + Framework :: Django :: 4.1 Intended Audience :: Developers License :: OSI Approved :: BSD License Operating System :: OS Independent @@ -31,7 +32,7 @@ classifiers = [options] python_requires = >=3.7 install_requires = - Django >= 3.2 + Django >= 3.2.4 sqlparse >= 0.2.0 packages = find: include_package_data = true diff --git a/tests/panels/test_history.py b/tests/panels/test_history.py index 9f1457049..326fb55b6 100644 --- a/tests/panels/test_history.py +++ b/tests/panels/test_history.py @@ -99,6 +99,20 @@ def test_history_sidebar_invalid(self): response = self.client.get(reverse("djdt:history_sidebar")) self.assertEqual(response.status_code, 400) + def test_history_headers(self): + """Validate the headers injected from the history panel.""" + response = self.client.get("/json_view/") + store_id = list(DebugToolbar._store)[0] + self.assertEqual(response.headers["djdt-store-id"], store_id) + + @override_settings( + DEBUG_TOOLBAR_CONFIG={"OBSERVE_REQUEST_CALLBACK": lambda request: False} + ) + def test_history_headers_unobserved(self): + """Validate the headers aren't injected from the history panel.""" + response = self.client.get("/json_view/") + self.assertNotIn("djdt-store-id", response.headers) + def test_history_sidebar(self): """Validate the history sidebar view.""" self.client.get("/json_view/") diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index f078820c8..575661df1 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -444,7 +444,7 @@ def test_prettify_sql(self): # Reset the queries self.panel._queries = [] - # Run it again, but with prettyify off. Verify that it's different. + # Run it again, but with prettify off. Verify that it's different. dt_settings.get_config()["PRETTIFY_SQL"] = False list(User.objects.filter(username__istartswith="spam")) response = self.panel.process_request(self.request) @@ -453,7 +453,7 @@ def test_prettify_sql(self): self.assertNotEqual(pretty_sql, self.panel._queries[-1]["sql"]) self.panel._queries = [] - # Run it again, but with prettyify back on. + # Run it again, but with prettify back on. # This is so we don't have to check what PRETTIFY_SQL does exactly, # but we know it's doing something. dt_settings.get_config()["PRETTIFY_SQL"] = True diff --git a/tests/test_integration.py b/tests/test_integration.py index 016b52217..e9962b32b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -482,18 +482,18 @@ def wait(self): def test_basic(self): self.get("/regular/basic/") - version_panel = self.selenium.find_element_by_id("VersionsPanel") + version_panel = self.selenium.find_element(By.ID, "VersionsPanel") # Versions panel isn't loaded with self.assertRaises(NoSuchElementException): - version_panel.find_element_by_tag_name("table") + version_panel.find_element(By.TAG_NAME, "table") # Click to show the versions panel - self.selenium.find_element_by_class_name("VersionsPanel").click() + self.selenium.find_element(By.CLASS_NAME, "VersionsPanel").click() # Version panel loads table = self.wait.until( - lambda selenium: version_panel.find_element_by_tag_name("table") + lambda selenium: version_panel.find_element(By.TAG_NAME, "table") ) self.assertIn("Name", table.text) self.assertIn("Version", table.text) @@ -505,10 +505,10 @@ def test_basic(self): ) def test_basic_jinja(self): self.get("/regular_jinja/basic") - template_panel = self.selenium.find_element_by_id("TemplatesPanel") + template_panel = self.selenium.find_element(By.ID, "TemplatesPanel") # Click to show the template panel - self.selenium.find_element_by_class_name("TemplatesPanel").click() + self.selenium.find_element(By.CLASS_NAME, "TemplatesPanel").click() self.assertIn("Templates (2 rendered)", template_panel.text) self.assertIn("base.html", template_panel.text) @@ -523,18 +523,20 @@ def test_rerender_on_history_switch(self): self.get("/regular_jinja/basic") # Make a new request so the history panel has more than one option. self.get("/execute_sql/") - template_panel = self.selenium.find_element_by_id("HistoryPanel") + template_panel = self.selenium.find_element(By.ID, "HistoryPanel") # Record the current side panel of buttons for later comparison. - previous_button_panel = self.selenium.find_element_by_id( - "djDebugPanelList" + previous_button_panel = self.selenium.find_element( + By.ID, "djDebugPanelList" ).text # Click to show the history panel - self.selenium.find_element_by_class_name("HistoryPanel").click() + self.selenium.find_element(By.CLASS_NAME, "HistoryPanel").click() # Click to switch back to the jinja page view snapshot - list(template_panel.find_elements_by_css_selector("button"))[-1].click() + list(template_panel.find_elements(By.CSS_SELECTOR, "button"))[-1].click() - current_button_panel = self.selenium.find_element_by_id("djDebugPanelList").text + current_button_panel = self.selenium.find_element( + By.ID, "djDebugPanelList" + ).text # Verify the button side panels have updated. self.assertNotEqual(previous_button_panel, current_button_panel) self.assertNotIn("1 query", current_button_panel) @@ -543,14 +545,14 @@ def test_rerender_on_history_switch(self): @override_settings(DEBUG_TOOLBAR_CONFIG={"RESULTS_CACHE_SIZE": 0}) def test_expired_store(self): self.get("/regular/basic/") - version_panel = self.selenium.find_element_by_id("VersionsPanel") + version_panel = self.selenium.find_element(By.ID, "VersionsPanel") # Click to show the version panel - self.selenium.find_element_by_class_name("VersionsPanel").click() + self.selenium.find_element(By.CLASS_NAME, "VersionsPanel").click() # Version panel doesn't loads error = self.wait.until( - lambda selenium: version_panel.find_element_by_tag_name("p") + lambda selenium: version_panel.find_element(By.TAG_NAME, "p") ) self.assertIn("Data for this panel isn't available anymore.", error.text) @@ -574,31 +576,31 @@ def test_expired_store(self): ) def test_django_cached_template_loader(self): self.get("/regular/basic/") - version_panel = self.selenium.find_element_by_id("TemplatesPanel") + version_panel = self.selenium.find_element(By.ID, "TemplatesPanel") # Click to show the templates panel - self.selenium.find_element_by_class_name("TemplatesPanel").click() + self.selenium.find_element(By.CLASS_NAME, "TemplatesPanel").click() # Templates panel loads trigger = self.wait.until( - lambda selenium: version_panel.find_element_by_css_selector(".remoteCall") + lambda selenium: version_panel.find_element(By.CSS_SELECTOR, ".remoteCall") ) trigger.click() # Verify the code is displayed self.wait.until( - lambda selenium: self.selenium.find_element_by_css_selector( - "#djDebugWindow code" + lambda selenium: self.selenium.find_element( + By.CSS_SELECTOR, "#djDebugWindow code" ) ) def test_sql_action_and_go_back(self): self.get("/execute_sql/") - sql_panel = self.selenium.find_element_by_id("SQLPanel") - debug_window = self.selenium.find_element_by_id("djDebugWindow") + sql_panel = self.selenium.find_element(By.ID, "SQLPanel") + debug_window = self.selenium.find_element(By.ID, "djDebugWindow") # Click to show the SQL panel - self.selenium.find_element_by_class_name("SQLPanel").click() + self.selenium.find_element(By.CLASS_NAME, "SQLPanel").click() # SQL panel loads button = self.wait.until( @@ -611,7 +613,7 @@ def test_sql_action_and_go_back(self): self.assertIn("SQL selected", debug_window.text) # Close the SQL selected window - debug_window.find_element_by_class_name("djDebugClose").click() + debug_window.find_element(By.CLASS_NAME, "djDebugClose").click() self.wait.until(EC.invisibility_of_element(debug_window)) # SQL panel is still visible @@ -620,7 +622,7 @@ def test_sql_action_and_go_back(self): @override_settings(DEBUG_TOOLBAR_PANELS=["tests.test_integration.BuggyPanel"]) def test_displays_server_error(self): self.get("/regular/basic/") - debug_window = self.selenium.find_element_by_id("djDebugWindow") - self.selenium.find_element_by_class_name("BuggyPanel").click() + debug_window = self.selenium.find_element(By.ID, "djDebugWindow") + self.selenium.find_element(By.CLASS_NAME, "BuggyPanel").click() self.wait.until(EC.visibility_of(debug_window)) self.assertEqual(debug_window.text, "»\n500: Internal Server Error") diff --git a/tox.ini b/tox.ini index 5485a2742..ad2e6c090 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ envlist = deps = dj32: django~=3.2.9 dj40: django~=4.0.0 - dj41: django>=4.1b1,<4.2 + dj41: django~=4.1.0 postgresql: psycopg2-binary postgis: psycopg2-binary mysql: mysqlclient @@ -17,6 +17,7 @@ deps = coverage Jinja2 html5lib + pygments selenium sqlparse passenv= @@ -33,6 +34,7 @@ setenv = PYTHONPATH = {toxinidir} PYTHONWARNINGS = d py39-dj32-postgresql: DJANGO_SELENIUM_TESTS = true + py310-dj40-postgresql: DJANGO_SELENIUM_TESTS = true DB_NAME = {env:DB_NAME:debug_toolbar} DB_USER = {env:DB_USER:debug_toolbar} DB_HOST = {env:DB_HOST:localhost}