diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml
deleted file mode 100644
index 76f0b9305..000000000
--- a/.github/workflows/security.yml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: Check Security Vulnerabilities
-
-on:
- pull_request:
- push:
- branches:
- - main
-
-jobs:
- safety:
- name: safety
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Set up Python 3.x
- uses: actions/setup-python@v5
- with:
- python-version: "3.13"
- - run: python -m pip install tox
- - name: safety
- run: tox
- env:
- TOXENV: safety
diff --git a/.gitignore b/.gitignore
index 4c713189c..b6e8c9437 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ django-sample-app*/
*.swp
*.swo
*.sqlite3
+.app-style.json
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c1cfddb13..38e3ced0f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,7 +18,6 @@ repos:
- id: mixed-line-ending
args: [ '--fix=lf' ]
description: Forces to replace line ending by the UNIX 'lf' character.
- - id: fix-encoding-pragma
- id: name-tests-test
args:
- --django
diff --git a/django_extensions/__init__.py b/django_extensions/__init__.py
index 87cef4971..f93ccdf85 100644
--- a/django_extensions/__init__.py
+++ b/django_extensions/__init__.py
@@ -1,6 +1,5 @@
-# -*- coding: utf-8 -*-
from django.utils.version import get_version
-VERSION = (4, 1, 0, "final", 0)
+VERSION = (4, 2, 0, "alpha", 0)
__version__ = get_version(VERSION)
diff --git a/django_extensions/admin/__init__.py b/django_extensions/admin/__init__.py
index ab9e69749..cfb39ec0e 100644
--- a/django_extensions/admin/__init__.py
+++ b/django_extensions/admin/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# Autocomplete feature for admin panel
#
diff --git a/django_extensions/admin/filter.py b/django_extensions/admin/filter.py
index 392b0c9c9..3a40def2a 100644
--- a/django_extensions/admin/filter.py
+++ b/django_extensions/admin/filter.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.contrib.admin import FieldListFilter
from django.contrib.admin.utils import prepare_lookup_value
from django.utils.translation import gettext_lazy as _
diff --git a/django_extensions/admin/widgets.py b/django_extensions/admin/widgets.py
index 9ed89c303..69c12d2c5 100644
--- a/django_extensions/admin/widgets.py
+++ b/django_extensions/admin/widgets.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import urllib
from django import forms
diff --git a/django_extensions/apps.py b/django_extensions/apps.py
index 2a869f0de..d01cbf0d0 100644
--- a/django_extensions/apps.py
+++ b/django_extensions/apps.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.apps import AppConfig
diff --git a/django_extensions/auth/mixins.py b/django_extensions/auth/mixins.py
index 791651ff5..ef42d3427 100644
--- a/django_extensions/auth/mixins.py
+++ b/django_extensions/auth/mixins.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.contrib.auth.mixins import UserPassesTestMixin
diff --git a/django_extensions/collision_resolvers.py b/django_extensions/collision_resolvers.py
index 15ef1a840..df28252bb 100644
--- a/django_extensions/collision_resolvers.py
+++ b/django_extensions/collision_resolvers.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import inspect
import sys
from abc import abstractmethod, ABCMeta
diff --git a/django_extensions/compat.py b/django_extensions/compat.py
index 8df20cd09..583286348 100644
--- a/django_extensions/compat.py
+++ b/django_extensions/compat.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import BytesIO
import csv
diff --git a/django_extensions/db/fields/__init__.py b/django_extensions/db/fields/__init__.py
index 2a2e0036e..20d4104da 100644
--- a/django_extensions/db/fields/__init__.py
+++ b/django_extensions/db/fields/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Django Extensions additional model fields
diff --git a/django_extensions/db/fields/json.py b/django_extensions/db/fields/json.py
index 435a8de8e..b63c47e62 100644
--- a/django_extensions/db/fields/json.py
+++ b/django_extensions/db/fields/json.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
JSONField automatically serializes most Python terms to JSON data.
Creates a TEXT field with a default value of "{}". See test_json.py for
diff --git a/django_extensions/db/models.py b/django_extensions/db/models.py
index 1e35b9369..cb7ea551b 100644
--- a/django_extensions/db/models.py
+++ b/django_extensions/db/models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.db import models
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
diff --git a/django_extensions/import_subclasses.py b/django_extensions/import_subclasses.py
index f31dd25c8..13d826c8b 100644
--- a/django_extensions/import_subclasses.py
+++ b/django_extensions/import_subclasses.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from importlib import import_module
from inspect import (
getmembers,
diff --git a/django_extensions/jobs/daily/cache_cleanup.py b/django_extensions/jobs/daily/cache_cleanup.py
index a387ecf6c..4eb1e4135 100644
--- a/django_extensions/jobs/daily/cache_cleanup.py
+++ b/django_extensions/jobs/daily/cache_cleanup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Daily cleanup job.
diff --git a/django_extensions/jobs/daily/daily_cleanup.py b/django_extensions/jobs/daily/daily_cleanup.py
index 7abce710f..e5e2b7176 100644
--- a/django_extensions/jobs/daily/daily_cleanup.py
+++ b/django_extensions/jobs/daily/daily_cleanup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Daily cleanup job.
diff --git a/django_extensions/logging/filters.py b/django_extensions/logging/filters.py
index 3178c1ba6..29995020a 100644
--- a/django_extensions/logging/filters.py
+++ b/django_extensions/logging/filters.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import time
import logging
from hashlib import md5
diff --git a/django_extensions/management/base.py b/django_extensions/management/base.py
index ceabb6c80..60ebf401e 100644
--- a/django_extensions/management/base.py
+++ b/django_extensions/management/base.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
from django.core.management.base import BaseCommand
diff --git a/django_extensions/management/color.py b/django_extensions/management/color.py
index ce893bbd0..884fa2b72 100644
--- a/django_extensions/management/color.py
+++ b/django_extensions/management/color.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core.management import color
from django.utils import termcolors
diff --git a/django_extensions/management/commands/admin_generator.py b/django_extensions/management/commands/admin_generator.py
index ee2a13f93..d02b58c0d 100644
--- a/django_extensions/management/commands/admin_generator.py
+++ b/django_extensions/management/commands/admin_generator.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
The Django Admin Generator is a project which can automatically generate
(scaffold) a Django Admin for you. By doing this it will introspect your
diff --git a/django_extensions/management/commands/clean_pyc.py b/django_extensions/management/commands/clean_pyc.py
index 9565dbe53..5e1fd9a97 100644
--- a/django_extensions/management/commands/clean_pyc.py
+++ b/django_extensions/management/commands/clean_pyc.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import fnmatch
import os
from os.path import join as _j
diff --git a/django_extensions/management/commands/clear_cache.py b/django_extensions/management/commands/clear_cache.py
index 3da26f82e..17540af28 100644
--- a/django_extensions/management/commands/clear_cache.py
+++ b/django_extensions/management/commands/clear_cache.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Author: AxiaCore S.A.S. https://axiacore.com
from django.conf import settings
from django.core.cache import DEFAULT_CACHE_ALIAS, caches
diff --git a/django_extensions/management/commands/compile_pyc.py b/django_extensions/management/commands/compile_pyc.py
index 09a76b1d2..dc2d2d69d 100644
--- a/django_extensions/management/commands/compile_pyc.py
+++ b/django_extensions/management/commands/compile_pyc.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import fnmatch
import os
import py_compile
diff --git a/django_extensions/management/commands/create_command.py b/django_extensions/management/commands/create_command.py
index 15bcd8b6d..23ecb6286 100644
--- a/django_extensions/management/commands/create_command.py
+++ b/django_extensions/management/commands/create_command.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import sys
import shutil
diff --git a/django_extensions/management/commands/create_jobs.py b/django_extensions/management/commands/create_jobs.py
index ec6adf7d5..0fba5aaf8 100644
--- a/django_extensions/management/commands/create_jobs.py
+++ b/django_extensions/management/commands/create_jobs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import sys
import shutil
diff --git a/django_extensions/management/commands/create_template_tags.py b/django_extensions/management/commands/create_template_tags.py
index 21ad95c0f..7666a0451 100644
--- a/django_extensions/management/commands/create_template_tags.py
+++ b/django_extensions/management/commands/create_template_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import sys
from typing import List
diff --git a/django_extensions/management/commands/delete_squashed_migrations.py b/django_extensions/management/commands/delete_squashed_migrations.py
index ea548c747..c41dcd7c8 100644
--- a/django_extensions/management/commands/delete_squashed_migrations.py
+++ b/django_extensions/management/commands/delete_squashed_migrations.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+import difflib
import os
import inspect
import re
@@ -7,7 +7,7 @@
from django.db import DEFAULT_DB_ALIAS, connections
from django.db.migrations.loader import AmbiguityError, MigrationLoader
-REPLACES_REGEX = re.compile(r"\s+replaces\s*=\s*\[[^\]]+\]\s*")
+REPLACES_REGEX = re.compile(r"^\s+replaces\s*=\s*\[[^\]]+\]\s*?$", flags=re.MULTILINE)
PYC = ".pyc"
@@ -146,32 +146,36 @@ def handle(self, **options):
if squashed_migration_fn.endswith(PYC):
squashed_migration_fn = py_from_pyc(squashed_migration_fn)
with open(squashed_migration_fn) as fp:
- squashed_migration_lines = list(fp)
-
- delete_lines = []
- for i, line in enumerate(squashed_migration_lines):
- if REPLACES_REGEX.match(line):
- delete_lines.append(i)
- if i > 0 and squashed_migration_lines[i - 1].strip() == "":
- delete_lines.insert(0, i - 1)
- break
- if not delete_lines:
+ squashed_migration_content = fp.read()
+
+ cleaned_migration_content = re.sub(
+ REPLACES_REGEX, "", squashed_migration_content
+ )
+ if cleaned_migration_content == squashed_migration_content:
raise CommandError(
(
- "Couldn't find 'replaces =' line in file %s. "
+ "Couldn't find 'replaces =' lines in file %s. "
"Please finish cleaning up manually."
)
% (squashed_migration_fn,)
)
if self.verbosity > 0 or self.interactive:
+ # Print the differences between the original and new content
+ diff = difflib.unified_diff(
+ squashed_migration_content.splitlines(),
+ cleaned_migration_content.splitlines(),
+ lineterm="",
+ fromfile="Original",
+ tofile="Modified",
+ )
+
self.stdout.write(
self.style.MIGRATE_HEADING(
- "Will delete line %s%s from file %s"
+ "The squashed migrations file %s will be modified like this :\n\n%s"
% (
- delete_lines[0],
- " and " + str(delete_lines[1]) if len(delete_lines) > 1 else "",
squashed_migration_fn,
+ "\n".join(diff),
)
)
)
@@ -179,12 +183,9 @@ def handle(self, **options):
if not self.confirm():
return
- for line_num in sorted(delete_lines, reverse=True):
- del squashed_migration_lines[line_num]
-
- with open(squashed_migration_fn, "w") as fp:
- if not self.dry_run:
- fp.write("".join(squashed_migration_lines))
+ if not self.dry_run:
+ with open(squashed_migration_fn, "w") as fp:
+ fp.write(cleaned_migration_content)
def confirm(self):
if self.interactive:
diff --git a/django_extensions/management/commands/describe_form.py b/django_extensions/management/commands/describe_form.py
index ed3686653..78f4b24da 100644
--- a/django_extensions/management/commands/describe_form.py
+++ b/django_extensions/management/commands/describe_form.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.apps import apps
from django.core.management.base import CommandError, LabelCommand
from django.utils.encoding import force_str
diff --git a/django_extensions/management/commands/drop_test_database.py b/django_extensions/management/commands/drop_test_database.py
index 28742f322..106ebf4c7 100644
--- a/django_extensions/management/commands/drop_test_database.py
+++ b/django_extensions/management/commands/drop_test_database.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import importlib.util
from itertools import count
import os
diff --git a/django_extensions/management/commands/dumpscript.py b/django_extensions/management/commands/dumpscript.py
index eeade42dc..d7d1f7bf2 100644
--- a/django_extensions/management/commands/dumpscript.py
+++ b/django_extensions/management/commands/dumpscript.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Title: Dumpscript management command
Project: Hardytools (queryset-refactor version)
diff --git a/django_extensions/management/commands/export_emails.py b/django_extensions/management/commands/export_emails.py
index 2ec75472d..142292f9c 100644
--- a/django_extensions/management/commands/export_emails.py
+++ b/django_extensions/management/commands/export_emails.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
import csv
diff --git a/django_extensions/management/commands/find_template.py b/django_extensions/management/commands/find_template.py
index b39d07ae7..f48d3ac57 100644
--- a/django_extensions/management/commands/find_template.py
+++ b/django_extensions/management/commands/find_template.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
from django.core.management.base import LabelCommand
diff --git a/django_extensions/management/commands/generate_password.py b/django_extensions/management/commands/generate_password.py
index 68b46908c..073a3a15e 100644
--- a/django_extensions/management/commands/generate_password.py
+++ b/django_extensions/management/commands/generate_password.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import argparse
import string
import secrets
diff --git a/django_extensions/management/commands/generate_secret_key.py b/django_extensions/management/commands/generate_secret_key.py
index 0ec129118..14e294bb3 100644
--- a/django_extensions/management/commands/generate_secret_key.py
+++ b/django_extensions/management/commands/generate_secret_key.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from typing import List
from django.core.management.base import BaseCommand
diff --git a/django_extensions/management/commands/graph_models.py b/django_extensions/management/commands/graph_models.py
index 66610dde4..e0bfc2f12 100644
--- a/django_extensions/management/commands/graph_models.py
+++ b/django_extensions/management/commands/graph_models.py
@@ -1,8 +1,9 @@
-# -*- coding: utf-8 -*-
import sys
import json
import os
import tempfile
+import fnmatch
+from collections import OrderedDict
from django.conf import settings
from django.core.management.base import BaseCommand, CommandError
@@ -27,26 +28,21 @@
except ImportError:
HAS_PYDOT = False
+DEFAULT_APP_STYLE_NAME = ".app-style.json"
-def retheme(graph_data, app_style={}):
- if isinstance(app_style, str):
- if os.path.exists(app_style):
- try:
- with open(app_style, "rt") as f:
- app_style = json.load(f)
- except Exception as e:
- print(f"Invalid app style file {app_style}")
- raise Exception(e)
- else:
- return graph_data
+
+def retheme(graph_data: dict, app_style_filename: str):
+ with open(app_style_filename, "rt") as f:
+ app_style = json.load(f, object_pairs_hook=OrderedDict)
for gc in graph_data["graphs"]:
for g in gc:
if "name" in g:
for m in g["models"]:
app_name = g["app_name"]
- if app_name in app_style:
- m["style"] = app_style[app_name]
+ for pattern, style in app_style.items():
+ if fnmatch.fnmatchcase(app_name, pattern):
+ m["style"] = dict(style)
return graph_data
@@ -73,7 +69,7 @@ def __init__(self, *args, **kwargs):
"action": "store",
"help": "Path to style json to configure the style per app",
"dest": "app-style",
- "default": ".app-style.json",
+ "default": "",
},
"--pygraphviz": {
"action": "store_true",
@@ -372,7 +368,21 @@ def handle(self, *args, **options):
)
template = loader.get_template(template_name)
- graph_data = retheme(graph_data, app_style=options["app-style"])
+ app_style_filename = options["app-style"]
+ if app_style_filename and not os.path.exists(app_style_filename):
+ raise CommandError(f"--app-style file {app_style_filename} not found")
+
+ if not app_style_filename:
+ # try default
+ default_app_style_filename = os.path.join(
+ settings.BASE_DIR, DEFAULT_APP_STYLE_NAME
+ )
+ if os.path.exists(default_app_style_filename):
+ app_style_filename = default_app_style_filename
+
+ if app_style_filename:
+ graph_data = retheme(graph_data, app_style_filename=app_style_filename)
+
dotdata = generate_dot(graph_data, template=template)
if output == "pygraphviz":
diff --git a/django_extensions/management/commands/list_model_info.py b/django_extensions/management/commands/list_model_info.py
index d1a329236..286711020 100644
--- a/django_extensions/management/commands/list_model_info.py
+++ b/django_extensions/management/commands/list_model_info.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Author: OmenApps. https://omenapps.com
import inspect
@@ -86,6 +85,9 @@ def list_model_info(self, options):
)
default_methods = [
+ "adelete",
+ "arefresh_from_db",
+ "asave",
"check",
"clean",
"clean_fields",
@@ -94,6 +96,7 @@ def list_model_info(self, options):
"from_db",
"full_clean",
"get_absolute_url",
+ "get_constraints",
"get_deferred_fields",
"prepare_database_save",
"refresh_from_db",
@@ -101,6 +104,7 @@ def list_model_info(self, options):
"save_base",
"serializable_value",
"unique_error_message",
+ "validate_constraints",
"validate_unique",
]
diff --git a/django_extensions/management/commands/list_signals.py b/django_extensions/management/commands/list_signals.py
index b19f3d3e2..9cadc4f85 100644
--- a/django_extensions/management/commands/list_signals.py
+++ b/django_extensions/management/commands/list_signals.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Based on https://gist.github.com/voldmar/1264102
# and https://gist.github.com/runekaagaard/2eecf0a8367959dc634b7866694daf2c
diff --git a/django_extensions/management/commands/mail_debug.py b/django_extensions/management/commands/mail_debug.py
index 74436f9f9..f12bbd221 100644
--- a/django_extensions/management/commands/mail_debug.py
+++ b/django_extensions/management/commands/mail_debug.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import asyncio
import sys
diff --git a/django_extensions/management/commands/managestate.py b/django_extensions/management/commands/managestate.py
index 6755310a3..e7ff244fd 100644
--- a/django_extensions/management/commands/managestate.py
+++ b/django_extensions/management/commands/managestate.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import json
from operator import itemgetter
from pathlib import Path
diff --git a/django_extensions/management/commands/merge_model_instances.py b/django_extensions/management/commands/merge_model_instances.py
index 204f792d9..3860be87e 100644
--- a/django_extensions/management/commands/merge_model_instances.py
+++ b/django_extensions/management/commands/merge_model_instances.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.apps import apps
from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.management import BaseCommand
diff --git a/django_extensions/management/commands/notes.py b/django_extensions/management/commands/notes.py
index 67d07a686..ed133ffb1 100644
--- a/django_extensions/management/commands/notes.py
+++ b/django_extensions/management/commands/notes.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import re
diff --git a/django_extensions/management/commands/print_settings.py b/django_extensions/management/commands/print_settings.py
index 026a514a6..057d489d1 100644
--- a/django_extensions/management/commands/print_settings.py
+++ b/django_extensions/management/commands/print_settings.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
print_settings
==============
diff --git a/django_extensions/management/commands/print_user_for_session.py b/django_extensions/management/commands/print_user_for_session.py
index c3760d738..5e36b00cb 100644
--- a/django_extensions/management/commands/print_user_for_session.py
+++ b/django_extensions/management/commands/print_user_for_session.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import importlib
from django.conf import settings
diff --git a/django_extensions/management/commands/raise_test_exception.py b/django_extensions/management/commands/raise_test_exception.py
index 9cc3b7599..4f69c2f54 100644
--- a/django_extensions/management/commands/raise_test_exception.py
+++ b/django_extensions/management/commands/raise_test_exception.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core.management.base import BaseCommand
from django_extensions.management.utils import signalcommand
diff --git a/django_extensions/management/commands/reset_db.py b/django_extensions/management/commands/reset_db.py
index 0c5472396..d91cbb073 100644
--- a/django_extensions/management/commands/reset_db.py
+++ b/django_extensions/management/commands/reset_db.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
reset_db command
diff --git a/django_extensions/management/commands/reset_schema.py b/django_extensions/management/commands/reset_schema.py
index 5e1d77c0b..e456b8a50 100644
--- a/django_extensions/management/commands/reset_schema.py
+++ b/django_extensions/management/commands/reset_schema.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Recreates the public schema for current database (PostgreSQL only).
Useful for Docker environments where you need to reset database
diff --git a/django_extensions/management/commands/runjob.py b/django_extensions/management/commands/runjob.py
index cfd38608f..2ecd31c4d 100644
--- a/django_extensions/management/commands/runjob.py
+++ b/django_extensions/management/commands/runjob.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import logging
from django.core.management.base import BaseCommand
diff --git a/django_extensions/management/commands/runjobs.py b/django_extensions/management/commands/runjobs.py
index 6ed0d2b20..43844886f 100644
--- a/django_extensions/management/commands/runjobs.py
+++ b/django_extensions/management/commands/runjobs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import logging
from django.apps import apps
diff --git a/django_extensions/management/commands/runprofileserver.py b/django_extensions/management/commands/runprofileserver.py
index 305a96eb8..c843d8e7a 100644
--- a/django_extensions/management/commands/runprofileserver.py
+++ b/django_extensions/management/commands/runprofileserver.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
runprofileserver.py
diff --git a/django_extensions/management/commands/runscript.py b/django_extensions/management/commands/runscript.py
index edf3a4787..5e64e8873 100644
--- a/django_extensions/management/commands/runscript.py
+++ b/django_extensions/management/commands/runscript.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import sys
import importlib
diff --git a/django_extensions/management/commands/runserver_plus.py b/django_extensions/management/commands/runserver_plus.py
index beb1f00bf..f8667720a 100644
--- a/django_extensions/management/commands/runserver_plus.py
+++ b/django_extensions/management/commands/runserver_plus.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import logging
import os
import re
diff --git a/django_extensions/management/commands/set_default_site.py b/django_extensions/management/commands/set_default_site.py
index 99e3eff40..4629888f8 100644
--- a/django_extensions/management/commands/set_default_site.py
+++ b/django_extensions/management/commands/set_default_site.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import socket
from django.conf import settings
diff --git a/django_extensions/management/commands/set_fake_emails.py b/django_extensions/management/commands/set_fake_emails.py
index 172eaa63a..269d13722 100644
--- a/django_extensions/management/commands/set_fake_emails.py
+++ b/django_extensions/management/commands/set_fake_emails.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
set_fake_emails.py
diff --git a/django_extensions/management/commands/set_fake_passwords.py b/django_extensions/management/commands/set_fake_passwords.py
index cf05c7928..b82208994 100644
--- a/django_extensions/management/commands/set_fake_passwords.py
+++ b/django_extensions/management/commands/set_fake_passwords.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
set_fake_passwords.py
diff --git a/django_extensions/management/commands/shell_plus.py b/django_extensions/management/commands/shell_plus.py
index aa5457a8b..d1367e0d1 100644
--- a/django_extensions/management/commands/shell_plus.py
+++ b/django_extensions/management/commands/shell_plus.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import inspect
import os
import sys
diff --git a/django_extensions/management/commands/show_permissions.py b/django_extensions/management/commands/show_permissions.py
index d1eed7195..2df0182f8 100644
--- a/django_extensions/management/commands/show_permissions.py
+++ b/django_extensions/management/commands/show_permissions.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand, CommandError
diff --git a/django_extensions/management/commands/show_template_tags.py b/django_extensions/management/commands/show_template_tags.py
index ddd238826..94ccd6394 100644
--- a/django_extensions/management/commands/show_template_tags.py
+++ b/django_extensions/management/commands/show_template_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import inspect
import os
import re
diff --git a/django_extensions/management/commands/show_urls.py b/django_extensions/management/commands/show_urls.py
index 26e41793e..c61739b83 100644
--- a/django_extensions/management/commands/show_urls.py
+++ b/django_extensions/management/commands/show_urls.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
import functools
import json
import re
diff --git a/django_extensions/management/commands/sqlcreate.py b/django_extensions/management/commands/sqlcreate.py
index fccfbcf47..62f3c4621 100644
--- a/django_extensions/management/commands/sqlcreate.py
+++ b/django_extensions/management/commands/sqlcreate.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import socket
import sys
import warnings
diff --git a/django_extensions/management/commands/sqldiff.py b/django_extensions/management/commands/sqldiff.py
index 0dd30f273..58a40ca8a 100644
--- a/django_extensions/management/commands/sqldiff.py
+++ b/django_extensions/management/commands/sqldiff.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
sqldiff.py - Prints the (approximated) difference between models and database
diff --git a/django_extensions/management/commands/sqldsn.py b/django_extensions/management/commands/sqldsn.py
index dbbab1674..592fded98 100644
--- a/django_extensions/management/commands/sqldsn.py
+++ b/django_extensions/management/commands/sqldsn.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
sqldns.py
diff --git a/django_extensions/management/commands/sync_s3.py b/django_extensions/management/commands/sync_s3.py
index 7e833ba1e..e591302a4 100644
--- a/django_extensions/management/commands/sync_s3.py
+++ b/django_extensions/management/commands/sync_s3.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Sync Media to S3
================
diff --git a/django_extensions/management/commands/syncdata.py b/django_extensions/management/commands/syncdata.py
index 8bbdd0cba..57b4bbba8 100644
--- a/django_extensions/management/commands/syncdata.py
+++ b/django_extensions/management/commands/syncdata.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
SyncData
========
diff --git a/django_extensions/management/commands/unreferenced_files.py b/django_extensions/management/commands/unreferenced_files.py
index 436b0ba54..efe37f1fe 100644
--- a/django_extensions/management/commands/unreferenced_files.py
+++ b/django_extensions/management/commands/unreferenced_files.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
from collections import defaultdict
diff --git a/django_extensions/management/commands/update_permissions.py b/django_extensions/management/commands/update_permissions.py
index 3ffab6e9c..9ea86ba2e 100644
--- a/django_extensions/management/commands/update_permissions.py
+++ b/django_extensions/management/commands/update_permissions.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.apps import apps as django_apps
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management import _get_all_permissions # type: ignore
diff --git a/django_extensions/management/commands/validate_templates.py b/django_extensions/management/commands/validate_templates.py
index 57e4f65ac..33b6a503c 100644
--- a/django_extensions/management/commands/validate_templates.py
+++ b/django_extensions/management/commands/validate_templates.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import fnmatch
diff --git a/django_extensions/management/commands/verify_named_urls.py b/django_extensions/management/commands/verify_named_urls.py
new file mode 100644
index 000000000..973cc842a
--- /dev/null
+++ b/django_extensions/management/commands/verify_named_urls.py
@@ -0,0 +1,236 @@
+from collections import defaultdict
+import fnmatch
+import functools
+import re
+import os
+
+from django.apps import apps
+from django.conf import settings
+from django.core.exceptions import ViewDoesNotExist
+from django.core.management.base import BaseCommand, CommandError
+from django.template.loader import get_template
+from django.urls import URLPattern, URLResolver
+from django.utils import translation
+
+from django_extensions.compat import get_template_setting
+from django_extensions.management.color import color_style, no_style
+from django_extensions.management.utils import signalcommand
+
+
+class RegexURLPattern:
+ pass
+
+
+class RegexURLResolver:
+ pass
+
+
+class LocaleRegexURLResolver:
+ pass
+
+
+class Command(BaseCommand):
+ args = ""
+ help = "Verify named URLs in templates"
+ ignores = set(
+ [
+ "*.swp",
+ "*~",
+ ]
+ )
+
+ def add_arguments(self, parser):
+ super().add_arguments(parser)
+ parser.add_argument(
+ "--ignore-app",
+ action="append",
+ dest="ignore_apps",
+ default=["admin"],
+ help="Ignore these apps",
+ )
+ parser.add_argument(
+ "--urlconf",
+ "-c",
+ dest="urlconf",
+ default="ROOT_URLCONF",
+ help="Set the settings URL conf variable to use",
+ )
+
+ def ignore_filename(self, filename):
+ filename = os.path.basename(filename)
+ for ignore_pattern in self.ignores:
+ if fnmatch.fnmatch(filename, ignore_pattern):
+ return True
+ return False
+
+ @signalcommand
+ def handle(self, *args, **options):
+ style = no_style() if options["no_color"] else color_style()
+
+ self.names = defaultdict(list)
+ self.views = {}
+
+ self.collect_templates(options)
+ self.collect_views(options)
+
+ for name in sorted(self.names):
+ n = len(self.names[name])
+ color = style.MODULE
+ try:
+ v = self.views[name]
+ print(
+ style.INFO(
+ f"Name: {name} ({n} occurences, handled in {v[0]}, {v[1]})"
+ )
+ )
+ except KeyError:
+ print(style.URL_NAME(f"Name: {name} ({n} occurences, UNKNOWN VIEW)"))
+ color = style.URL_NAME
+ for item in self.names[name]:
+ print(color(f"* {item[0]}:{item[1]}"))
+
+ def collect_templates(self, options):
+ template_dirs = set(get_template_setting("DIRS", []))
+
+ for app in apps.get_app_configs():
+ if app.name.split(".")[-1] in options["ignore_apps"]:
+ continue
+ app_template_dir = os.path.join(app.path, "templates")
+ if os.path.isdir(app_template_dir):
+ template_dirs.add(app_template_dir)
+
+ settings.TEMPLATES[0]["DIRS"] = list(template_dirs)
+
+ self.template_parse_errors = 0
+ self.names_re = re.compile(r"\{%\s*url\s*['\"]([\w\-]+)['\"]")
+
+ for template_dir in template_dirs:
+ for root, dirs, filenames in os.walk(template_dir):
+ for filename in filenames:
+ if self.ignore_filename(filename):
+ continue
+ filepath = os.path.join(root, filename)
+ self.process_template(filepath)
+
+ if self.template_parse_errors > 0:
+ self.stdout.write(
+ f"{self.template_parse_errors} template parse errors found"
+ )
+
+ def collect_views(self, options):
+ urlconf = options["urlconf"]
+
+ if not hasattr(settings, urlconf):
+ raise CommandError(
+ "Settings module {} does not have the attribute {}.".format(
+ settings, urlconf
+ )
+ )
+
+ try:
+ urlconf = __import__(getattr(settings, urlconf), {}, {}, [""])
+ except Exception as e:
+ raise CommandError(
+ "Error occurred while trying to load %s: %s"
+ % (getattr(settings, urlconf), str(e))
+ )
+
+ view_functions = self.extract_views_from_urlpatterns(urlconf.urlpatterns)
+ for func, regex, view in view_functions:
+ if view is not None:
+ if isinstance(func, functools.partial):
+ func = func.func
+ if hasattr(func, "view_class"):
+ func = func.view_class
+ if hasattr(func, "__name__"):
+ func_name = func.__name__
+ elif hasattr(func, "__class__"):
+ func_name = "%s()" % func.__class__.__name__
+ else:
+ func_name = re.sub(r" at 0x[0-9a-f]+", "", repr(func))
+
+ self.views[view] = (func_name, regex)
+
+ def process_template(self, filepath):
+ try:
+ get_template(filepath)
+ except Exception:
+ self.template_parse_errors += 1
+ self.stdout.write(f"Error parsing template {filepath}")
+
+ with open(filepath, "r") as file:
+ lineno = 1
+ for line in file:
+ for match in self.names_re.findall(line):
+ self.names[match].append((filepath, lineno))
+ lineno += 1
+
+ # copied from show_urls.py
+ def extract_views_from_urlpatterns(self, urlpatterns, base="", namespace=None):
+ """
+ Return a list of views from a list of urlpatterns.
+
+ Each object in the returned list is a three-tuple: (view_func, regex, name)
+ """
+ views = []
+ for p in urlpatterns:
+ if isinstance(p, (URLPattern, RegexURLPattern)):
+ try:
+ if not p.name:
+ name = p.name
+ elif namespace:
+ name = "{0}:{1}".format(namespace, p.name)
+ else:
+ name = p.name
+ pattern = describe_pattern(p)
+ views.append((p.callback, base + pattern, name))
+ except ViewDoesNotExist:
+ continue
+ elif isinstance(p, (URLResolver, RegexURLResolver)):
+ try:
+ patterns = p.url_patterns
+ except ImportError:
+ continue
+ if namespace and p.namespace:
+ _namespace = "{0}:{1}".format(namespace, p.namespace)
+ else:
+ _namespace = p.namespace or namespace
+ pattern = describe_pattern(p)
+ if isinstance(p, LocaleRegexURLResolver):
+ for language in self.LANGUAGES:
+ with translation.override(language[0]):
+ views.extend(
+ self.extract_views_from_urlpatterns(
+ patterns, base + pattern, namespace=_namespace
+ )
+ )
+ else:
+ views.extend(
+ self.extract_views_from_urlpatterns(
+ patterns, base + pattern, namespace=_namespace
+ )
+ )
+ elif hasattr(p, "_get_callback"):
+ try:
+ views.append(
+ (p._get_callback(), base + describe_pattern(p), p.name)
+ )
+ except ViewDoesNotExist:
+ continue
+ elif hasattr(p, "url_patterns") or hasattr(p, "_get_url_patterns"):
+ try:
+ patterns = p.url_patterns
+ except ImportError:
+ continue
+ views.extend(
+ self.extract_views_from_urlpatterns(
+ patterns, base + describe_pattern(p), namespace=namespace
+ )
+ )
+ else:
+ raise TypeError("%s does not appear to be a urlpattern object" % p)
+ return views
+
+
+def describe_pattern(p):
+ return str(p.pattern)
diff --git a/django_extensions/management/debug_cursor.py b/django_extensions/management/debug_cursor.py
index 5236b24b7..d04cd2e82 100644
--- a/django_extensions/management/debug_cursor.py
+++ b/django_extensions/management/debug_cursor.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import time
import traceback
from contextlib import contextmanager
diff --git a/django_extensions/management/email_notifications.py b/django_extensions/management/email_notifications.py
index 51f40336e..9f070b0a4 100644
--- a/django_extensions/management/email_notifications.py
+++ b/django_extensions/management/email_notifications.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
import traceback
diff --git a/django_extensions/management/jobs.py b/django_extensions/management/jobs.py
index ebf7462d0..59668f052 100644
--- a/django_extensions/management/jobs.py
+++ b/django_extensions/management/jobs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import sys
import importlib
diff --git a/django_extensions/management/modelviz.py b/django_extensions/management/modelviz.py
index 93deb08d8..b46a5571d 100644
--- a/django_extensions/management/modelviz.py
+++ b/django_extensions/management/modelviz.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
modelviz.py - DOT file generator for Django Models
diff --git a/django_extensions/management/mysql.py b/django_extensions/management/mysql.py
index 3ce306687..0adec2900 100644
--- a/django_extensions/management/mysql.py
+++ b/django_extensions/management/mysql.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import configparser
diff --git a/django_extensions/management/notebook_extension.py b/django_extensions/management/notebook_extension.py
index 9c38c2a82..477a6b986 100644
--- a/django_extensions/management/notebook_extension.py
+++ b/django_extensions/management/notebook_extension.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
def load_ipython_extension(ipython):
from django.core.management.color import no_style
from django_extensions.management.shells import import_objects
diff --git a/django_extensions/management/shells.py b/django_extensions/management/shells.py
index bbd6a6f09..4afb9e9da 100644
--- a/django_extensions/management/shells.py
+++ b/django_extensions/management/shells.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import ast
import traceback
import warnings
diff --git a/django_extensions/management/signals.py b/django_extensions/management/signals.py
index 3a490cf36..378d30aad 100644
--- a/django_extensions/management/signals.py
+++ b/django_extensions/management/signals.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.dispatch import Signal
run_minutely_jobs = Signal()
diff --git a/django_extensions/management/technical_response.py b/django_extensions/management/technical_response.py
index 08b776960..fd98cea59 100644
--- a/django_extensions/management/technical_response.py
+++ b/django_extensions/management/technical_response.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import threading
from django.core.handlers.wsgi import WSGIHandler
diff --git a/django_extensions/management/utils.py b/django_extensions/management/utils.py
index 0a31460aa..f12bc82f0 100644
--- a/django_extensions/management/utils.py
+++ b/django_extensions/management/utils.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import logging
import os
import sys
diff --git a/django_extensions/mongodb/fields/__init__.py b/django_extensions/mongodb/fields/__init__.py
index 6773f831c..77754a38e 100644
--- a/django_extensions/mongodb/fields/__init__.py
+++ b/django_extensions/mongodb/fields/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
MongoDB model fields emulating Django Extensions' additional model fields
diff --git a/django_extensions/mongodb/fields/json.py b/django_extensions/mongodb/fields/json.py
index e4d855429..e7c10ef6b 100644
--- a/django_extensions/mongodb/fields/json.py
+++ b/django_extensions/mongodb/fields/json.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
JSONField automatically serializes most Python terms to JSON data.
Creates a TEXT field with a default value of "{}". See test_json.py for
diff --git a/django_extensions/mongodb/models.py b/django_extensions/mongodb/models.py
index 50682fae8..19c67cd4f 100644
--- a/django_extensions/mongodb/models.py
+++ b/django_extensions/mongodb/models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import datetime
from django.utils.translation import gettext_lazy as _
diff --git a/django_extensions/settings.py b/django_extensions/settings.py
index 8562bb86c..b35c84cad 100644
--- a/django_extensions/settings.py
+++ b/django_extensions/settings.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
from django.conf import settings
@@ -43,3 +42,5 @@
)
DEFAULT_PRINT_SQL_TRUNCATE_CHARS = 1000
+
+RUNSERVER_PLUS_EXCLUDE_PATTERNS = ["**/__pycache__/*"]
diff --git a/django_extensions/templates/django_extensions/graph_models/django2018/label.dot b/django_extensions/templates/django_extensions/graph_models/django2018/label.dot
index 6303a806c..b88f50641 100644
--- a/django_extensions/templates/django_extensions/graph_models/django2018/label.dot
+++ b/django_extensions/templates/django_extensions/graph_models/django2018/label.dot
@@ -12,8 +12,8 @@
style="rounded"{% endif %}
{% indentby 2 if use_subgraph %}{% for model in graph.models %}
{{ model.app_name }}_{{ model.name }} [label=<
-
-
+
+
{{ model.label }}{% if model.abstracts %} <{{ model.abstracts|join:"," }}>{% endif %}
|
diff --git a/django_extensions/templates/django_extensions/graph_models/original/label.dot b/django_extensions/templates/django_extensions/graph_models/original/label.dot
index 6db9980df..a46fbbe12 100644
--- a/django_extensions/templates/django_extensions/graph_models/original/label.dot
+++ b/django_extensions/templates/django_extensions/graph_models/original/label.dot
@@ -12,8 +12,8 @@
style="rounded"{% endif %}
{% indentby 2 if use_subgraph %}{% for model in graph.models %}
{{ model.app_name }}_{{ model.name }} [label=<
-
-
+
+
{{ model.label }}{% if model.abstracts %} <{{ model.abstracts|join:"," }}>{% endif %}
|
diff --git a/django_extensions/templatetags/debugger_tags.py b/django_extensions/templatetags/debugger_tags.py
index 5ff7dd04d..67d7e1436 100644
--- a/django_extensions/templatetags/debugger_tags.py
+++ b/django_extensions/templatetags/debugger_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Make debugging Django templates easier.
diff --git a/django_extensions/templatetags/highlighting.py b/django_extensions/templatetags/highlighting.py
index 9ff0e8372..6153e0069 100644
--- a/django_extensions/templatetags/highlighting.py
+++ b/django_extensions/templatetags/highlighting.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Similar to syntax_color.py but this is intended more for being able to
copy+paste actual code into your Django templates without needing to
diff --git a/django_extensions/templatetags/indent_text.py b/django_extensions/templatetags/indent_text.py
index fb3f2804b..a5c4abbce 100644
--- a/django_extensions/templatetags/indent_text.py
+++ b/django_extensions/templatetags/indent_text.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django import template
register = template.Library()
diff --git a/django_extensions/templatetags/syntax_color.py b/django_extensions/templatetags/syntax_color.py
index c82fc1888..180150ee0 100644
--- a/django_extensions/templatetags/syntax_color.py
+++ b/django_extensions/templatetags/syntax_color.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
r"""
Template filter for rendering a string with syntax highlighting.
It relies on Pygments to accomplish this.
diff --git a/django_extensions/templatetags/widont.py b/django_extensions/templatetags/widont.py
index a0ab237b5..fd70d97d6 100644
--- a/django_extensions/templatetags/widont.py
+++ b/django_extensions/templatetags/widont.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import re
from django.template import Library
diff --git a/django_extensions/utils/__init__.py b/django_extensions/utils/__init__.py
index a5923e813..689090c6a 100644
--- a/django_extensions/utils/__init__.py
+++ b/django_extensions/utils/__init__.py
@@ -1,2 +1 @@
-# -*- coding: utf-8 -*-
from .internal_ips import InternalIPS # NOQA
diff --git a/django_extensions/utils/deprecation.py b/django_extensions/utils/deprecation.py
index 9b7b835c2..b627c7659 100644
--- a/django_extensions/utils/deprecation.py
+++ b/django_extensions/utils/deprecation.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-
-
class MarkedForDeprecationWarning(DeprecationWarning):
pass
diff --git a/django_extensions/utils/dia2django.py b/django_extensions/utils/dia2django.py
index 06736daec..7b2e53c68 100644
--- a/django_extensions/utils/dia2django.py
+++ b/django_extensions/utils/dia2django.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
Author Igor Támara igor@tamarapatino.org
Use this little program as you wish, if you
diff --git a/django_extensions/utils/internal_ips.py b/django_extensions/utils/internal_ips.py
index 0a00e854d..afefb09b7 100644
--- a/django_extensions/utils/internal_ips.py
+++ b/django_extensions/utils/internal_ips.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from collections.abc import Container
import ipaddress
import itertools
diff --git a/django_extensions/validators.py b/django_extensions/validators.py
index 05b12ad28..d5b93a3a5 100644
--- a/django_extensions/validators.py
+++ b/django_extensions/validators.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import unicodedata
import binascii
diff --git a/docs/conf.py b/docs/conf.py
index 442b3d683..ca5f3bdf7 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
#
# django-extensions documentation build configuration file, created by
# sphinx-quickstart on Wed Apr 1 20:39:40 2009.
diff --git a/docs/graph_models.rst b/docs/graph_models.rst
index a736d4ac2..845364f58 100644
--- a/docs/graph_models.rst
+++ b/docs/graph_models.rst
@@ -84,6 +84,39 @@ Documentation on how to create dot files can be found here: https://www.graphviz
the Django app *django-template-minifier* this automatically removed the newlines before/after
template tags even for non-HTML templates which leads to a malformed file.
+
+App-based Styling
+-----------------
+
+You can style models by app to visually distinguish them in the generated graph. This is useful when working with multiple apps that have interrelated models.
+
+To use this feature, provide a JSON file specifying styles for each app. You can either:
+
+- Place a `.app-style.json` file in the project root, or
+- Use the `--app-style` command line option to specify a path to the file::
+
+ $ ./manage.py graph_models -a --app-style path/to/style.json -o styled_output.png
+
+The JSON file should map app labels to style dictionaries. The app labels can be exact matches or use wildcards (e.g., `django.*`) where the last entry wins.
+For example:
+
+.. code-block:: json
+
+ {
+ "app1": {"bg": "#341b56"},
+ "app2": {"bg": "#1b3956"},
+ "django.*": {"bg": "#561b4c"},
+ "django.contrib.auth": {"bg": "#c41e3a"}
+ }
+
+Currently, the supported style option is `bg` (background color), but the system is designed to be extended in the future with support for additional styling such as font, shape, or border.
+*Note: help is wanted to update themes to support more style options*
+
+This feature allows you to generate a single graph that highlights model groupings by app while still showing relationships across apps.
+
+*Note: an exception will be raised if the provided json file does not exist*
+
+
Example Usage
-------------
@@ -98,6 +131,9 @@ image by using the *graph_models* command::
# Create a PNG image file called my_project_visualized.png with application grouping
$ ./manage.py graph_models -a -g -o my_project_visualized.png
+ # Create a PNG with per-app styling
+ $ ./manage.py graph_models -a --app-style path/to/style.json -o my_styled_project.png
+
# Same example but with explicit selection of pygraphviz or pydot
$ ./manage.py graph_models --pygraphviz -a -g -o my_project_visualized.png
$ ./manage.py graph_models --pydot -a -g -o my_project_visualized.png
diff --git a/docs/verify_named_urls.rst b/docs/verify_named_urls.rst
new file mode 100644
index 000000000..54ab2e8c0
--- /dev/null
+++ b/docs/verify_named_urls.rst
@@ -0,0 +1,51 @@
+verify_named_urls
+=================
+
+:synopsis: Verify named URLs in templates
+
+This command will check whether the templates in your apps have matching named
+URLs defined in your views.
+
+For example this template, when rendered, would result in a 500 on your site:
+
+::
+
+ Some template text
+
+ {% url 'this-view-does-not-exist' %}
+
+ Some other template text
+
+The intention is to help catching typos and leftover references when you rename
+or remove a named view - before real users hit the relevant templates.
+
+Options
+-------
+
+ignore-apps
+~~~~~~~~~~~
+
+Ignore these apps (comma separaded list) when looking for templates to check.
+Default is "admin".
+
+urlconf
+~~~~~~~
+
+Set the settings URL conf variable to use
+
+
+Usage Example
+-------------
+
+::
+
+ ./manage.py verify_named_urls
+
+Example output, where the first named URL is defined, the second is missing:
+
+::
+
+ Name: entry-detail (1 occurences, handled in EntryDetailView, blog/)
+ * /home/myuser/django/blog/templates/blog/entry-list.html:9
+ Name: this-view-is-removed-by-now (1 occurences, UNKNOWN VIEW)
+ * /home/myuser/django/blog/templates/blog/reference.html:6
diff --git a/manage.py b/manage.py
index 1ca113a04..20a710164 100755
--- a/manage.py
+++ b/manage.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python
-# -*- coding: utf-8 -*-
import os
import sys
diff --git a/pyproject.toml b/pyproject.toml
index de2748d4c..dd4f0037b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,7 +2,7 @@
build-backend = "setuptools.build_meta"
requires = [
"django>=4.2",
- "setuptools>=61.2",
+ "setuptools>=77",
]
[project]
diff --git a/tests/__init__.py b/tests/__init__.py
index d308e8165..d9ef5a71c 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from unittest import mock
diff --git a/tests/auth/test_mixins.py b/tests/auth/test_mixins.py
index e1d799316..fbc333cf7 100644
--- a/tests/auth/test_mixins.py
+++ b/tests/auth/test_mixins.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import TestCase, RequestFactory
from django.http import HttpResponse
from django.views.generic import DetailView
diff --git a/tests/collisions/apps.py b/tests/collisions/apps.py
index 36ed52dfd..ce53e9dc4 100644
--- a/tests/collisions/apps.py
+++ b/tests/collisions/apps.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.apps import AppConfig
diff --git a/tests/collisions/models.py b/tests/collisions/models.py
index 057b4e496..1c88e23b8 100644
--- a/tests/collisions/models.py
+++ b/tests/collisions/models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.db import models
diff --git a/tests/db/fields/test_uniq_field_mixin.py b/tests/db/fields/test_uniq_field_mixin.py
index 69a5aa9d2..9593bc0f2 100644
--- a/tests/db/fields/test_uniq_field_mixin.py
+++ b/tests/db/fields/test_uniq_field_mixin.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from unittest import mock
from django.db import models
diff --git a/tests/db/fields/test_uniq_field_mixin_compat.py b/tests/db/fields/test_uniq_field_mixin_compat.py
index 7b8e47ad2..e32dd0b9d 100644
--- a/tests/db/fields/test_uniq_field_mixin_compat.py
+++ b/tests/db/fields/test_uniq_field_mixin_compat.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from unittest import mock
from django.db import models
diff --git a/tests/jobs/daily/test_cache_cleanup.py b/tests/jobs/daily/test_cache_cleanup.py
index a099eb790..de61bd60f 100644
--- a/tests/jobs/daily/test_cache_cleanup.py
+++ b/tests/jobs/daily/test_cache_cleanup.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core.cache import caches
from django.core.management import call_command
from django.test import TestCase
diff --git a/tests/management/commands/error_raising_command.py b/tests/management/commands/error_raising_command.py
index b46e06ab8..0cc77456e 100644
--- a/tests/management/commands/error_raising_command.py
+++ b/tests/management/commands/error_raising_command.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.base import LoggingBaseCommand
diff --git a/tests/management/commands/shell_plus_tests/test_collision_resolver.py b/tests/management/commands/shell_plus_tests/test_collision_resolver.py
index c237cab5f..785065569 100644
--- a/tests/management/commands/shell_plus_tests/test_collision_resolver.py
+++ b/tests/management/commands/shell_plus_tests/test_collision_resolver.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
from django.contrib.auth.models import Group, Permission
from django.test import override_settings
diff --git a/tests/management/commands/shell_plus_tests/test_import_subclasses.py b/tests/management/commands/shell_plus_tests/test_import_subclasses.py
index fa3bbd44a..c30895da6 100644
--- a/tests/management/commands/shell_plus_tests/test_import_subclasses.py
+++ b/tests/management/commands/shell_plus_tests/test_import_subclasses.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from typing import Optional, Set # noqa
from django.conf import settings
diff --git a/tests/management/commands/shell_plus_tests/test_shell_plus.py b/tests/management/commands/shell_plus_tests/test_shell_plus.py
index 6e2a51a39..c97e068af 100644
--- a/tests/management/commands/shell_plus_tests/test_shell_plus.py
+++ b/tests/management/commands/shell_plus_tests/test_shell_plus.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import re
import pytest
diff --git a/tests/management/commands/shell_plus_tests/test_utils.py b/tests/management/commands/shell_plus_tests/test_utils.py
index 68eea7279..3d1110aec 100644
--- a/tests/management/commands/shell_plus_tests/test_utils.py
+++ b/tests/management/commands/shell_plus_tests/test_utils.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
from io import StringIO
from typing import Dict, Set, Type # noqa
diff --git a/tests/management/commands/test_admin_generator.py b/tests/management/commands/test_admin_generator.py
index 19c9fe0ab..1a9c2bc80 100644
--- a/tests/management/commands/test_admin_generator.py
+++ b/tests/management/commands/test_admin_generator.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.core.management import call_command
diff --git a/tests/management/commands/test_clear_cache.py b/tests/management/commands/test_clear_cache.py
index 46e1b4567..9d0a9860c 100644
--- a/tests/management/commands/test_clear_cache.py
+++ b/tests/management/commands/test_clear_cache.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from unittest import mock
import os
from io import StringIO
diff --git a/tests/management/commands/test_create_command.py b/tests/management/commands/test_create_command.py
index 540c09e2b..272f3c1c0 100644
--- a/tests/management/commands/test_create_command.py
+++ b/tests/management/commands/test_create_command.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
diff --git a/tests/management/commands/test_create_jobs.py b/tests/management/commands/test_create_jobs.py
index c1b97e417..913af3998 100644
--- a/tests/management/commands/test_create_jobs.py
+++ b/tests/management/commands/test_create_jobs.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
diff --git a/tests/management/commands/test_create_template_tags.py b/tests/management/commands/test_create_template_tags.py
index 7218f2d41..078a9491d 100644
--- a/tests/management/commands/test_create_template_tags.py
+++ b/tests/management/commands/test_create_template_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
diff --git a/tests/management/commands/test_delete_squashed_migrations.py b/tests/management/commands/test_delete_squashed_migrations.py
index c47add571..4895d76d2 100644
--- a/tests/management/commands/test_delete_squashed_migrations.py
+++ b/tests/management/commands/test_delete_squashed_migrations.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import pytest
diff --git a/tests/management/commands/test_describe_form.py b/tests/management/commands/test_describe_form.py
index 6f569e228..5a509f56f 100644
--- a/tests/management/commands/test_describe_form.py
+++ b/tests/management/commands/test_describe_form.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.test import TestCase
diff --git a/tests/management/commands/test_drop_test_database.py b/tests/management/commands/test_drop_test_database.py
index 84cea2549..60da29998 100644
--- a/tests/management/commands/test_drop_test_database.py
+++ b/tests/management/commands/test_drop_test_database.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import importlib.util
from io import StringIO
from unittest.mock import MagicMock, Mock, PropertyMock, call, patch
diff --git a/tests/management/commands/test_export_emails.py b/tests/management/commands/test_export_emails.py
index ca1a73d1f..e03cc9a16 100644
--- a/tests/management/commands/test_export_emails.py
+++ b/tests/management/commands/test_export_emails.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core.management import call_command
from django_extensions.management.commands.export_emails import full_name
diff --git a/tests/management/commands/test_generate_password.py b/tests/management/commands/test_generate_password.py
index f946b41d7..5bda529d1 100644
--- a/tests/management/commands/test_generate_password.py
+++ b/tests/management/commands/test_generate_password.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core.management import call_command
diff --git a/tests/management/commands/test_generate_secret_key.py b/tests/management/commands/test_generate_secret_key.py
index 99b41e6f0..10dc3b089 100644
--- a/tests/management/commands/test_generate_secret_key.py
+++ b/tests/management/commands/test_generate_secret_key.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.core.management import call_command
diff --git a/tests/management/commands/test_graph_models.py b/tests/management/commands/test_graph_models.py
index fa9e7578a..0191d1a83 100644
--- a/tests/management/commands/test_graph_models.py
+++ b/tests/management/commands/test_graph_models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import json
import os
import re
diff --git a/tests/management/commands/test_list_signals.py b/tests/management/commands/test_list_signals.py
index d97abc973..48b0fedfa 100644
--- a/tests/management/commands/test_list_signals.py
+++ b/tests/management/commands/test_list_signals.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import re
from io import StringIO
diff --git a/tests/management/commands/test_mail_debug.py b/tests/management/commands/test_mail_debug.py
index 5bd18ae56..514011894 100644
--- a/tests/management/commands/test_mail_debug.py
+++ b/tests/management/commands/test_mail_debug.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core.management import call_command
from unittest import mock
diff --git a/tests/management/commands/test_managestate.py b/tests/management/commands/test_managestate.py
index 0ae25e97e..2b9a4659b 100644
--- a/tests/management/commands/test_managestate.py
+++ b/tests/management/commands/test_managestate.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import json
from pathlib import Path
from tempfile import TemporaryDirectory
diff --git a/tests/management/commands/test_notes.py b/tests/management/commands/test_notes.py
index 34d6dc620..ae1980b12 100644
--- a/tests/management/commands/test_notes.py
+++ b/tests/management/commands/test_notes.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
from django.core.management import call_command
@@ -8,8 +7,9 @@ def test_without_args(capsys, settings):
call_command("notes")
out, err = capsys.readouterr()
+ print([out])
assert (
- "tests/testapp/file_without_utf8_notes.py:\n * [ 3] TODO this is a test todo\n\n"
+ "tests/testapp/file_without_utf8_notes.py:\n * [ 1] TODO this is a test todo\n\n"
in out
)
@@ -19,7 +19,7 @@ def test_with_utf8(capsys, settings):
out, err = capsys.readouterr()
assert (
- "tests/testapp/file_with_utf8_notes.py:\n * [ 3] TODO Russian text followed: Это техт на кириллице\n\n"
+ "tests/testapp/file_with_utf8_notes.py:\n * [ 1] TODO Russian text followed: Это техт на кириллице\n\n"
in out
)
diff --git a/tests/management/commands/test_print_settings.py b/tests/management/commands/test_print_settings.py
index 3e97f541b..9724228a3 100644
--- a/tests/management/commands/test_print_settings.py
+++ b/tests/management/commands/test_print_settings.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
import pytest
from django.core.management import CommandError, call_command
diff --git a/tests/management/commands/test_print_user_for_session.py b/tests/management/commands/test_print_user_for_session.py
index f42a18d15..733980775 100644
--- a/tests/management/commands/test_print_user_for_session.py
+++ b/tests/management/commands/test_print_user_for_session.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from importlib import import_module
from io import StringIO
diff --git a/tests/management/commands/test_raise_test_exception.py b/tests/management/commands/test_raise_test_exception.py
index 74f7a8e7a..77bea014d 100644
--- a/tests/management/commands/test_raise_test_exception.py
+++ b/tests/management/commands/test_raise_test_exception.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from django.core.management import call_command
diff --git a/tests/management/commands/test_reset_db.py b/tests/management/commands/test_reset_db.py
index d36d63c17..845da6551 100644
--- a/tests/management/commands/test_reset_db.py
+++ b/tests/management/commands/test_reset_db.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import importlib.util
import os
from io import StringIO
diff --git a/tests/management/commands/test_reset_schema.py b/tests/management/commands/test_reset_schema.py
index 237fb5b65..944797029 100644
--- a/tests/management/commands/test_reset_schema.py
+++ b/tests/management/commands/test_reset_schema.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.core.management import CommandError, call_command
diff --git a/tests/management/commands/test_runjob.py b/tests/management/commands/test_runjob.py
index 8eb142f16..0a89271a5 100644
--- a/tests/management/commands/test_runjob.py
+++ b/tests/management/commands/test_runjob.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import logging
import sys
from io import StringIO
diff --git a/tests/management/commands/test_runserver_plus.py b/tests/management/commands/test_runserver_plus.py
index c82948486..0de0bd1a6 100644
--- a/tests/management/commands/test_runserver_plus.py
+++ b/tests/management/commands/test_runserver_plus.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from django.core.management import call_command
diff --git a/tests/management/commands/test_set_default_site.py b/tests/management/commands/test_set_default_site.py
index 671526afa..ddc9c2b71 100644
--- a/tests/management/commands/test_set_default_site.py
+++ b/tests/management/commands/test_set_default_site.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.conf import settings
diff --git a/tests/management/commands/test_set_fake_emails.py b/tests/management/commands/test_set_fake_emails.py
index 8bb9c3ab6..93878c0e3 100644
--- a/tests/management/commands/test_set_fake_emails.py
+++ b/tests/management/commands/test_set_fake_emails.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.core.management import call_command, CommandError
diff --git a/tests/management/commands/test_set_fake_passwords.py b/tests/management/commands/test_set_fake_passwords.py
index e72714d62..0bcc2317f 100644
--- a/tests/management/commands/test_set_fake_passwords.py
+++ b/tests/management/commands/test_set_fake_passwords.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
import pytest
diff --git a/tests/management/commands/test_show_permissions.py b/tests/management/commands/test_show_permissions.py
index 31f157784..91b6fc988 100644
--- a/tests/management/commands/test_show_permissions.py
+++ b/tests/management/commands/test_show_permissions.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
from io import StringIO
diff --git a/tests/management/commands/test_show_template_tags.py b/tests/management/commands/test_show_template_tags.py
index f13919784..a1c50ab82 100644
--- a/tests/management/commands/test_show_template_tags.py
+++ b/tests/management/commands/test_show_template_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.core.management import call_command
diff --git a/tests/management/commands/test_show_urls.py b/tests/management/commands/test_show_urls.py
index cadcbf669..55eeac130 100644
--- a/tests/management/commands/test_show_urls.py
+++ b/tests/management/commands/test_show_urls.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.urls import path
diff --git a/tests/management/commands/test_sqlcreate.py b/tests/management/commands/test_sqlcreate.py
index d4788d91f..8d96a5911 100644
--- a/tests/management/commands/test_sqlcreate.py
+++ b/tests/management/commands/test_sqlcreate.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.core.management import CommandError, call_command
diff --git a/tests/management/commands/test_sqldsn.py b/tests/management/commands/test_sqldsn.py
index 63079f88b..0ef803cc4 100644
--- a/tests/management/commands/test_sqldsn.py
+++ b/tests/management/commands/test_sqldsn.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from io import StringIO
from django.core.management import CommandError, call_command
diff --git a/tests/management/commands/test_sync_s3.py b/tests/management/commands/test_sync_s3.py
index cb0b8a71d..bd852ee4c 100644
--- a/tests/management/commands/test_sync_s3.py
+++ b/tests/management/commands/test_sync_s3.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
diff --git a/tests/management/commands/test_syncdata.py b/tests/management/commands/test_syncdata.py
index 78cac464d..3554b7b9b 100644
--- a/tests/management/commands/test_syncdata.py
+++ b/tests/management/commands/test_syncdata.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import pytest
diff --git a/tests/management/commands/test_unreferenced_files.py b/tests/management/commands/test_unreferenced_files.py
index 3bdc65d9c..c48b25c78 100644
--- a/tests/management/commands/test_unreferenced_files.py
+++ b/tests/management/commands/test_unreferenced_files.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
from io import StringIO
diff --git a/tests/management/commands/test_update_permissions.py b/tests/management/commands/test_update_permissions.py
index ebc8a6d6d..99db8336c 100644
--- a/tests/management/commands/test_update_permissions.py
+++ b/tests/management/commands/test_update_permissions.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import sys
from io import StringIO
diff --git a/tests/management/commands/test_validate_templates.py b/tests/management/commands/test_validate_templates.py
index 449b53ce0..82eb04b99 100644
--- a/tests/management/commands/test_validate_templates.py
+++ b/tests/management/commands/test_validate_templates.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
from io import StringIO
diff --git a/tests/management/test_email_notifications.py b/tests/management/test_email_notifications.py
index 432537e20..db2f197dd 100644
--- a/tests/management/test_email_notifications.py
+++ b/tests/management/test_email_notifications.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core import mail
from django.core.management import call_command
from django.test import TestCase
diff --git a/tests/management/test_modelviz.py b/tests/management/test_modelviz.py
index d91192a38..8b276127b 100644
--- a/tests/management/test_modelviz.py
+++ b/tests/management/test_modelviz.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import SimpleTestCase
from django_extensions.management.modelviz import generate_graph_data, ON_DELETE_COLORS
diff --git a/tests/pythonrc.py b/tests/pythonrc.py
index a4576c5b1..1c1a48a25 100644
--- a/tests/pythonrc.py
+++ b/tests/pythonrc.py
@@ -1,3 +1,2 @@
-# -*- coding: utf-8 -*-
def pythonrc_test_func():
return "pythonrc was loaded"
diff --git a/tests/runner.py b/tests/runner.py
index ffa42cde2..1b75fa439 100644
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -1,6 +1,3 @@
-# -*- coding: utf-8 -*-
-
-
class PytestTestRunner:
"""Runs pytest to discover and run tests."""
diff --git a/tests/templatetags/test_highlighting.py b/tests/templatetags/test_highlighting.py
index fe001f03b..aa28903f1 100644
--- a/tests/templatetags/test_highlighting.py
+++ b/tests/templatetags/test_highlighting.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.template import Context, Template, TemplateSyntaxError
from django.test import TestCase
diff --git a/tests/templatetags/test_indent_text.py b/tests/templatetags/test_indent_text.py
index 8cfebc0e3..cd606958b 100644
--- a/tests/templatetags/test_indent_text.py
+++ b/tests/templatetags/test_indent_text.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import TestCase
from django.template import Context, Template, TemplateSyntaxError
diff --git a/tests/templatetags/test_syntax_color.py b/tests/templatetags/test_syntax_color.py
index 31d0981d1..b2f82c3a4 100644
--- a/tests/templatetags/test_syntax_color.py
+++ b/tests/templatetags/test_syntax_color.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import html
import shutil
diff --git a/tests/test_admin_filter.py b/tests/test_admin_filter.py
index 7279352d2..06ae238a5 100644
--- a/tests/test_admin_filter.py
+++ b/tests/test_admin_filter.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from unittest.mock import Mock
from django.test import RequestFactory, TestCase
diff --git a/tests/test_admin_widgets.py b/tests/test_admin_widgets.py
index 3f46044b5..7d965ee68 100644
--- a/tests/test_admin_widgets.py
+++ b/tests/test_admin_widgets.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import TestCase
from django.utils.text import Truncator
diff --git a/tests/test_autoslug_fields.py b/tests/test_autoslug_fields.py
index 28aeea029..7c83a8608 100644
--- a/tests/test_autoslug_fields.py
+++ b/tests/test_autoslug_fields.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from django.db import migrations, models
diff --git a/tests/test_clean_pyc.py b/tests/test_clean_pyc.py
index 0791906fb..1f9cfaead 100644
--- a/tests/test_clean_pyc.py
+++ b/tests/test_clean_pyc.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import fnmatch
import os
import shutil
diff --git a/tests/test_color.py b/tests/test_color.py
index 191baf146..e419a0f05 100644
--- a/tests/test_color.py
+++ b/tests/test_color.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import SimpleTestCase
from django_extensions.management import color
from . import force_color_support
diff --git a/tests/test_compat.py b/tests/test_compat.py
index 022da4e24..d0d5e9a03 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import TestCase
from django.test.utils import override_settings
diff --git a/tests/test_compile_pyc.py b/tests/test_compile_pyc.py
index be28e18e1..486fdbf25 100644
--- a/tests/test_compile_pyc.py
+++ b/tests/test_compile_pyc.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import fnmatch
import os
diff --git a/tests/test_dumpscript.py b/tests/test_dumpscript.py
index d0e1ca799..e68a17825 100644
--- a/tests/test_dumpscript.py
+++ b/tests/test_dumpscript.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import ast
import shutil
import sys
diff --git a/tests/test_find_template.py b/tests/test_find_template.py
index ba378a377..841e09ef1 100644
--- a/tests/test_find_template.py
+++ b/tests/test_find_template.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.core.management import call_command
from django.test import TestCase
from io import StringIO
diff --git a/tests/test_json_field.py b/tests/test_json_field.py
index 8bcceb72b..2264aaf7f 100644
--- a/tests/test_json_field.py
+++ b/tests/test_json_field.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import TestCase
from django_extensions.db.fields.json import dumps, loads, JSONField, JSONDict, JSONList
from .testapp.models import JSONFieldTestModel
diff --git a/tests/test_logging_filters.py b/tests/test_logging_filters.py
index 000ada2f4..b91e977d5 100644
--- a/tests/test_logging_filters.py
+++ b/tests/test_logging_filters.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import time
from django.test import TestCase
diff --git a/tests/test_management_command.py b/tests/test_management_command.py
index 4ed2975be..ea205ed72 100644
--- a/tests/test_management_command.py
+++ b/tests/test_management_command.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
from unittest import mock
import logging
diff --git a/tests/test_models.py b/tests/test_models.py
index 1c6c84841..7e7bebec6 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import TestCase
from django_extensions.db.models import ActivatorModel
diff --git a/tests/test_modificationdatetime_fields.py b/tests/test_modificationdatetime_fields.py
index 281670edc..1fe0d8359 100644
--- a/tests/test_modificationdatetime_fields.py
+++ b/tests/test_modificationdatetime_fields.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from datetime import datetime
from django.test import TestCase
diff --git a/tests/test_module_in_project_dir.py b/tests/test_module_in_project_dir.py
index 6e5d8bc9b..8fe53a1ec 100644
--- a/tests/test_module_in_project_dir.py
+++ b/tests/test_module_in_project_dir.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from tests.testapp.classes_to_include import BaseIncludedClass
diff --git a/tests/test_parse_mysql_cnf.py b/tests/test_parse_mysql_cnf.py
index aefa25e37..7f8976b2c 100644
--- a/tests/test_parse_mysql_cnf.py
+++ b/tests/test_parse_mysql_cnf.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import shutil
from tempfile import mkdtemp
diff --git a/tests/test_randomchar_field.py b/tests/test_randomchar_field.py
index 8d66d06e2..62ed9bfe1 100644
--- a/tests/test_randomchar_field.py
+++ b/tests/test_randomchar_field.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import string
import pytest
diff --git a/tests/test_runscript.py b/tests/test_runscript.py
index c61aa3398..6e23168f5 100644
--- a/tests/test_runscript.py
+++ b/tests/test_runscript.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
import sys
import importlib
diff --git a/tests/test_runserver.py b/tests/test_runserver.py
index 24bb051f7..8250b2a70 100644
--- a/tests/test_runserver.py
+++ b/tests/test_runserver.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import pytest
from os.path import join
diff --git a/tests/test_shortuuid_field.py b/tests/test_shortuuid_field.py
index 290fcffb3..70e88c020 100644
--- a/tests/test_shortuuid_field.py
+++ b/tests/test_shortuuid_field.py
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
from django.test import TestCase
from .testapp.models import (
diff --git a/tests/test_sqldiff.py b/tests/test_sqldiff.py
index 708353604..64ba4c6e8 100644
--- a/tests/test_sqldiff.py
+++ b/tests/test_sqldiff.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from unittest import mock
import pytest
from io import StringIO
diff --git a/tests/test_template_rendering.py b/tests/test_template_rendering.py
index d5f14ef18..d0963ffff 100644
--- a/tests/test_template_rendering.py
+++ b/tests/test_template_rendering.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.test import TestCase
from django.template import Context, Template
diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py
index e696b42e9..64fd2f3da 100644
--- a/tests/test_templatetags.py
+++ b/tests/test_templatetags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.template import engines
from django.test import TestCase
diff --git a/tests/test_timestamped_model.py b/tests/test_timestamped_model.py
index 3e39b6f77..cbba93bfe 100644
--- a/tests/test_timestamped_model.py
+++ b/tests/test_timestamped_model.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import time
from django.test import TestCase
diff --git a/tests/test_validators.py b/tests/test_validators.py
index 87885374e..5eadb2b71 100644
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from unittest.mock import patch
from django.core.exceptions import ValidationError
diff --git a/tests/testapp/admin.py b/tests/testapp/admin.py
index 117a40b3a..d768fa152 100644
--- a/tests/testapp/admin.py
+++ b/tests/testapp/admin.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.contrib import admin
from . import models
diff --git a/tests/testapp/apps.py b/tests/testapp/apps.py
index bbd342be4..15626fb6b 100644
--- a/tests/testapp/apps.py
+++ b/tests/testapp/apps.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.apps import AppConfig
diff --git a/tests/testapp/classes_to_include.py b/tests/testapp/classes_to_include.py
index 5d494fd83..589a7b424 100644
--- a/tests/testapp/classes_to_include.py
+++ b/tests/testapp/classes_to_include.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
class BaseIncludedClass:
pass
diff --git a/tests/testapp/derived_classes_for_testing/__init__.py b/tests/testapp/derived_classes_for_testing/__init__.py
index db038e56b..432ef5219 100644
--- a/tests/testapp/derived_classes_for_testing/__init__.py
+++ b/tests/testapp/derived_classes_for_testing/__init__.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from tests.testapp.classes_to_include import BaseIncludedClass
diff --git a/tests/testapp/derived_classes_for_testing/test_module.py b/tests/testapp/derived_classes_for_testing/test_module.py
index c4127235f..fa79f5fa3 100644
--- a/tests/testapp/derived_classes_for_testing/test_module.py
+++ b/tests/testapp/derived_classes_for_testing/test_module.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from tests.testapp.classes_to_include import IncludedMixin
diff --git a/tests/testapp/factories.py b/tests/testapp/factories.py
index fff2e31eb..7aa98f20b 100644
--- a/tests/testapp/factories.py
+++ b/tests/testapp/factories.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import factory
from factory.django import DjangoModelFactory
diff --git a/tests/testapp/fields.py b/tests/testapp/fields.py
index 4727ce367..021ab8792 100644
--- a/tests/testapp/fields.py
+++ b/tests/testapp/fields.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.db import models
from django_extensions.db.fields import UniqueFieldMixin
diff --git a/tests/testapp/file_with_utf8_notes.py b/tests/testapp/file_with_utf8_notes.py
index 4722afe5c..200ab8157 100644
--- a/tests/testapp/file_with_utf8_notes.py
+++ b/tests/testapp/file_with_utf8_notes.py
@@ -1,3 +1 @@
-# -*- coding: utf-8 -*-
-
# TODO: Russian text followed: Это техт на кириллице
diff --git a/tests/testapp/file_without_utf8_notes.py b/tests/testapp/file_without_utf8_notes.py
index 2fb1a381c..4be8892bd 100644
--- a/tests/testapp/file_without_utf8_notes.py
+++ b/tests/testapp/file_without_utf8_notes.py
@@ -1,3 +1 @@
-# -*- coding: utf-8 -*-
-
# TODO: this is a test todo
diff --git a/tests/testapp/jobs/daily/test_daily_job.py b/tests/testapp/jobs/daily/test_daily_job.py
index 6294d3ef1..c185e795d 100644
--- a/tests/testapp/jobs/daily/test_daily_job.py
+++ b/tests/testapp/jobs/daily/test_daily_job.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.jobs import DailyJob
from unittest import mock
diff --git a/tests/testapp/jobs/hourly/test_hourly_job.py b/tests/testapp/jobs/hourly/test_hourly_job.py
index ac3198ce8..4c4f96677 100644
--- a/tests/testapp/jobs/hourly/test_hourly_job.py
+++ b/tests/testapp/jobs/hourly/test_hourly_job.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.jobs import HourlyJob
from unittest import mock
diff --git a/tests/testapp/jobs/monthly/test_monthly_job.py b/tests/testapp/jobs/monthly/test_monthly_job.py
index 6adaec08b..e58f6346c 100644
--- a/tests/testapp/jobs/monthly/test_monthly_job.py
+++ b/tests/testapp/jobs/monthly/test_monthly_job.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.jobs import MonthlyJob
from unittest import mock
diff --git a/tests/testapp/jobs/sample.py b/tests/testapp/jobs/sample.py
index c868ad189..b1ae62cb8 100644
--- a/tests/testapp/jobs/sample.py
+++ b/tests/testapp/jobs/sample.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.jobs import BaseJob
diff --git a/tests/testapp/jobs/sample_job.py b/tests/testapp/jobs/sample_job.py
index 722522916..b63acb2a9 100644
--- a/tests/testapp/jobs/sample_job.py
+++ b/tests/testapp/jobs/sample_job.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.jobs import BaseJob
diff --git a/tests/testapp/jobs/weekly/test_weekly_job.py b/tests/testapp/jobs/weekly/test_weekly_job.py
index 6951a5bee..e66a504a6 100644
--- a/tests/testapp/jobs/weekly/test_weekly_job.py
+++ b/tests/testapp/jobs/weekly/test_weekly_job.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.jobs import WeeklyJob
from unittest import mock
diff --git a/tests/testapp/jobs/yearly/test_yearly_job.py b/tests/testapp/jobs/yearly/test_yearly_job.py
index d5dfb86aa..9897924c1 100644
--- a/tests/testapp/jobs/yearly/test_yearly_job.py
+++ b/tests/testapp/jobs/yearly/test_yearly_job.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.jobs import YearlyJob
from unittest import mock
diff --git a/tests/testapp/management/commands/test_email_notification_command.py b/tests/testapp/management/commands/test_email_notification_command.py
index 0b3a92bad..6b1617af9 100644
--- a/tests/testapp/management/commands/test_email_notification_command.py
+++ b/tests/testapp/management/commands/test_email_notification_command.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django_extensions.management.email_notifications import EmailNotificationCommand
diff --git a/tests/testapp/models.py b/tests/testapp/models.py
index ca317405e..278402f51 100644
--- a/tests/testapp/models.py
+++ b/tests/testapp/models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.db import models
from django.contrib.auth import get_user_model
from django.db.models import UniqueConstraint
diff --git a/tests/testapp/scripts/directory_checker_script.py b/tests/testapp/scripts/directory_checker_script.py
index 7c91f23d1..0380be894 100644
--- a/tests/testapp/scripts/directory_checker_script.py
+++ b/tests/testapp/scripts/directory_checker_script.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
diff --git a/tests/testapp/scripts/error_script.py b/tests/testapp/scripts/error_script.py
index 6dfd200bc..033ba598c 100644
--- a/tests/testapp/scripts/error_script.py
+++ b/tests/testapp/scripts/error_script.py
@@ -1,3 +1,2 @@
-# -*- coding: utf-8 -*-
def run():
raise Exception
diff --git a/tests/testapp/scripts/invalid_import_script.py b/tests/testapp/scripts/invalid_import_script.py
index 8800598ea..c76c09b35 100644
--- a/tests/testapp/scripts/invalid_import_script.py
+++ b/tests/testapp/scripts/invalid_import_script.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import invalidpackage # NOQA
diff --git a/tests/testapp/scripts/sample_script.py b/tests/testapp/scripts/sample_script.py
index c80e928e3..d04d42367 100644
--- a/tests/testapp/scripts/sample_script.py
+++ b/tests/testapp/scripts/sample_script.py
@@ -1,3 +1,2 @@
-# -*- coding: utf-8 -*-
def run():
pass
diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py
index 57e0eef08..c03c56140 100644
--- a/tests/testapp/settings.py
+++ b/tests/testapp/settings.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
SECRET_KEY = "dummy"
diff --git a/tests/testapp/templatetags/dummy_tags.py b/tests/testapp/templatetags/dummy_tags.py
index cfe5b670a..a94b2a213 100644
--- a/tests/testapp/templatetags/dummy_tags.py
+++ b/tests/testapp/templatetags/dummy_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django import template
register = template.Library()
diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py
index 3dcca6a26..f17ae7d02 100644
--- a/tests/testapp/urls.py
+++ b/tests/testapp/urls.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
URL Configuration
diff --git a/tests/testapp_with_appconfig/admin.py b/tests/testapp_with_appconfig/admin.py
index f05f6941a..4185d360e 100644
--- a/tests/testapp_with_appconfig/admin.py
+++ b/tests/testapp_with_appconfig/admin.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# from django.contrib import admin
# Register your models here.
diff --git a/tests/testapp_with_appconfig/apps.py b/tests/testapp_with_appconfig/apps.py
index d0493294c..d4fe181fb 100644
--- a/tests/testapp_with_appconfig/apps.py
+++ b/tests/testapp_with_appconfig/apps.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django.apps import AppConfig
diff --git a/tests/testapp_with_appconfig/models.py b/tests/testapp_with_appconfig/models.py
index 00f43d369..0b4331b36 100644
--- a/tests/testapp_with_appconfig/models.py
+++ b/tests/testapp_with_appconfig/models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# from django.db import models
# Create your models here.
diff --git a/tests/testapp_with_appconfig/templatetags/dummy_tags_appconfig.py b/tests/testapp_with_appconfig/templatetags/dummy_tags_appconfig.py
index 29732e5eb..206e620c4 100644
--- a/tests/testapp_with_appconfig/templatetags/dummy_tags_appconfig.py
+++ b/tests/testapp_with_appconfig/templatetags/dummy_tags_appconfig.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
from django import template
register = template.Library()
diff --git a/tests/testapp_with_appconfig/tests.py b/tests/testapp_with_appconfig/tests.py
index 5da68aead..a79ca8be5 100644
--- a/tests/testapp_with_appconfig/tests.py
+++ b/tests/testapp_with_appconfig/tests.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# from django.test import TestCase
# Create your tests here.
diff --git a/tests/testapp_with_appconfig/views.py b/tests/testapp_with_appconfig/views.py
index 4d80df8e5..fd0e04495 100644
--- a/tests/testapp_with_appconfig/views.py
+++ b/tests/testapp_with_appconfig/views.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# from django.shortcuts import render
# Create your views here.
diff --git a/tests/testapp_with_no_models_file/scripts/other_directory_checker_script.py b/tests/testapp_with_no_models_file/scripts/other_directory_checker_script.py
index 5c13ecdf0..b07b297ff 100644
--- a/tests/testapp_with_no_models_file/scripts/other_directory_checker_script.py
+++ b/tests/testapp_with_no_models_file/scripts/other_directory_checker_script.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
import os
diff --git a/tests/testapp_with_no_models_file/teslacar.py b/tests/testapp_with_no_models_file/teslacar.py
index 5662ac0f6..4484fed9f 100644
--- a/tests/testapp_with_no_models_file/teslacar.py
+++ b/tests/testapp_with_no_models_file/teslacar.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# An app without a models.py file for issue #936
from django.db import models
diff --git a/tox.ini b/tox.ini
index 90840c002..bd177ff9e 100644
--- a/tox.ini
+++ b/tox.ini
@@ -7,7 +7,6 @@
envlist =
ruff
precommit
- safety
mypy
{py39,py312,pypy}-dj42
{py311,py312,py313,pypy}-dj51
@@ -58,14 +57,6 @@ deps =
pre-commit
commands = pre-commit run -a
-[testenv:safety]
-deps =
- pip >= 21.1
- safety
-# https://github.com/pyupio/safety/issues/539
-# Ignore 70612 / CVE-2019-8341
-commands = safety --disable-optional-telemetry check --full-report --ignore 70612
-
[testenv:mypy]
allowlist_externals=
echo
| |