diff --git a/README.rst b/README.rst index c147abf4f..725bb4fe8 100644 --- a/README.rst +++ b/README.rst @@ -45,7 +45,7 @@ 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.2.4. It works on -Django ≥ 2.2. +Django ≥ 3.2. 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 401de16c1..7dbb53bd5 100644 --- a/debug_toolbar/__init__.py +++ b/debug_toolbar/__init__.py @@ -1,5 +1,3 @@ -import django - from debug_toolbar.urls import app_name __all__ = ["VERSION"] @@ -12,6 +10,3 @@ # Code that discovers files or modules in INSTALLED_APPS imports this module. urls = "debug_toolbar.urls", app_name - -if django.VERSION < (3, 2): - default_app_config = "debug_toolbar.apps.DebugToolbarConfig" diff --git a/debug_toolbar/management/commands/debugsqlshell.py b/debug_toolbar/management/commands/debugsqlshell.py index ba0f32463..577126ecd 100644 --- a/debug_toolbar/management/commands/debugsqlshell.py +++ b/debug_toolbar/management/commands/debugsqlshell.py @@ -1,11 +1,10 @@ from time import time -import django import sqlparse from django.core.management.commands.shell import Command from django.db import connection -if connection.vendor == "postgresql" and django.VERSION >= (3, 0, 0): +if connection.vendor == "postgresql": from django.db.backends.postgresql import base as base_module else: from django.db.backends import utils as base_module diff --git a/debug_toolbar/panels/cache.py b/debug_toolbar/panels/cache.py index 9f998b18f..19ec7b583 100644 --- a/debug_toolbar/panels/cache.py +++ b/debug_toolbar/panels/cache.py @@ -8,15 +8,9 @@ except ImportError: ConnectionProxy = None -import django from django.conf import settings from django.core import cache -from django.core.cache import ( - DEFAULT_CACHE_ALIAS, - CacheHandler, - cache as original_cache, - caches as original_caches, -) +from django.core.cache import DEFAULT_CACHE_ALIAS, CacheHandler from django.core.cache.backends.base import BaseCache from django.dispatch import Signal from django.middleware import cache as middleware_cache @@ -141,26 +135,17 @@ def decr_version(self, *args, **kwargs): return self.cache.decr_version(*args, **kwargs) -if django.VERSION < (3, 2): +class CacheHandlerPatch(CacheHandler): + def __init__(self, settings=None): + self._djdt_wrap = True + super().__init__(settings=settings) - class CacheHandlerPatch(CacheHandler): - def __getitem__(self, alias): - actual_cache = super().__getitem__(alias) + def create_connection(self, alias): + actual_cache = super().create_connection(alias) + if self._djdt_wrap: return CacheStatTracker(actual_cache) - -else: - - class CacheHandlerPatch(CacheHandler): - def __init__(self, settings=None): - self._djdt_wrap = True - super().__init__(settings=settings) - - def create_connection(self, alias): - actual_cache = super().create_connection(alias) - if self._djdt_wrap: - return CacheStatTracker(actual_cache) - else: - return actual_cache + else: + return actual_cache middleware_cache.caches = CacheHandlerPatch() @@ -268,40 +253,26 @@ def title(self): ) def enable_instrumentation(self): - if django.VERSION < (3, 2): - if isinstance(middleware_cache.caches, CacheHandlerPatch): - cache.caches = middleware_cache.caches - else: - cache.caches = CacheHandlerPatch() - else: - for alias in cache.caches: - if not isinstance(cache.caches[alias], CacheStatTracker): - cache.caches[alias] = CacheStatTracker(cache.caches[alias]) + for alias in cache.caches: + if not isinstance(cache.caches[alias], CacheStatTracker): + cache.caches[alias] = CacheStatTracker(cache.caches[alias]) - if not isinstance(middleware_cache.caches, CacheHandlerPatch): - middleware_cache.caches = cache.caches + if not isinstance(middleware_cache.caches, CacheHandlerPatch): + middleware_cache.caches = cache.caches # Wrap the patched cache inside Django's ConnectionProxy if ConnectionProxy: cache.cache = ConnectionProxy(cache.caches, DEFAULT_CACHE_ALIAS) def disable_instrumentation(self): - if django.VERSION < (3, 2): - cache.caches = original_caches - cache.cache = original_cache - # While it can be restored to the original, any views that were - # wrapped with the cache_page decorator will continue to use a - # monkey patched cache. - middleware_cache.caches = original_caches - else: - for alias in cache.caches: - if isinstance(cache.caches[alias], CacheStatTracker): - cache.caches[alias] = cache.caches[alias].cache - if ConnectionProxy: - cache.cache = ConnectionProxy(cache.caches, DEFAULT_CACHE_ALIAS) - # While it can be restored to the original, any views that were - # wrapped with the cache_page decorator will continue to use a - # monkey patched cache. + for alias in cache.caches: + if isinstance(cache.caches[alias], CacheStatTracker): + cache.caches[alias] = cache.caches[alias].cache + if ConnectionProxy: + cache.cache = ConnectionProxy(cache.caches, DEFAULT_CACHE_ALIAS) + # While it can be restored to the original, any views that were + # wrapped with the cache_page decorator will continue to use a + # monkey patched cache. def generate_stats(self, request, response): self.record_stats( diff --git a/debug_toolbar/panels/settings.py b/debug_toolbar/panels/settings.py index 85a92788e..37bba8727 100644 --- a/debug_toolbar/panels/settings.py +++ b/debug_toolbar/panels/settings.py @@ -1,17 +1,12 @@ from collections import OrderedDict -import django from django.conf import settings from django.utils.translation import gettext_lazy as _ +from django.views.debug import get_default_exception_reporter_filter from debug_toolbar.panels import Panel -if django.VERSION >= (3, 1): - from django.views.debug import get_default_exception_reporter_filter - - get_safe_settings = get_default_exception_reporter_filter().get_safe_settings -else: - from django.views.debug import get_safe_settings +get_safe_settings = get_default_exception_reporter_filter().get_safe_settings class SettingsPanel(Panel): diff --git a/docs/changes.rst b/docs/changes.rst index f953ce498..a24be5ce7 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -4,7 +4,7 @@ Change log Next version ------------ -* Removed support for Django 3.1. +* Removed support for Django < 3.2. 3.2.4 (2021-12-15) ------------------ diff --git a/setup.cfg b/setup.cfg index 2f4afdc90..4511d3c1c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,7 +14,6 @@ classifiers = Development Status :: 5 - Production/Stable Environment :: Web Environment Framework :: Django - Framework :: Django :: 2.2 Framework :: Django :: 3.2 Framework :: Django :: 4.0 Intended Audience :: Developers @@ -33,7 +32,7 @@ classifiers = [options] python_requires = >=3.6 install_requires = - Django >= 2.2 + Django >= 3.2 sqlparse >= 0.2.0 packages = find: include_package_data = true diff --git a/tests/commands/test_debugsqlshell.py b/tests/commands/test_debugsqlshell.py index 9520d0dd8..9939c5ca9 100644 --- a/tests/commands/test_debugsqlshell.py +++ b/tests/commands/test_debugsqlshell.py @@ -1,14 +1,13 @@ import io import sys -import django from django.contrib.auth.models import User from django.core import management from django.db import connection from django.test import TestCase from django.test.utils import override_settings -if connection.vendor == "postgresql" and django.VERSION >= (3, 0, 0): +if connection.vendor == "postgresql": from django.db.backends.postgresql import base as base_module else: from django.db.backends import utils as base_module diff --git a/tests/models.py b/tests/models.py index 0c070a7f2..95696020a 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,6 @@ from django.conf import settings from django.db import models +from django.db.models import JSONField class NonAsciiRepr: @@ -11,19 +12,8 @@ class Binary(models.Model): field = models.BinaryField() -try: - from django.db.models import JSONField -except ImportError: # Django<3.1 - try: - from django.contrib.postgres.fields import JSONField - except ImportError: # psycopg2 not installed - JSONField = None - - -if JSONField: - - class PostgresJSON(models.Model): - field = JSONField() +class PostgresJSON(models.Model): + field = JSONField() if settings.USE_GIS: diff --git a/tests/panels/test_sql.py b/tests/panels/test_sql.py index 9358a2f70..d7e9759af 100644 --- a/tests/panels/test_sql.py +++ b/tests/panels/test_sql.py @@ -15,16 +15,7 @@ from debug_toolbar import settings as dt_settings from ..base import BaseTestCase - -try: - from psycopg2._json import Json as PostgresJson -except ImportError: - PostgresJson = None - -if connection.vendor == "postgresql": - from ..models import PostgresJSON as PostgresJSONModel -else: - PostgresJSONModel = None +from ..models import PostgresJSON class SQLPanelTestCase(BaseTestCase): @@ -138,12 +129,9 @@ def test_param_conversion(self): # ensure query was logged self.assertEqual(len(self.panel._queries), 3) - if ( - django.VERSION >= (3, 1) - # Django 4.1 started passing true/false back for boolean - # comparisons in MySQL. - and not (django.VERSION >= (4, 1) and connection.vendor == "mysql") - ): + # Django 4.1 started passing true/false back for boolean + # comparisons in MySQL. + if not (django.VERSION >= (4, 1) and connection.vendor == "mysql"): self.assertEqual( tuple([q[1]["params"] for q in self.panel._queries]), ('["Foo"]', "[10, 1]", '["2017-12-22 16:07:01"]'), @@ -160,7 +148,7 @@ def test_param_conversion(self): def test_json_param_conversion(self): self.assertEqual(len(self.panel._queries), 0) - list(PostgresJSONModel.objects.filter(field__contains={"foo": "bar"})) + list(PostgresJSON.objects.filter(field__contains={"foo": "bar"})) response = self.panel.process_request(self.request) self.panel.generate_stats(self.request, response) @@ -171,11 +159,6 @@ def test_json_param_conversion(self): self.panel._queries[0][1]["params"], '["{\\"foo\\": \\"bar\\"}"]', ) - if django.VERSION < (3, 1): - self.assertIsInstance( - self.panel._queries[0][1]["raw_params"][0], - PostgresJson, - ) def test_binary_param_force_text(self): self.assertEqual(len(self.panel._queries), 0) diff --git a/tests/test_forms.py b/tests/test_forms.py index 73d820fd8..6da504233 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -1,17 +1,11 @@ from datetime import datetime -import django from django import forms from django.test import TestCase from debug_toolbar.forms import SignedDataForm -# Django 3.1 uses sha256 by default. -SIGNATURE = ( - "v02QBcJplEET6QXHNWejnRcmSENWlw6_RjxLTR7QG9g" - if django.VERSION >= (3, 1) - else "ukcAFUqYhUUnqT-LupnYoo-KvFg" -) +SIGNATURE = "v02QBcJplEET6QXHNWejnRcmSENWlw6_RjxLTR7QG9g" DATA = {"value": "foo", "date": datetime(2020, 1, 1)} SIGNED_DATA = f'{{"date": "2020-01-01 00:00:00", "value": "foo"}}:{SIGNATURE}' diff --git a/tests/test_integration.py b/tests/test_integration.py index ff6d32d9f..b76ebf875 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -2,7 +2,6 @@ import re import unittest -import django import html5lib from django.contrib.staticfiles.testing import StaticLiveServerTestCase from django.core import signing @@ -444,10 +443,7 @@ def test_auth_login_view_without_redirect(self): self.assertEqual(response.status_code, 200) # The key None (without quotes) exists in the list of template # variables. - if django.VERSION < (3, 0): - self.assertIn("None: ''", response.json()["content"]) - else: - self.assertIn("None: ''", response.json()["content"]) + self.assertIn("None: ''", response.json()["content"]) @unittest.skipIf(webdriver is None, "selenium isn't installed") diff --git a/tox.ini b/tox.ini index 65fea4b36..9a4a5c8d4 100644 --- a/tox.ini +++ b/tox.ini @@ -2,13 +2,11 @@ envlist = docs packaging - py{36,37}-dj{22,32}-{sqlite,postgresql,postgis,mysql} - py{38,39}-dj{22,32,40,main}-{sqlite,postgresql,postgis,mysql} - py{310}-dj{32,40,main}-{sqlite,postgresql,postgis,mysql} + py{36,37}-dj{32}-{sqlite,postgresql,postgis,mysql} + py{38,39,310}-dj{32,40,main}-{sqlite,postgresql,postgis,mysql} [testenv] deps = - dj22: django~=2.2.17 dj32: django~=3.2.9 dj40: django~=4.0.0 sqlite: mock @@ -42,25 +40,25 @@ whitelist_externals = make pip_pre = True commands = make coverage TEST_ARGS='{posargs:tests}' -[testenv:py{36,37,38,39,310}-dj{22,32,40,main}-postgresql] +[testenv:py{36,37,38,39,310}-dj{40,main}-postgresql] setenv = {[testenv]setenv} DB_BACKEND = postgresql DB_PORT = {env:DB_PORT:5432} -[testenv:py{36,37,38,39,310}-dj{22,32,40,main}-postgis] +[testenv:py{36,37,38,39,310}-dj{32,40,main}-postgis] setenv = {[testenv]setenv} DB_BACKEND = postgis DB_PORT = {env:DB_PORT:5432} -[testenv:py{36,37,38,39,310}-dj{22,32,40,main}-mysql] +[testenv:py{36,37,38,39,310}-dj{32,40,main}-mysql] setenv = {[testenv]setenv} DB_BACKEND = mysql DB_PORT = {env:DB_PORT:3306} -[testenv:py{36,37,38,39,310}-dj{22,32,40,main}-sqlite] +[testenv:py{36,37,38,39,310}-dj{32,40,main}-sqlite] setenv = {[testenv]setenv} DB_BACKEND = sqlite3