diff --git a/.editorconfig b/.editorconfig index 5c99f6582..736c9d3e9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# http://editorconfig.org +# https://editorconfig.org # Source: pydanny cookiecutter-django repo root = true diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 3abc67892..f10aeee6e 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -18,11 +18,14 @@ jobs: - 3.8 - 3.9 - "3.10" - - "pypy3.9" + - "3.11" + # 2023-06-05: disabled pypy3.9 due to asgiref typing error + # - "pypy3.9" tox-django-version: - "32" - "40" - "41" + - "42" # GH Actions don't support something like allow-failure ? # - "master" exclude: @@ -36,6 +39,12 @@ jobs: tox-django-version: "41" - python-version: "pypy3.9" tox-django-version: "41" + - python-version: "3.6" + tox-django-version: "42" + - python-version: "3.7" + tox-django-version: "42" + - python-version: "pypy3.9" + tox-django-version: "42" steps: - name: Checkout uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 71437a3d6..8debcbae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ Changelog See https://github.com/django-extensions/django-extensions/releases +3.2.2 +----- + +Changes: + +- Improvement: Add support for psycopg3 (#1814) +- Improvement: runserver_plus, autoreload on template change (#1796) +- Improvement: highlighting, test_should_highlight_bash_syntax_without_name to include whitespace spans (#1797) +- Improvement: tests, add Python 3.11 to tox and actions to formally support python 3.11 (#1786) +- Improvement: runserver_plus, Send the file_changed event when a reload is triggered (#1775) +- Improvement: runserver_plus, Add REMOTE_USER to werkzeug environment (#1708) +- Improvement: pipchecker, force pip to use pkg_resources as backend for resolving distributions (#1782) +- Fix: Fix error with lack of PosixPath support (#1785) +- Fix: Cleanup http: links (#1798) + 3.2.1 ----- diff --git a/README.rst b/README.rst index 36d5f866e..ca5bf09e2 100644 --- a/README.rst +++ b/README.rst @@ -110,7 +110,7 @@ Open Source projects can always use more help. Fixing a problem, documenting a f translation in your language. If you have some time to spare and like to help us, here are the places to do so: - GitHub: https://github.com/django-extensions/django-extensions -- Mailing list: http://groups.google.com/group/django-extensions +- Mailing list: https://groups.google.com/group/django-extensions - Translations: https://www.transifex.com/projects/p/django-extensions/ @@ -134,6 +134,6 @@ Please remember that nobody is paid directly to develop or maintain Django Exten between putting food on the table, family, this project and the rest of life :-) -__ http://ericholscher.com/blog/2008/sep/12/screencast-django-command-extensions/ -__ http://vimeo.com/1720508 +__ https://ericholscher.com/blog/2008/sep/12/screencast-django-command-extensions/ +__ https://vimeo.com/1720508 __ https://www.youtube.com/watch?v=1F6G3ONhr4k diff --git a/django_extensions/__init__.py b/django_extensions/__init__.py index 8f4c64ef1..6a53c7df5 100644 --- a/django_extensions/__init__.py +++ b/django_extensions/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -VERSION = (3, 2, 1) +VERSION = (3, 2, 3) def get_version(version): diff --git a/django_extensions/db/fields/__init__.py b/django_extensions/db/fields/__init__.py index 5d1b657aa..bc524f14f 100644 --- a/django_extensions/db/fields/__init__.py +++ b/django_extensions/db/fields/__init__.py @@ -143,7 +143,7 @@ def slugify_function(self, content): slug = AutoSlugField(populate_from='title') Inspired by SmileyChris' Unique Slugify snippet: - http://www.djangosnippets.org/snippets/690/ + https://www.djangosnippets.org/snippets/690/ """ def __init__(self, *args, **kwargs): @@ -484,7 +484,7 @@ class UUIDFieldMixin: By default uses UUID version 4 (randomly generated UUID). The field support all uuid versions which are natively supported by the uuid python module, except version 2. - For more information see: http://docs.python.org/lib/module-uuid.html + For more information see: https://docs.python.org/lib/module-uuid.html """ DEFAULT_MAX_LENGTH = 36 diff --git a/django_extensions/import_subclasses.py b/django_extensions/import_subclasses.py index 4e6140ca5..ad0f3dbe1 100644 --- a/django_extensions/import_subclasses.py +++ b/django_extensions/import_subclasses.py @@ -39,7 +39,7 @@ def collect_subclasses(self): # type: () -> Dict[str, List[Tuple[str, str]]] but in future functionality of aliasing subclasses can be added. """ result = {} # type: Dict[str, List[Tuple[str, str]]] - for loader, module_name, is_pkg in walk_packages(path=[settings.BASE_DIR]): + for loader, module_name, is_pkg in walk_packages(path=[str(settings.BASE_DIR)]): subclasses_from_module = self._collect_classes_from_module(module_name) if subclasses_from_module: result[module_name] = subclasses_from_module diff --git a/django_extensions/locale/fr/LC_MESSAGES/django.po b/django_extensions/locale/fr/LC_MESSAGES/django.po index 0e9afbe19..7dce17f79 100644 --- a/django_extensions/locale/fr/LC_MESSAGES/django.po +++ b/django_extensions/locale/fr/LC_MESSAGES/django.po @@ -13,7 +13,7 @@ msgstr "" "POT-Creation-Date: 2011-02-02 11:42+0100\n" "PO-Revision-Date: 2014-01-11 11:14+0000\n" "Last-Translator: mathiasuk\n" -"Language-Team: French (http://www.transifex.com/projects/p/django-extensions/language/fr/)\n" +"Language-Team: French (https://www.transifex.com/projects/p/django-extensions/language/fr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" diff --git a/django_extensions/management/commands/clear_cache.py b/django_extensions/management/commands/clear_cache.py index b022c61dc..dcfcde112 100644 --- a/django_extensions/management/commands/clear_cache.py +++ b/django_extensions/management/commands/clear_cache.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Author: AxiaCore S.A.S. http://axiacore.com +# Author: AxiaCore S.A.S. https://axiacore.com from django.conf import settings from django.core.cache import DEFAULT_CACHE_ALIAS, caches from django.core.cache.backends.base import InvalidCacheBackendError diff --git a/django_extensions/management/commands/drop_test_database.py b/django_extensions/management/commands/drop_test_database.py index 5021ab5b3..66d45b73a 100644 --- a/django_extensions/management/commands/drop_test_database.py +++ b/django_extensions/management/commands/drop_test_database.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import importlib.util from itertools import count import os import logging @@ -167,9 +168,13 @@ def format_filename(name, number): cursor.execute(drop_query) elif engine in POSTGRESQL_ENGINES: - import psycopg2 as Database # NOQA + has_psycopg3 = importlib.util.find_spec("psycopg") + if has_psycopg3: + import psycopg as Database # NOQA + else: + import psycopg2 as Database # NOQA - conn_params = {'database': 'template1'} + conn_params = {'dbname': 'template1'} if user: conn_params['user'] = user if password: @@ -180,7 +185,10 @@ def format_filename(name, number): conn_params['port'] = database_port connection = Database.connect(**conn_params) - connection.set_isolation_level(0) # autocommit false + if has_psycopg3: + connection.autocommit = True + else: + connection.set_isolation_level(0) # autocommit false cursor = connection.cursor() for db_name in get_database_names('{}_{}'.format): diff --git a/django_extensions/management/commands/dumpscript.py b/django_extensions/management/commands/dumpscript.py index d31ddcbd7..384549c13 100644 --- a/django_extensions/management/commands/dumpscript.py +++ b/django_extensions/management/commands/dumpscript.py @@ -2,7 +2,7 @@ """ Title: Dumpscript management command Project: Hardytools (queryset-refactor version) - Author: Will Hardy (http://willhardy.com.au) + Author: Will Hardy Date: June 2008 Usage: python manage.py dumpscript appname > scripts/scriptname.py $Revision: 217 $ diff --git a/django_extensions/management/commands/list_model_info.py b/django_extensions/management/commands/list_model_info.py index 15e067f0e..c0a027314 100644 --- a/django_extensions/management/commands/list_model_info.py +++ b/django_extensions/management/commands/list_model_info.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Author: OmenApps. http://www.omenapps.com +# Author: OmenApps. https://omenapps.com import inspect from django.apps import apps as django_apps diff --git a/django_extensions/management/commands/pipchecker.py b/django_extensions/management/commands/pipchecker.py index e3563ee2c..9f4a84b71 100644 --- a/django_extensions/management/commands/pipchecker.py +++ b/django_extensions/management/commands/pipchecker.py @@ -39,20 +39,16 @@ def get_installed_distributions( """Return a list of installed Distribution objects. Left for compatibility until direct pkg_resources uses are refactored out. """ - from pip._internal.metadata import get_default_environment, get_environment - from pip._internal.metadata.pkg_resources import Distribution as _Dist - if paths is None: - env = get_default_environment() - else: - env = get_environment(paths) - dists = env.iter_installed_distributions( + from pip._internal.metadata import pkg_resources + + dists = pkg_resources.Environment.from_paths(paths).iter_installed_distributions( local_only=local_only, include_editables=include_editables, editables_only=editables_only, user_only=user_only, ) - return [cast(_Dist, dist)._dist for dist in dists] + return [cast(pkg_resources.Distribution, dist)._dist for dist in dists] except ImportError: # pip < 10 try: @@ -220,8 +216,8 @@ def check_github(self): curl -u 'rizumu' -d '{"scopes":["repo"], "note":"pipchecker"}' https://api.github.com/authorizations For more info on github api tokens: - https://help.github.com/articles/creating-an-oauth-token-for-command-line-use - http://developer.github.com/v3/oauth/#oauth-authorizations-api + https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token + https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps Requirement Format ------------------ diff --git a/django_extensions/management/commands/reset_db.py b/django_extensions/management/commands/reset_db.py index 09bc1d43a..e35273c97 100644 --- a/django_extensions/management/commands/reset_db.py +++ b/django_extensions/management/commands/reset_db.py @@ -2,8 +2,9 @@ """ reset_db command -originally from http://www.djangosnippets.org/snippets/828/ by dnordberg +originally from https://www.djangosnippets.org/snippets/828/ by dnordberg """ +import importlib.util import os import logging import warnings @@ -141,9 +142,13 @@ def handle(self, *args, **options): logging.info('Executing... "%s"', create_query) connection.query(create_query.strip()) elif engine in POSTGRESQL_ENGINES: - import psycopg2 as Database # NOQA + has_psycopg3 = importlib.util.find_spec("psycopg") + if has_psycopg3: + import psycopg as Database # NOQA + else: + import psycopg2 as Database # NOQA - conn_params = {'database': 'template1'} + conn_params = {'dbname': 'template1'} if user: conn_params['user'] = user if password: @@ -154,7 +159,10 @@ def handle(self, *args, **options): conn_params['port'] = database_port connection = Database.connect(**conn_params) - connection.set_isolation_level(0) # autocommit false + if has_psycopg3: + connection.autocommit = True + else: + connection.set_isolation_level(0) # autocommit false cursor = connection.cursor() if options['close_sessions']: diff --git a/django_extensions/management/commands/runserver_plus.py b/django_extensions/management/commands/runserver_plus.py index e8f104c58..6ed2174ce 100644 --- a/django_extensions/management/commands/runserver_plus.py +++ b/django_extensions/management/commands/runserver_plus.py @@ -7,7 +7,8 @@ import traceback import webbrowser import functools -from typing import List, Set +from pathlib import Path +from typing import List, Set # NOQA import django from django.conf import settings @@ -15,7 +16,8 @@ from django.core.management.color import color_style from django.core.servers.basehttp import get_internal_wsgi_application from django.dispatch import Signal -from django.utils.autoreload import get_reloader +from django.template.autoreload import get_template_directories, reset_loaders +from django.utils.autoreload import file_changed, get_reloader from django.views import debug as django_views_debug try: @@ -76,22 +78,62 @@ _error_files = set() # type: Set[str] +def get_all_template_files() -> Set[str]: + template_list = set() + + for template_dir in get_template_directories(): + for base_dir, _, filenames in os.walk(template_dir): + for filename in filenames: + template_list.add(os.path.join(base_dir, filename)) + + return template_list + + if HAS_WERKZEUG: # Monkey patch the reloader to support adding more files to extra_files for name, reloader_loop_klass in _reloader.reloader_loops.items(): class WrappedReloaderLoop(reloader_loop_klass): # type: ignore def __init__(self, *args, **kwargs): + self._template_files: Set[str] = get_all_template_files() super().__init__(*args, **kwargs) self._extra_files = self.extra_files @property def extra_files(self): - return self._extra_files.union(_error_files) + template_files = get_all_template_files() + + # reset loaders if there are new files detected + if len(self._template_files) != len(template_files): + + changed = template_files.difference(self._template_files) + for filename in changed: + _log("info", f" * New file {filename} added, reset template loaders") + self.register_file_changed(filename) + + reset_loaders() + + self._template_files = template_files + + return self._extra_files.union(_error_files, template_files) @extra_files.setter def extra_files(self, extra_files): self._extra_files = extra_files + def trigger_reload(self, filename: str) -> None: + path = Path(filename) + results = file_changed.send(sender=self, file_path=path) + if not any(res[1] for res in results): + super().trigger_reload(filename) + else: + _log("info", f" * Detected change in {filename!r}, reset template loaders") + self.register_file_changed(filename) + + def register_file_changed(self, filename): + if hasattr(self, "mtimes"): + mtime = os.stat(filename).st_mtime + self.mtimes[filename] = mtime + _reloader.reloader_loops[name] = WrappedReloaderLoop @@ -311,7 +353,7 @@ def application(env, start_response): def inner_run(self, options): if not HAS_WERKZEUG: - raise CommandError("Werkzeug is required to use runserver_plus. Please visit http://werkzeug.pocoo.org/ or install via pip. (pip install Werkzeug)") + raise CommandError("Werkzeug is required to use runserver_plus. Please visit https://werkzeug.palletsprojects.com/ or install via pip. (pip install Werkzeug)") # Set colored output if settings.DEBUG: @@ -326,6 +368,9 @@ def make_environ(self): environ = super().make_environ() if not options['keep_meta_shutdown_func'] and 'werkzeug.server.shutdown' in environ: del environ['werkzeug.server.shutdown'] + remote_user = os.getenv('REMOTE_USER') + if remote_user is not None: + environ['REMOTE_USER'] = remote_user return environ threaded = options['threaded'] @@ -383,7 +428,7 @@ def make_environ(self): if self.show_startup_messages: print("\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)) print("Development server is running at %s" % (bind_url,)) - print("Using the Werkzeug debugger (http://werkzeug.pocoo.org/)") + print("Using the Werkzeug debugger (https://werkzeug.palletsprojects.com/)") print("Quit the server with %s." % quit_command) if open_browser: diff --git a/django_extensions/management/commands/shell_plus.py b/django_extensions/management/commands/shell_plus.py index 8229918fe..b4ad275cf 100644 --- a/django_extensions/management/commands/shell_plus.py +++ b/django_extensions/management/commands/shell_plus.py @@ -484,7 +484,7 @@ def set_application_name(self, options): Use the fallback_application_name to let the user override it with PGAPPNAME env variable - http://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS # noqa + https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS # noqa """ supported_backends = ( 'django.db.backends.postgresql', diff --git a/django_extensions/management/commands/show_template_tags.py b/django_extensions/management/commands/show_template_tags.py index 399dd262e..b0ebc75f3 100644 --- a/django_extensions/management/commands/show_template_tags.py +++ b/django_extensions/management/commands/show_template_tags.py @@ -38,7 +38,7 @@ def format_block(block, nlspaces=0): The purpose is to let us list a code block as a multiline, triple-quoted Python string, taking care of indentation concerns. - http://code.activestate.com/recipes/145672/ + https://code.activestate.com/recipes/145672/ """ # separate block into lines lines = smart_str(block).split('\n') diff --git a/django_extensions/management/commands/sqldiff.py b/django_extensions/management/commands/sqldiff.py index 20849dadb..d1dc8e015 100644 --- a/django_extensions/management/commands/sqldiff.py +++ b/django_extensions/management/commands/sqldiff.py @@ -265,7 +265,7 @@ def sql_to_dict(self, query, param): sql_to_dict(query, param) -> list of dicts - code from snippet at http://www.djangosnippets.org/snippets/1383/ + code from snippet at https://www.djangosnippets.org/snippets/1383/ """ cursor = connection.cursor() cursor.execute(query, param) @@ -947,7 +947,7 @@ def load_null(self): # sqlite does not support tablespaces tablespace = "public" # index, column_name, column_type, nullable, default_value - # see: http://www.sqlite.org/pragma.html#pragma_table_info + # see: https://www.sqlite.org/pragma.html#pragma_table_info for table_info in self.sql_to_dict("PRAGMA table_info('%s');" % table_name, []): key = (tablespace, table_name, table_info['name']) self.null[key] = not table_info['notnull'] diff --git a/django_extensions/management/debug_cursor.py b/django_extensions/management/debug_cursor.py index 24f79172d..d161bd8d8 100644 --- a/django_extensions/management/debug_cursor.py +++ b/django_extensions/management/debug_cursor.py @@ -18,7 +18,6 @@ def monkey_patch_cursordebugwrapper(print_sql=None, print_sql_location=False, tr if truncate is None: truncate = getattr(settings, '%s_PRINT_SQL_TRUNCATE' % confprefix, DEFAULT_PRINT_SQL_TRUNCATE_CHARS) - # Code orginally from http://gist.github.com/118990 sqlparse = None if getattr(settings, '%s_SQLPARSE_ENABLED' % confprefix, True): try: diff --git a/django_extensions/management/modelviz.py b/django_extensions/management/modelviz.py index 81804a3a5..b9d1cddea 100644 --- a/django_extensions/management/modelviz.py +++ b/django_extensions/management/modelviz.py @@ -31,9 +31,9 @@ __license__ = "Python" __author__ = "Bas van Oostveen ", __contributors__ = [ - "Antonio Cavedoni " - "Stefano J. Attardi ", - "limodou ", + "Antonio Cavedoni " + "Stefano J. Attardi ", + "limodou", "Carlo C8E Miron", "Andre Campos ", "Justin Findlay ", diff --git a/django_extensions/mongodb/fields/__init__.py b/django_extensions/mongodb/fields/__init__.py index ee28484d9..e87cace8b 100644 --- a/django_extensions/mongodb/fields/__init__.py +++ b/django_extensions/mongodb/fields/__init__.py @@ -56,7 +56,7 @@ class AutoSlugField(SlugField): If set to True, overwrites the slug on every save (default: False) Inspired by SmileyChris' Unique Slugify snippet: - http://www.djangosnippets.org/snippets/690/ + https://www.djangosnippets.org/snippets/690/ """ def __init__(self, *args, **kwargs): @@ -216,7 +216,7 @@ class UUIDField(StringField): By default uses UUID version 1 (generate from host ID, sequence number and current time) The field support all uuid versions which are natively supported by the uuid python module. - For more information see: http://docs.python.org/lib/module-uuid.html + For more information see: https://docs.python.org/lib/module-uuid.html """ def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs): diff --git a/django_extensions/static/django_extensions/js/jquery.ajaxQueue.js b/django_extensions/static/django_extensions/js/jquery.ajaxQueue.js index cd4492c13..aca15d9a6 100644 --- a/django_extensions/static/django_extensions/js/jquery.ajaxQueue.js +++ b/django_extensions/static/django_extensions/js/jquery.ajaxQueue.js @@ -1,8 +1,5 @@ /** * Ajax Queue Plugin - * - * Homepage: http://jquery.com/plugins/project/ajaxqueue - * Documentation: http://docs.jquery.com/AjaxQueue */ /** diff --git a/django_extensions/static/django_extensions/js/jquery.bgiframe.js b/django_extensions/static/django_extensions/js/jquery.bgiframe.js index 5c3735d68..1a452ba00 100644 --- a/django_extensions/static/django_extensions/js/jquery.bgiframe.js +++ b/django_extensions/static/django_extensions/js/jquery.bgiframe.js @@ -1,4 +1,4 @@ -/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net) +/*! Copyright (c) 2010 Brandon Aaron (http://brandon.aaron.sh/) * Licensed under the MIT License (LICENSE.txt). * * Version 2.1.2 diff --git a/django_extensions/templatetags/highlighting.py b/django_extensions/templatetags/highlighting.py index a38d39d73..f172fd3df 100644 --- a/django_extensions/templatetags/highlighting.py +++ b/django_extensions/templatetags/highlighting.py @@ -4,14 +4,11 @@ copy+paste actual code into your Django templates without needing to escape or anything crazy. -http://lobstertech.com/2008/aug/30/django_syntax_highlight_template_tag/ - Example: {% load highlighting %}