Name | django-upgrade JSON |
Version |
1.22.2
JSON |
| download |
home_page | None |
Summary | Automatically upgrade your Django project code. |
upload_time | 2024-12-02 15:11:00 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.9 |
license | None |
keywords |
django
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
==============
django-upgrade
==============
.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-upgrade/main.yml.svg?branch=main&style=for-the-badge
:target: https://github.com/adamchainz/django-upgrade/actions?workflow=CI
.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge
:target: https://github.com/adamchainz/django-upgrade/actions?workflow=CI
.. image:: https://img.shields.io/pypi/v/django-upgrade.svg?style=for-the-badge
:target: https://pypi.org/project/django-upgrade/
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
:target: https://github.com/psf/black
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
:target: https://github.com/pre-commit/pre-commit
:alt: pre-commit
Automatically upgrade your Django project code.
----
**Improve your code quality** with my book `Boost Your Django DX <https://adamchainz.gumroad.com/l/byddx>`__ which covers using pre-commit, django-upgrade, and many other tools.
I wrote django-upgrade whilst working on the book!
----
Installation
============
Use **pip**:
.. code-block:: sh
python -m pip install django-upgrade
Python 3.9 to 3.13 supported.
(Python 3.12+ is required to correctly apply fixes within f-strings.)
pre-commit hook
---------------
You can also install django-upgrade as a `pre-commit <https://pre-commit.com/>`__ hook.
Add the following to the ``repos`` section of your ``.pre-commit-config.yaml`` file (`docs <https://pre-commit.com/#plugins>`__), above any code formatters (such as Black):
.. code-block:: yaml
- repo: https://github.com/adamchainz/django-upgrade
rev: "" # replace with latest tag on GitHub
hooks:
- id: django-upgrade
args: [--target-version, "5.0"] # Replace with Django version
Then, upgrade your entire project:
.. code-block:: sh
pre-commit run django-upgrade --all-files
Commit any changes.
In the process, your other hooks will run, potentially reformatting django-upgrade’s changes to match your project’s code style.
Keep the hook installed in order to upgrade all code added to your project.
pre-commit’s ``autoupdate`` command will also let you take advantage of future django-upgrade features.
Usage
=====
``django-upgrade`` is a commandline tool that rewrites files in place.
Pass your Django version as ``<major>.<minor>`` to the ``--target-version`` flag and a list of files.
django-upgrade’s fixers will rewrite your code to avoid ``DeprecationWarning``\s and use some new features.
For example:
.. code-block:: sh
django-upgrade --target-version 5.0 example/core/models.py example/settings.py
``django-upgrade`` focuses on upgrading your code and not on making it look nice.
Run django-upgrade before formatters like `Black <https://black.readthedocs.io/en/stable/>`__.
Some of django-upgrade’s fixers make changes to models that need migrations:
* ``index_together``
* ``null_boolean_field``
Add a `test for pending migrations <https://adamj.eu/tech/2024/06/23/django-test-pending-migrations/>`__ to ensure that you do not miss these.
``django-upgrade`` does not have any ability to recurse through directories.
Use the pre-commit integration, globbing, or another technique for applying to many files.
Some fixers depend on the names of containing directories to activate, so ensure you run django-upgrade with paths relative to the root of your project.
For example, |with git ls-files pipe xargs|_:
.. |with git ls-files pipe xargs| replace:: with ``git ls-files | xargs``
.. _with git ls-files pipe xargs: https://adamj.eu/tech/2022/03/09/how-to-run-a-command-on-many-files-in-your-git-repository/
.. code-block:: sh
git ls-files -z -- '*.py' | xargs -0 django-upgrade --target-version 5.0
…or PowerShell’s |ForEach-Object|__:
.. |ForEach-Object| replace:: ``ForEach-Object``
__ https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object
.. code-block:: powershell
git ls-files -- '*.py' | %{django-upgrade --target-version 5.0 $_}
The full list of fixers is documented below.
Options
=======
``--target-version``
--------------------
The version of Django to target, in the format ``<major>.<minor>``.
django-upgrade enables all of its fixers for versions up to and including the target version.
This option defaults to 2.2, the oldest supported version when this project was created.
See the list of available versions with ``django-upgrade --help``.
``--exit-zero-even-if-changed``
-------------------------------
Exit with a zero return code even if files have changed.
By default, django-upgrade uses the failure return code 1 if it changes any files, which may stop scripts or CI pipelines.
``--only <fixer_name>``
-----------------------
Run only the named fixer (names are documented below).
The fixer must still be enabled by ``--target-version``.
Select multiple fixers with multiple ``--only`` options.
For example:
.. code-block:: sh
django-upgrade --target-version 5.0 --only admin_allow_tags --only admin_decorators example/core/admin.py
``--skip <fixer_name>``
-----------------------
Skip the named fixer.
Skip multiple fixers with multiple ``--skip`` options.
For example:
.. code-block:: sh
django-upgrade --target-version 5.0 --skip admin_register example/core/admin.py
``--list-fixers``
-----------------
List all available fixers’ names and then exit.
All other options are ignored when listing fixers.
For example:
.. code-block:: sh
django-upgrade --list-fixers
History
=======
`django-codemod <https://django-codemod.readthedocs.io/en/latest/>`__ is a pre-existing, more complete Django auto-upgrade tool, written by Bruno Alla.
Unfortunately its underlying library `LibCST <https://pypi.org/project/libcst/>`__ is particularly slow, making it annoying to run django-codemod on every commit and in CI.
django-upgrade is an experiment in reimplementing such a tool using the same techniques as the fantastic `pyupgrade <https://github.com/asottile/pyupgrade>`__.
The tool leans on the standard library’s `ast <https://docs.python.org/3/library/ast.html>`__ and `tokenize <https://docs.python.org/3/library/tokenize.html>`__ modules, the latter via the `tokenize-rt wrapper <https://github.com/asottile/tokenize-rt>`__.
This means it will always be fast and support the latest versions of Python.
For a quick benchmark: running django-codemod against a medium Django repository with 153k lines of Python takes 133 seconds.
pyupgrade and django-upgrade both take less than 0.5 seconds.
Fixers
======
All Versions
------------
The below fixers run regardless of the target version.
Versioned blocks
~~~~~~~~~~~~~~~~
**Name:** ``versioned_branches``
Removes outdated comparisons and blocks from ``if`` statements comparing to ``django.VERSION``.
Supports comparisons of the form:
.. code-block:: text
if django.VERSION <comparator> (<X>, <Y>):
...
Where ``<comparator>`` is one of ``<``, ``<=`` , ``>``, or ``>=``, and ``<X>`` and ``<Y>`` are integer literals.
A single ``else`` block may be present, but ``elif`` is not supported.
.. code-block:: diff
-if django.VERSION < (4, 1):
- class RenameIndex:
- ...
-if django.VERSION >= (4, 1):
- constraint.validate()
-else:
- custom_validation(constraint)
+constraint.validate()
See also `pyupgrade’s similar feature <https://github.com/asottile/pyupgrade/#python2-and-old-python3x-blocks>`__ that removes outdated code from checks on the Python version.
Versioned test skip decorators
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``versioned_test_skip_decorators``
Removes outdated test skip decorators that compare to ``django.VERSION``.
Like the above, it requires comparisons of the form:
.. code-block:: text
django.VERSION <comparator> (<X>, <Y>)
Supports these test skip decorators:
* |unittest.skipIf|__
.. |unittest.skipIf| replace:: ``@unittest.skipIf``
__ https://docs.python.org/3/library/unittest.html#unittest.skipIf
* |unittest.skipUnless|__
.. |unittest.skipUnless| replace:: ``@unittest.skipUnless``
__ https://docs.python.org/3/library/unittest.html#unittest.skipUnless
* |pytest.mark.skipif|__
.. |pytest.mark.skipif| replace:: ``@pytest.mark.skipif``
__ https://docs.pytest.org/en/stable/how-to/skipping.html#id1
For example:
.. code-block:: diff
import unittest
import django
import pytest
from django.test import TestCase
class ExampleTests(TestCase):
- @unittest.skipIf(django.VERSION < (5, 1), "Django 5.1+")
def test_one(self):
...
- @unittest.skipUnless(django.VERSION >= (5, 1), "Django 5.1+")
def test_two(self):
...
- @pytest.mark.skipif(django.VERSION < (5, 1), reason="Django 5.1+")
def test_three(self):
...
-@unittest.skipIf(django.VERSION < (5, 1), "Django 5.1+")
class Example2Tests(TestCase):
...
-@pytest.mark.skipif(django.VERSION < (5, 1), reason="Django 5.1+")
class Example3Tests(TestCase):
...
Django 5.1
----------
`Release Notes <https://docs.djangoproject.com/en/5.1/releases/5.1/>`__
``CheckConstraint`` ``condition`` argument
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``check_constraint_condition``
Rewrites calls to ``CheckConstraint`` and built-in subclasses from the old ``check`` argument to the new name ``condition``.
.. code-block:: diff
-CheckConstraint(check=Q(amount__gte=0))
+CheckConstraint(condition=Q(amount__gte=0))
Django 5.0
----------
`Release Notes <https://docs.djangoproject.com/en/5.0/releases/5.0/>`__
``format_html()`` calls
~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``format_html``
Rewrites ``format_html()`` calls without ``args`` or ``kwargs`` but using ``str.format()``.
Such calls are most likely incorrectly applying formatting without escaping, making them vulnerable to HTML injection.
Such use cases are why calling ``format_html()`` without any arguments or keyword arguments was deprecated in `Ticket #34609 <https://code.djangoproject.com/ticket/34609>`__.
.. code-block:: diff
from django.utils.html import format_html
-format_html("<marquee>{}</marquee>".format(message))
+format_html("<marquee>{}</marquee>", message)
-format_html("<marquee>{name}</marquee>".format(name=name))
+format_html("<marquee>{name}</marquee>", name=name)
Django 4.2
----------
`Release Notes <https://docs.djangoproject.com/en/4.2/releases/4.2/>`__
``STORAGES`` setting
~~~~~~~~~~~~~~~~~~~~
**Name:** ``settings_storages``
Combines deprecated settings ``DEFAULT_FILE_STORAGE`` and ``STATICFILES_STORAGE`` into the new ``STORAGES`` setting, within settings files.
Only applies if all old settings are defined as strings, at module level, and a ``STORAGES`` setting hasn’t been defined.
Settings files are heuristically detected as modules with the whole word “settings” somewhere in their path.
For example ``myproject/settings.py`` or ``myproject/settings/production.py``.
.. code-block:: diff
-DEFAULT_FILE_STORAGE = "example.storages.ExtendedFileSystemStorage"
-STATICFILES_STORAGE = "example.storages.ExtendedS3Storage"
+STORAGES = {
+ "default": {
+ "BACKEND": "example.storages.ExtendedFileSystemStorage",
+ },
+ "staticfiles": {
+ "BACKEND": "example.storages.ExtendedS3Storage",
+ },
+}
If the module has a ``from ... import *`` with a module path mentioning “settings”, django-upgrade makes an educated guess that a base ``STORAGES`` setting is imported from there.
It then uses ``**`` to extend that with any values in the current module:
.. code-block:: diff
from example.settings.base import *
-DEFAULT_FILE_STORAGE = "example.storages.S3Storage"
+STORAGES = {
+ **STORAGES,
+ "default": {
+ "BACKEND": "example.storages.S3Storage",
+ },
+}
Test client HTTP headers
~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``test_http_headers``
Transforms HTTP headers from the old WSGI kwarg format to use the new ``headers`` dictionary, for:
* ``Client`` method like ``self.client.get()``
* ``Client`` instantiation
* ``RequestFactory`` instantiation
.. code-block:: diff
-response = self.client.get("/", HTTP_ACCEPT="text/plain")
+response = self.client.get("/", headers={"accept": "text/plain"})
from django.test import Client
-Client(HTTP_ACCEPT_LANGUAGE="fr-fr")
+Client(headers={"accept-language": "fr-fr"})
from django.test import RequestFactory
-RequestFactory(HTTP_USER_AGENT="curl")
+RequestFactory(headers={"user-agent": "curl"})
``index_together`` deprecation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``index_together``
Rewrites ``index_together`` declarations into ``indexes`` declarations in model ``Meta`` classes.
.. code-block:: diff
from django.db import models
class Duck(models.Model):
class Meta:
- index_together = [["bill", "tail"]]
+ indexes = [models.Index(fields=["bill", "tail"])]
``assertFormsetError`` and ``assertQuerysetEqual``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``assert_set_methods``
Rewrites calls to these test case methods from the old names to the new ones with capitalized “Set”.
.. code-block:: diff
-self.assertFormsetError(response.context["form"], "username", ["Too long"])
+self.assertFormSetError(response.context["form"], "username", ["Too long"])
-self.assertQuerysetEqual(authors, ["Brad Dayley"], lambda a: a.name)
+self.assertQuerySetEqual(authors, ["Brad Dayley"], lambda a: a.name)
Django 4.1
----------
`Release Notes <https://docs.djangoproject.com/en/4.1/releases/4.1/>`__
``django.utils.timezone.utc`` deprecations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``utils_timezone``
Rewrites imports of ``django.utils.timezone.utc`` to use ``datetime.timezone.utc``.
Requires an existing import of the ``datetime`` module.
.. code-block:: diff
import datetime
-from django.utils.timezone import utc
-calculate_some_datetime(utc)
+calculate_some_datetime(datetime.timezone.utc)
.. code-block:: diff
import datetime as dt
from django.utils import timezone
-do_a_thing(timezone.utc)
+do_a_thing(dt.timezone.utc)
``assertFormError()`` and ``assertFormsetError()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``assert_form_error``
Rewrites calls to these test case methods from the old signatures to the new ones.
.. code-block:: diff
-self.assertFormError(response, "form", "username", ["Too long"])
+self.assertFormError(response.context["form"], "username", ["Too long"])
-self.assertFormError(response, "form", "username", None)
+self.assertFormError(response.context["form"], "username", [])
-self.assertFormsetError(response, "formset", 0, "username", ["Too long"])
+self.assertFormsetError(response.context["formset"], 0, "username", ["Too long"])
-self.assertFormsetError(response, "formset", 0, "username", None)
+self.assertFormsetError(response.context["formset"], 0, "username", [])
Django 4.0
----------
`Release Notes <https://docs.djangoproject.com/en/4.0/releases/4.0/>`__
``USE_L10N``
~~~~~~~~~~~~
**Name:** ``use_l10n``
Removes the deprecated ``USE_L10N`` setting if set to its default value of ``True``.
Settings files are heuristically detected as modules with the whole word “settings” somewhere in their path.
For example ``myproject/settings.py`` or ``myproject/settings/production.py``.
.. code-block:: diff
-USE_L10N = True
``lookup_needs_distinct``
~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``admin_lookup_needs_distinct``
Renames the undocumented ``django.contrib.admin.utils.lookup_needs_distinct`` to ``lookup_spawns_duplicates``:
.. code-block:: diff
-from django.contrib.admin.utils import lookup_needs_distinct
+from django.contrib.admin.utils import lookup_spawns_duplicates
-if lookup_needs_distinct(self.opts, search_spec):
+if lookup_spawns_duplicates(self.opts, search_spec):
...
Compatibility imports
~~~~~~~~~~~~~~~~~~~~~
Rewrites some compatibility imports:
* ``django.utils.translation.template.TRANSLATOR_COMMENT_MARK`` in ``django.template.base``
.. code-block:: diff
-from django.template.base import TRANSLATOR_COMMENT_MARK
+from django.utils.translation.template import TRANSLATOR_COMMENT_MARK
Django 3.2
----------
`Release Notes <https://docs.djangoproject.com/en/3.2/releases/3.2/>`__
``@admin.action()``
~~~~~~~~~~~~~~~~~~~
**Name:** ``admin_decorators``
Rewrites functions that have admin action attributes assigned to them to use the new |@admin.action decorator|_.
This only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.
.. |@admin.action decorator| replace:: ``@admin.action()`` decorator
.. _@admin.action decorator: https://docs.djangoproject.com/en/stable/ref/contrib/admin/actions/#django.contrib.admin.action
.. code-block:: diff
from django.contrib import admin
# Module-level actions:
+@admin.action(
+ description="Publish articles",
+)
def make_published(modeladmin, request, queryset):
...
-make_published.short_description = "Publish articles"
# …and within classes:
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
+ @admin.action(
+ description="Unpublish articles",
+ permissions=("unpublish",),
+ )
def make_unpublished(self, request, queryset):
...
- make_unpublished.allowed_permissions = ("unpublish",)
- make_unpublished.short_description = "Unpublish articles"
``@admin.display()``
~~~~~~~~~~~~~~~~~~~~
**Name:** ``admin_decorators``
Rewrites functions that have admin display attributes assigned to them to use the new |@admin.display decorator|_.
This only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.
.. |@admin.display decorator| replace:: ``@admin.display()`` decorator
.. _@admin.display decorator: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.display
.. code-block:: diff
from django.contrib import admin
# Module-level display functions:
+@admin.display(
+ description="NAME",
+)
def upper_case_name(obj):
...
-upper_case_name.short_description = "NAME"
# …and within classes:
@admin.register(Book)
class BookAdmin(admin.ModelAdmin):
+ @admin.display(
+ description='Is Published?',
+ boolean=True,
+ ordering='-publish_date',
+ )
def is_published(self, obj):
...
- is_published.boolean = True
- is_published.admin_order_field = '-publish_date'
- is_published.short_description = 'Is Published?'
``BaseCommand.requires_system_checks``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``management_commands``
Rewrites the ``requires_system_checks`` attributes of management command classes from bools to ``"__all__"`` or ``[]`` as appropriate.
This only applies in command files, which are heuristically detected as files with ``management/commands`` somewhere in their path.
.. code-block:: diff
from django.core.management.base import BaseCommand
class Command(BaseCommand):
- requires_system_checks = True
+ requires_system_checks = "__all__"
class SecondCommand(BaseCommand):
- requires_system_checks = False
+ requires_system_checks = []
``EmailValidator``
~~~~~~~~~~~~~~~~~~
**Name:** ``email_validator``
Rewrites the ``whitelist`` keyword argument to its new name ``allowlist``.
.. code-block:: diff
from django.core.validators import EmailValidator
-EmailValidator(whitelist=["example.com"])
+EmailValidator(allowlist=["example.com"])
``default_app_config``
~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``default_app_config``
Removes module-level ``default_app_config`` assignments from ``__init__.py`` files:
.. code-block:: diff
-default_app_config = 'my_app.apps.AppConfig'
Django 3.1
----------
`Release Notes <https://docs.djangoproject.com/en/3.1/releases/3.1/>`__
``JSONField``
~~~~~~~~~~~~~
**Name:** ``compatibility_imports``
Rewrites imports of ``JSONField`` and related transform classes from those in ``django.contrib.postgres`` to the new all-database versions.
Ignores usage in migration files, since Django kept the old class around to support old migrations.
You will need to make migrations after this fix makes changes to models.
.. code-block:: diff
-from django.contrib.postgres.fields import JSONField
+from django.db.models import JSONField
``PASSWORD_RESET_TIMEOUT_DAYS``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``password_reset_timeout_days``
Rewrites the setting ``PASSWORD_RESET_TIMEOUT_DAYS`` to ``PASSWORD_RESET_TIMEOUT``, adding the multiplication by the number of seconds in a day.
Settings files are heuristically detected as modules with the whole word “settings” somewhere in their path.
For example ``myproject/settings.py`` or ``myproject/settings/production.py``.
.. code-block:: diff
-PASSWORD_RESET_TIMEOUT_DAYS = 4
+PASSWORD_RESET_TIMEOUT = 60 * 60 * 24 * 4
``Signal``
~~~~~~~~~~
**Name:** ``signal_providing_args``
Removes the deprecated documentation-only ``providing_args`` argument.
.. code-block:: diff
from django.dispatch import Signal
-my_cool_signal = Signal(providing_args=["documented", "arg"])
+my_cool_signal = Signal()
``get_random_string``
~~~~~~~~~~~~~~~~~~~~~
**Name:** ``crypto_get_random_string``
Injects the now-required ``length`` argument, with its previous default ``12``.
.. code-block:: diff
from django.utils.crypto import get_random_string
-key = get_random_string(allowed_chars="01234567899abcdef")
+key = get_random_string(length=12, allowed_chars="01234567899abcdef")
``NullBooleanField``
~~~~~~~~~~~~~~~~~~~~
**Name:** ``null_boolean_field``
Transforms the ``NullBooleanField()`` model field to ``BooleanField(null=True)``.
Applied only in model files, not migration files, since Django kept the old class around to support old migrations.
You will need to make migrations after this fix makes changes to models.
.. code-block:: diff
-from django.db.models import Model, NullBooleanField
+from django.db.models import Model, BooleanField
class Book(Model):
- valuable = NullBooleanField("Valuable")
+ valuable = BooleanField("Valuable", null=True)
``ModelMultipleChoiceField``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``forms_model_multiple_choice_field``
Replace ``list`` error message key with ``list_invalid`` on forms ``ModelMultipleChoiceField``.
.. code-block:: diff
-forms.ModelMultipleChoiceField(error_messages={"list": "Enter multiple values."})
+forms.ModelMultipleChoiceField(error_messages={"invalid_list": "Enter multiple values."})
Django 3.0
----------
`Release Notes <https://docs.djangoproject.com/en/3.0/releases/3.0/>`__
``django.utils.encoding`` aliases
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``utils_encoding``
Rewrites ``smart_text()`` to ``smart_str()``, and ``force_text()`` to ``force_str()``.
.. code-block:: diff
-from django.utils.encoding import force_text, smart_text
+from django.utils.encoding import force_str, smart_str
-force_text("yada")
-smart_text("yada")
+force_str("yada")
+smart_str("yada")
``django.utils.http`` deprecations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``utils_http``:
Rewrites the ``urlquote()``, ``urlquote_plus()``, ``urlunquote()``, and ``urlunquote_plus()`` functions to the ``urllib.parse`` versions.
Also rewrites the internal function ``is_safe_url()`` to ``url_has_allowed_host_and_scheme()``.
.. code-block:: diff
-from django.utils.http import urlquote
+from urllib.parse import quote
-escaped_query_string = urlquote(query_string)
+escaped_query_string = quote(query_string)
``django.utils.text`` deprecation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``utils_text``
Rewrites ``unescape_entities()`` with the standard library ``html.escape()``.
.. code-block:: diff
-from django.utils.text import unescape_entities
+import html
-unescape_entities("some input string")
+html.escape("some input string")
``django.utils.translation`` deprecations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``utils_translation``
Rewrites the ``ugettext()``, ``ugettext_lazy()``, ``ugettext_noop()``, ``ungettext()``, and ``ungettext_lazy()`` functions to their non-u-prefixed versions.
.. code-block:: diff
-from django.utils.translation import ugettext as _, ungettext
+from django.utils.translation import gettext as _, ngettext
-ungettext("octopus", "octopodes", n)
+ngettext("octopus", "octopodes", n)
Django 2.2
----------
`Release Notes <https://docs.djangoproject.com/en/2.2/releases/2.2/>`__
``HttpRequest.headers``
~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``request_headers``
Rewrites use of ``request.META`` to read HTTP headers to instead use |request.headers|_.
Header lookups are done in lowercase per `the HTTP/2 specification <https://httpwg.org/specs/rfc9113.html#HttpHeaders>`__.
.. |request.headers| replace:: ``request.headers``
.. _request.headers: https://docs.djangoproject.com/en/stable/ref/request-response/#django.http.HttpRequest.headers
.. code-block:: diff
-request.META['HTTP_ACCEPT_ENCODING']
+request.headers['accept-encoding']
-self.request.META.get('HTTP_SERVER', '')
+self.request.headers.get('server', '')
-request.META.get('CONTENT_LENGTH')
+request.headers.get('content-length')
-"HTTP_SERVER" in request.META
+"server" in request.headers
``QuerySetPaginator``
~~~~~~~~~~~~~~~~~~~~~
**Name:** ``queryset_paginator``
Rewrites deprecated alias ``django.core.paginator.QuerySetPaginator`` to ``Paginator``.
.. code-block:: diff
-from django.core.paginator import QuerySetPaginator
+from django.core.paginator import Paginator
-QuerySetPaginator(...)
+Paginator(...)
``FixedOffset``
~~~~~~~~~~~~~~~
**Name:** ``timezone_fixedoffset``
Rewrites deprecated class ``FixedOffset(x, y))`` to ``timezone(timedelta(minutes=x), y)``
Known limitation: this fixer will leave code broken with an ``ImportError`` if ``FixedOffset`` is called with only ``*args`` or ``**kwargs``.
.. code-block:: diff
-from django.utils.timezone import FixedOffset
-FixedOffset(120, "Super time")
+from datetime import timedelta, timezone
+timezone(timedelta(minutes=120), "Super time")
``FloatRangeField``
~~~~~~~~~~~~~~~~~~~
**Name:** ``postgres_float_range_field``
Rewrites model and form fields using ``FloatRangeField`` to ``DecimalRangeField``, from the relevant ``django.contrib.postgres`` modules.
.. code-block:: diff
from django.db.models import Model
-from django.contrib.postgres.fields import FloatRangeField
+from django.contrib.postgres.fields import DecimalRangeField
class MyModel(Model):
- my_field = FloatRangeField("My range of numbers")
+ my_field = DecimalRangeField("My range of numbers")
``TestCase`` class database declarations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``testcase_databases``
Rewrites the ``allow_database_queries`` and ``multi_db`` attributes of Django’s ``TestCase`` classes to the new ``databases`` attribute.
This only applies in test files, which are heuristically detected as files with either “test” or “tests” somewhere in their path.
Note that this will only rewrite to ``databases = []`` or ``databases = "__all__"``.
With multiple databases you can save some test time by limiting test cases to the databases they require (which is why Django made the change).
.. code-block:: diff
from django.test import SimpleTestCase
class MyTests(SimpleTestCase):
- allow_database_queries = True
+ databases = "__all__"
def test_something(self):
self.assertEqual(2 * 2, 4)
Django 2.1
----------
`Release Notes <https://docs.djangoproject.com/en/2.1/releases/2.1/>`__
No fixers yet.
Django 2.0
----------
`Release Notes <https://docs.djangoproject.com/en/2.0/releases/2.0/>`__
URL’s
~~~~~
**Name:** ``django_urls``
Rewrites imports of ``include()`` and ``url()`` from ``django.conf.urls`` to ``django.urls``.
``url()`` calls using compatible regexes are rewritten to the |new path() syntax|_, otherwise they are converted to call ``re_path()``.
.. |new path() syntax| replace:: new ``path()`` syntax
.. _new path() syntax: https://docs.djangoproject.com/en/2.0/releases/2.0/#simplified-url-routing-syntax
.. code-block:: diff
-from django.conf.urls import include, url
+from django.urls import include, path, re_path
urlpatterns = [
- url(r'^$', views.index, name='index'),
+ path('', views.index, name='index'),
- url(r'^about/$', views.about, name='about'),
+ path('about/', views.about, name='about'),
- url(r'^post/(?P<slug>[-a-zA-Z0-9_]+)/$', views.post, name='post'),
+ path('post/<slug:slug>/', views.post, name='post'),
- url(r'^weblog', include('blog.urls')),
+ re_path(r'^weblog', include('blog.urls')),
]
Existing ``re_path()`` calls are also rewritten to the ``path()`` syntax when eligible.
.. code-block:: diff
-from django.urls import include, re_path
+from django.urls import include, path, re_path
urlpatterns = [
- re_path(r'^about/$', views.about, name='about'),
+ path('about/', views.about, name='about'),
re_path(r'^post/(?P<slug>[\w-]+)/$', views.post, name='post'),
]
The compatible regexes that will be converted to use `path converters <https://docs.djangoproject.com/en/stable/topics/http/urls/#path-converters>`__ are the following:
* ``[^/]+`` → ``str``
* ``[0-9]+`` → ``int``
* ``[-a-zA-Z0-9_]+`` → ``slug``
* ``[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`` → ``uuid``
* ``.+`` → ``path``
These are taken from the path converter classes.
For some cases, this change alters the type of the arguments passed to the view, from ``str`` to the converted type (e.g. ``int``).
This is not guaranteed backwards compatible: there is a chance that the view expects a string, rather than the converted type.
But, pragmatically, it seems 99.9% of views do not require strings, and instead work with either strings or the converted type.
Thus, you should test affected paths after this fixer makes any changes.
Note that ``[\w-]`` is sometimes used for slugs, but is not converted because it might be incompatible.
That pattern matches all Unicode word characters, such as “α”, unlike Django's ``slug`` converter, which only matches Latin characters.
``lru_cache``
~~~~~~~~~~~~~
**Name:** ``compatibility_imports``
Rewrites imports of ``lru_cache`` from ``django.utils.functional`` to use ``functools``.
.. code-block:: diff
-from django.utils.functional import lru_cache
+from functools import lru_cache
``ContextDecorator``
~~~~~~~~~~~~~~~~~~~~
Rewrites imports of ``ContextDecorator`` from ``django.utils.decorators`` to use ``contextlib``.
.. code-block:: diff
-from django.utils.decorators import ContextDecorator
+from contextlib import ContextDecorator
``<func>.allow_tags = True``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``admin_allow_tags``
Removes assignments of ``allow_tags`` attributes to ``True``.
This was an admin feature to allow display functions to return HTML without marking it as unsafe, deprecated in Django 1.9.
In practice, most display functions that return HTML already use |format_html()|_ or similar, so the attribute wasn’t necessary.
This only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.
.. |format_html()| replace:: ``format_html()``
.. _format_html(): https://docs.djangoproject.com/en/stable/ref/utils/#django.utils.html.format_html
.. code-block:: diff
from django.contrib import admin
def upper_case_name(obj):
...
-upper_case_name.allow_tags = True
Django 1.11
-----------
`Release Notes <https://docs.djangoproject.com/en/1.11/releases/1.11/>`__
Compatibility imports
~~~~~~~~~~~~~~~~~~~~~
**Name:** ``compatibility_imports``
Rewrites some compatibility imports:
* ``django.core.exceptions.EmptyResultSet`` in ``django.db.models.query``, ``django.db.models.sql``, and ``django.db.models.sql.datastructures``
* ``django.core.exceptions.FieldDoesNotExist`` in ``django.db.models.fields``
Whilst mentioned in the `Django 3.1 release notes <https://docs.djangoproject.com/en/3.1/releases/3.1/#id1>`_, these have been possible since Django 1.11.
.. code-block:: diff
-from django.db.models.query import EmptyResultSet
+from django.core.exceptions import EmptyResultSet
-from django.db.models.fields import FieldDoesNotExist
+from django.core.exceptions import FieldDoesNotExist
Django 1.10
-----------
`Release Notes <https://docs.djangoproject.com/en/1.10/releases/1.10/>`__
``request.user`` boolean attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``request_user_attributes``
Rewrites calls to ``request.user.is_authenticated()`` and ``request.user.is_anonymous()`` to remove the parentheses, per `the deprecation <https://docs.djangoproject.com/en/1.10/releases/1.10/#using-user-is-authenticated-and-user-is-anonymous-as-methods>`__.
.. code-block:: diff
-request.user.is_authenticated()
+request.user.is_authenticated
-self.request.user.is_anonymous()
+self.request.user.is_anonymous
Compatibility imports
~~~~~~~~~~~~~~~~~~~~~
Rewrites some compatibility imports:
* ``django.templatetags.static.static`` in ``django.contrib.staticfiles.templatetags.staticfiles``
(Whilst mentioned in the `Django 2.1 release notes <https://docs.djangoproject.com/en/2.1/releases/2.1/#features-deprecated-in-2-1>`_, this has been possible since Django 1.10.)
* ``django.urls.*`` in ``django.core.urlresolvers.*``
.. code-block:: diff
-from django.contrib.staticfiles.templatetags.staticfiles import static
+from django.templatetags.static import static
-from django.core.urlresolvers import reverse
+from django.urls import reverse
-from django.core.urlresolvers import resolve
+from django.urls import resolve
Django 1.9
-----------
`Release Notes <https://docs.djangoproject.com/en/stable/releases/1.9/>`__
``on_delete`` argument
~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``on_delete``
Add ``on_delete=models.CASCADE`` to ``ForeignKey`` and ``OneToOneField``:
.. code-block:: diff
from django.db import models
-models.ForeignKey("auth.User")
+models.ForeignKey("auth.User", on_delete=models.CASCADE)
-models.OneToOneField("auth.User")
+models.OneToOneField("auth.User", on_delete=models.CASCADE)
This fixer also support from-imports:
.. code-block:: diff
-from django.db.models import ForeignKey
+from django.db.models import CASCADE, ForeignKey
-ForeignKey("auth.User")
+ForeignKey("auth.User", on_delete=CASCADE)
``DATABASES``
~~~~~~~~~~~~~
**Name:** ``settings_database_postgresql``
Update the ``DATABASES`` setting backend path ``django.db.backends.postgresql_psycopg2`` to use the renamed version ``django.db.backends.postgresql``.
Settings files are heuristically detected as modules with the whole word “settings” somewhere in their path.
For example ``myproject/settings.py`` or ``myproject/settings/production.py``.
.. code-block:: diff
DATABASES = {
"default": {
- "ENGINE": "django.db.backends.postgresql_psycopg2",
+ "ENGINE": "django.db.backends.postgresql",
"NAME": "mydatabase",
"USER": "mydatabaseuser",
"PASSWORD": "mypassword",
"HOST": "127.0.0.1",
"PORT": "5432",
}
}
Compatibility imports
~~~~~~~~~~~~~~~~~~~~~
**Name:** ``compatibility_imports``
Rewrites some compatibility imports:
* ``django.forms.utils.pretty_name`` in ``django.forms.forms``
* ``django.forms.boundfield.BoundField`` in ``django.forms.forms``
* ``django.forms.widgets.SelectDateWidget`` in ``django.forms.extras``
Whilst mentioned in the `Django 3.1 release notes <https://docs.djangoproject.com/en/3.1/releases/3.1/#id1>`_, these have been possible since Django 1.9.
.. code-block:: diff
-from django.forms.forms import pretty_name
+from django.forms.utils import pretty_name
Django 1.8
----------
`Release Notes <https://docs.djangoproject.com/en/stable/releases/1.8/>`__
No fixers yet.
Django 1.7
----------
`Release Notes <https://docs.djangoproject.com/en/stable/releases/1.7/>`__
Admin model registration
~~~~~~~~~~~~~~~~~~~~~~~~
**Name:** ``admin_register``
Rewrites ``admin.site.register()`` calls to the new |@admin.register|_ decorator syntax when eligible.
This only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.
.. |@admin.register| replace:: ``@admin.register()``
.. _@admin.register: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#the-register-decorator
.. code-block:: diff
from django.contrib import admin
+@admin.register(MyModel1, MyModel2)
class MyCustomAdmin(admin.ModelAdmin):
...
-admin.site.register(MyModel1, MyCustomAdmin)
-admin.site.register(MyModel2, MyCustomAdmin)
This also works with custom admin sites.
Such calls are detected heuristically based on three criteria:
1. The object whose ``register()`` method is called has a name ending with ``site``.
2. The registered class has a name ending with ``Admin``.
3. The filename has the word ``admin`` somewhere in its path.
.. code-block:: diff
from myapp.admin import custom_site
from django.contrib import admin
+@admin.register(MyModel)
+@admin.register(MyModel, site=custom_site)
class MyModelAdmin(admin.ModelAdmin):
pass
-custom_site.register(MyModel, MyModelAdmin)
-admin.site.register(MyModel, MyModelAdmin)
If a ``register()`` call is preceded by an ``unregister()`` call that includes the same model, it is ignored.
.. code-block:: python
from django.contrib import admin
class MyCustomAdmin(admin.ModelAdmin):
...
admin.site.unregister(MyModel1)
admin.site.register(MyModel1, MyCustomAdmin)
Compatibility imports
~~~~~~~~~~~~~~~~~~~~~
Rewrites some compatibility imports:
* ``django.contrib.admin.helpers.ACTION_CHECKBOX_NAME`` in ``django.contrib.admin``
* ``django.template.context.BaseContext``, ``django.template.context.Context``, ``django.template.context.ContextPopException`` and ``django.template.context.RequestContext`` in ``django.template.base``
.. code-block:: diff
-from django.contrib.admin import ACTION_CHECKBOX_NAME
+from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
-from django.template.base import Context
+from django.template.context import Context
Raw data
{
"_id": null,
"home_page": null,
"name": "django-upgrade",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "Django",
"author": null,
"author_email": "Adam Johnson <me@adamj.eu>",
"download_url": "https://files.pythonhosted.org/packages/f3/46/24e32ffdad682df8c273e2f434096246c89d265948a2fd214307f31eeeec/django_upgrade-1.22.2.tar.gz",
"platform": null,
"description": "==============\ndjango-upgrade\n==============\n\n.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-upgrade/main.yml.svg?branch=main&style=for-the-badge\n :target: https://github.com/adamchainz/django-upgrade/actions?workflow=CI\n\n.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge\n :target: https://github.com/adamchainz/django-upgrade/actions?workflow=CI\n\n.. image:: https://img.shields.io/pypi/v/django-upgrade.svg?style=for-the-badge\n :target: https://pypi.org/project/django-upgrade/\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge\n :target: https://github.com/psf/black\n\n.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge\n :target: https://github.com/pre-commit/pre-commit\n :alt: pre-commit\n\nAutomatically upgrade your Django project code.\n\n----\n\n**Improve your code quality** with my book `Boost Your Django DX <https://adamchainz.gumroad.com/l/byddx>`__ which covers using pre-commit, django-upgrade, and many other tools.\nI wrote django-upgrade whilst working on the book!\n\n----\n\nInstallation\n============\n\nUse **pip**:\n\n.. code-block:: sh\n\n python -m pip install django-upgrade\n\nPython 3.9 to 3.13 supported.\n\n(Python 3.12+ is required to correctly apply fixes within f-strings.)\n\npre-commit hook\n---------------\n\nYou can also install django-upgrade as a `pre-commit <https://pre-commit.com/>`__ hook.\nAdd the following to the ``repos`` section of your ``.pre-commit-config.yaml`` file (`docs <https://pre-commit.com/#plugins>`__), above any code formatters (such as Black):\n\n.. code-block:: yaml\n\n - repo: https://github.com/adamchainz/django-upgrade\n rev: \"\" # replace with latest tag on GitHub\n hooks:\n - id: django-upgrade\n args: [--target-version, \"5.0\"] # Replace with Django version\n\nThen, upgrade your entire project:\n\n.. code-block:: sh\n\n pre-commit run django-upgrade --all-files\n\nCommit any changes.\nIn the process, your other hooks will run, potentially reformatting django-upgrade\u2019s changes to match your project\u2019s code style.\n\nKeep the hook installed in order to upgrade all code added to your project.\npre-commit\u2019s ``autoupdate`` command will also let you take advantage of future django-upgrade features.\n\nUsage\n=====\n\n``django-upgrade`` is a commandline tool that rewrites files in place.\nPass your Django version as ``<major>.<minor>`` to the ``--target-version`` flag and a list of files.\ndjango-upgrade\u2019s fixers will rewrite your code to avoid ``DeprecationWarning``\\s and use some new features.\n\nFor example:\n\n.. code-block:: sh\n\n django-upgrade --target-version 5.0 example/core/models.py example/settings.py\n\n``django-upgrade`` focuses on upgrading your code and not on making it look nice.\nRun django-upgrade before formatters like `Black <https://black.readthedocs.io/en/stable/>`__.\n\nSome of django-upgrade\u2019s fixers make changes to models that need migrations:\n\n* ``index_together``\n* ``null_boolean_field``\n\nAdd a `test for pending migrations <https://adamj.eu/tech/2024/06/23/django-test-pending-migrations/>`__ to ensure that you do not miss these.\n\n``django-upgrade`` does not have any ability to recurse through directories.\nUse the pre-commit integration, globbing, or another technique for applying to many files.\nSome fixers depend on the names of containing directories to activate, so ensure you run django-upgrade with paths relative to the root of your project.\nFor example, |with git ls-files pipe xargs|_:\n\n.. |with git ls-files pipe xargs| replace:: with ``git ls-files | xargs``\n.. _with git ls-files pipe xargs: https://adamj.eu/tech/2022/03/09/how-to-run-a-command-on-many-files-in-your-git-repository/\n\n.. code-block:: sh\n\n git ls-files -z -- '*.py' | xargs -0 django-upgrade --target-version 5.0\n\n\u2026or PowerShell\u2019s |ForEach-Object|__:\n\n.. |ForEach-Object| replace:: ``ForEach-Object``\n__ https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/foreach-object\n\n.. code-block:: powershell\n\n git ls-files -- '*.py' | %{django-upgrade --target-version 5.0 $_}\n\nThe full list of fixers is documented below.\n\nOptions\n=======\n\n``--target-version``\n--------------------\n\nThe version of Django to target, in the format ``<major>.<minor>``.\ndjango-upgrade enables all of its fixers for versions up to and including the target version.\n\nThis option defaults to 2.2, the oldest supported version when this project was created.\nSee the list of available versions with ``django-upgrade --help``.\n\n``--exit-zero-even-if-changed``\n-------------------------------\n\nExit with a zero return code even if files have changed.\nBy default, django-upgrade uses the failure return code 1 if it changes any files, which may stop scripts or CI pipelines.\n\n``--only <fixer_name>``\n-----------------------\n\nRun only the named fixer (names are documented below).\nThe fixer must still be enabled by ``--target-version``.\nSelect multiple fixers with multiple ``--only`` options.\n\nFor example:\n\n.. code-block:: sh\n\n django-upgrade --target-version 5.0 --only admin_allow_tags --only admin_decorators example/core/admin.py\n\n``--skip <fixer_name>``\n-----------------------\n\nSkip the named fixer.\nSkip multiple fixers with multiple ``--skip`` options.\n\nFor example:\n\n.. code-block:: sh\n\n django-upgrade --target-version 5.0 --skip admin_register example/core/admin.py\n\n``--list-fixers``\n-----------------\n\nList all available fixers\u2019 names and then exit.\nAll other options are ignored when listing fixers.\n\nFor example:\n\n.. code-block:: sh\n\n django-upgrade --list-fixers\n\nHistory\n=======\n\n`django-codemod <https://django-codemod.readthedocs.io/en/latest/>`__ is a pre-existing, more complete Django auto-upgrade tool, written by Bruno Alla.\nUnfortunately its underlying library `LibCST <https://pypi.org/project/libcst/>`__ is particularly slow, making it annoying to run django-codemod on every commit and in CI.\n\ndjango-upgrade is an experiment in reimplementing such a tool using the same techniques as the fantastic `pyupgrade <https://github.com/asottile/pyupgrade>`__.\nThe tool leans on the standard library\u2019s `ast <https://docs.python.org/3/library/ast.html>`__ and `tokenize <https://docs.python.org/3/library/tokenize.html>`__ modules, the latter via the `tokenize-rt wrapper <https://github.com/asottile/tokenize-rt>`__.\nThis means it will always be fast and support the latest versions of Python.\n\nFor a quick benchmark: running django-codemod against a medium Django repository with 153k lines of Python takes 133 seconds.\npyupgrade and django-upgrade both take less than 0.5 seconds.\n\nFixers\n======\n\nAll Versions\n------------\n\nThe below fixers run regardless of the target version.\n\nVersioned blocks\n~~~~~~~~~~~~~~~~\n\n**Name:** ``versioned_branches``\n\nRemoves outdated comparisons and blocks from ``if`` statements comparing to ``django.VERSION``.\nSupports comparisons of the form:\n\n.. code-block:: text\n\n if django.VERSION <comparator> (<X>, <Y>):\n ...\n\nWhere ``<comparator>`` is one of ``<``, ``<=`` , ``>``, or ``>=``, and ``<X>`` and ``<Y>`` are integer literals.\nA single ``else`` block may be present, but ``elif`` is not supported.\n\n.. code-block:: diff\n\n -if django.VERSION < (4, 1):\n - class RenameIndex:\n - ...\n\n -if django.VERSION >= (4, 1):\n - constraint.validate()\n -else:\n - custom_validation(constraint)\n +constraint.validate()\n\nSee also `pyupgrade\u2019s similar feature <https://github.com/asottile/pyupgrade/#python2-and-old-python3x-blocks>`__ that removes outdated code from checks on the Python version.\n\nVersioned test skip decorators\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``versioned_test_skip_decorators``\n\nRemoves outdated test skip decorators that compare to ``django.VERSION``.\nLike the above, it requires comparisons of the form:\n\n.. code-block:: text\n\n django.VERSION <comparator> (<X>, <Y>)\n\nSupports these test skip decorators:\n\n* |unittest.skipIf|__\n\n .. |unittest.skipIf| replace:: ``@unittest.skipIf``\n __ https://docs.python.org/3/library/unittest.html#unittest.skipIf\n\n* |unittest.skipUnless|__\n\n .. |unittest.skipUnless| replace:: ``@unittest.skipUnless``\n __ https://docs.python.org/3/library/unittest.html#unittest.skipUnless\n\n* |pytest.mark.skipif|__\n\n .. |pytest.mark.skipif| replace:: ``@pytest.mark.skipif``\n __ https://docs.pytest.org/en/stable/how-to/skipping.html#id1\n\nFor example:\n\n.. code-block:: diff\n\n import unittest\n\n import django\n import pytest\n from django.test import TestCase\n\n class ExampleTests(TestCase):\n - @unittest.skipIf(django.VERSION < (5, 1), \"Django 5.1+\")\n def test_one(self):\n ...\n\n - @unittest.skipUnless(django.VERSION >= (5, 1), \"Django 5.1+\")\n def test_two(self):\n ...\n\n - @pytest.mark.skipif(django.VERSION < (5, 1), reason=\"Django 5.1+\")\n def test_three(self):\n ...\n\n -@unittest.skipIf(django.VERSION < (5, 1), \"Django 5.1+\")\n class Example2Tests(TestCase):\n ...\n\n -@pytest.mark.skipif(django.VERSION < (5, 1), reason=\"Django 5.1+\")\n class Example3Tests(TestCase):\n ...\n\nDjango 5.1\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/5.1/releases/5.1/>`__\n\n``CheckConstraint`` ``condition`` argument\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``check_constraint_condition``\n\nRewrites calls to ``CheckConstraint`` and built-in subclasses from the old ``check`` argument to the new name ``condition``.\n\n.. code-block:: diff\n\n -CheckConstraint(check=Q(amount__gte=0))\n +CheckConstraint(condition=Q(amount__gte=0))\n\nDjango 5.0\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/5.0/releases/5.0/>`__\n\n``format_html()`` calls\n~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``format_html``\n\nRewrites ``format_html()`` calls without ``args`` or ``kwargs`` but using ``str.format()``.\nSuch calls are most likely incorrectly applying formatting without escaping, making them vulnerable to HTML injection.\nSuch use cases are why calling ``format_html()`` without any arguments or keyword arguments was deprecated in `Ticket #34609 <https://code.djangoproject.com/ticket/34609>`__.\n\n.. code-block:: diff\n\n from django.utils.html import format_html\n\n -format_html(\"<marquee>{}</marquee>\".format(message))\n +format_html(\"<marquee>{}</marquee>\", message)\n\n -format_html(\"<marquee>{name}</marquee>\".format(name=name))\n +format_html(\"<marquee>{name}</marquee>\", name=name)\n\nDjango 4.2\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/4.2/releases/4.2/>`__\n\n``STORAGES`` setting\n~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``settings_storages``\n\nCombines deprecated settings ``DEFAULT_FILE_STORAGE`` and ``STATICFILES_STORAGE`` into the new ``STORAGES`` setting, within settings files.\nOnly applies if all old settings are defined as strings, at module level, and a ``STORAGES`` setting hasn\u2019t been defined.\n\nSettings files are heuristically detected as modules with the whole word \u201csettings\u201d somewhere in their path.\nFor example ``myproject/settings.py`` or ``myproject/settings/production.py``.\n\n.. code-block:: diff\n\n -DEFAULT_FILE_STORAGE = \"example.storages.ExtendedFileSystemStorage\"\n -STATICFILES_STORAGE = \"example.storages.ExtendedS3Storage\"\n +STORAGES = {\n + \"default\": {\n + \"BACKEND\": \"example.storages.ExtendedFileSystemStorage\",\n + },\n + \"staticfiles\": {\n + \"BACKEND\": \"example.storages.ExtendedS3Storage\",\n + },\n +}\n\nIf the module has a ``from ... import *`` with a module path mentioning \u201csettings\u201d, django-upgrade makes an educated guess that a base ``STORAGES`` setting is imported from there.\nIt then uses ``**`` to extend that with any values in the current module:\n\n.. code-block:: diff\n\n from example.settings.base import *\n -DEFAULT_FILE_STORAGE = \"example.storages.S3Storage\"\n +STORAGES = {\n + **STORAGES,\n + \"default\": {\n + \"BACKEND\": \"example.storages.S3Storage\",\n + },\n +}\n\nTest client HTTP headers\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``test_http_headers``\n\nTransforms HTTP headers from the old WSGI kwarg format to use the new ``headers`` dictionary, for:\n\n* ``Client`` method like ``self.client.get()``\n* ``Client`` instantiation\n* ``RequestFactory`` instantiation\n\n.. code-block:: diff\n\n -response = self.client.get(\"/\", HTTP_ACCEPT=\"text/plain\")\n +response = self.client.get(\"/\", headers={\"accept\": \"text/plain\"})\n\n from django.test import Client\n -Client(HTTP_ACCEPT_LANGUAGE=\"fr-fr\")\n +Client(headers={\"accept-language\": \"fr-fr\"})\n\n from django.test import RequestFactory\n -RequestFactory(HTTP_USER_AGENT=\"curl\")\n +RequestFactory(headers={\"user-agent\": \"curl\"})\n\n\n``index_together`` deprecation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``index_together``\n\nRewrites ``index_together`` declarations into ``indexes`` declarations in model ``Meta`` classes.\n\n.. code-block:: diff\n\n from django.db import models\n\n class Duck(models.Model):\n class Meta:\n - index_together = [[\"bill\", \"tail\"]]\n + indexes = [models.Index(fields=[\"bill\", \"tail\"])]\n\n``assertFormsetError`` and ``assertQuerysetEqual``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``assert_set_methods``\n\nRewrites calls to these test case methods from the old names to the new ones with capitalized \u201cSet\u201d.\n\n.. code-block:: diff\n\n -self.assertFormsetError(response.context[\"form\"], \"username\", [\"Too long\"])\n +self.assertFormSetError(response.context[\"form\"], \"username\", [\"Too long\"])\n\n -self.assertQuerysetEqual(authors, [\"Brad Dayley\"], lambda a: a.name)\n +self.assertQuerySetEqual(authors, [\"Brad Dayley\"], lambda a: a.name)\n\nDjango 4.1\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/4.1/releases/4.1/>`__\n\n``django.utils.timezone.utc`` deprecations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``utils_timezone``\n\nRewrites imports of ``django.utils.timezone.utc`` to use ``datetime.timezone.utc``.\nRequires an existing import of the ``datetime`` module.\n\n.. code-block:: diff\n\n import datetime\n -from django.utils.timezone import utc\n\n -calculate_some_datetime(utc)\n +calculate_some_datetime(datetime.timezone.utc)\n\n.. code-block:: diff\n\n import datetime as dt\n from django.utils import timezone\n\n\n -do_a_thing(timezone.utc)\n +do_a_thing(dt.timezone.utc)\n\n``assertFormError()`` and ``assertFormsetError()``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``assert_form_error``\n\nRewrites calls to these test case methods from the old signatures to the new ones.\n\n.. code-block:: diff\n\n -self.assertFormError(response, \"form\", \"username\", [\"Too long\"])\n +self.assertFormError(response.context[\"form\"], \"username\", [\"Too long\"])\n\n -self.assertFormError(response, \"form\", \"username\", None)\n +self.assertFormError(response.context[\"form\"], \"username\", [])\n\n -self.assertFormsetError(response, \"formset\", 0, \"username\", [\"Too long\"])\n +self.assertFormsetError(response.context[\"formset\"], 0, \"username\", [\"Too long\"])\n\n -self.assertFormsetError(response, \"formset\", 0, \"username\", None)\n +self.assertFormsetError(response.context[\"formset\"], 0, \"username\", [])\n\nDjango 4.0\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/4.0/releases/4.0/>`__\n\n``USE_L10N``\n~~~~~~~~~~~~\n\n**Name:** ``use_l10n``\n\nRemoves the deprecated ``USE_L10N`` setting if set to its default value of ``True``.\n\nSettings files are heuristically detected as modules with the whole word \u201csettings\u201d somewhere in their path.\nFor example ``myproject/settings.py`` or ``myproject/settings/production.py``.\n\n.. code-block:: diff\n\n -USE_L10N = True\n\n``lookup_needs_distinct``\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``admin_lookup_needs_distinct``\n\nRenames the undocumented ``django.contrib.admin.utils.lookup_needs_distinct`` to ``lookup_spawns_duplicates``:\n\n.. code-block:: diff\n\n -from django.contrib.admin.utils import lookup_needs_distinct\n +from django.contrib.admin.utils import lookup_spawns_duplicates\n\n -if lookup_needs_distinct(self.opts, search_spec):\n +if lookup_spawns_duplicates(self.opts, search_spec):\n ...\n\nCompatibility imports\n~~~~~~~~~~~~~~~~~~~~~\n\nRewrites some compatibility imports:\n\n* ``django.utils.translation.template.TRANSLATOR_COMMENT_MARK`` in ``django.template.base``\n\n.. code-block:: diff\n\n -from django.template.base import TRANSLATOR_COMMENT_MARK\n +from django.utils.translation.template import TRANSLATOR_COMMENT_MARK\n\nDjango 3.2\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/3.2/releases/3.2/>`__\n\n``@admin.action()``\n~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``admin_decorators``\n\nRewrites functions that have admin action attributes assigned to them to use the new |@admin.action decorator|_.\nThis only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.\n\n.. |@admin.action decorator| replace:: ``@admin.action()`` decorator\n.. _@admin.action decorator: https://docs.djangoproject.com/en/stable/ref/contrib/admin/actions/#django.contrib.admin.action\n\n.. code-block:: diff\n\n from django.contrib import admin\n\n # Module-level actions:\n\n +@admin.action(\n + description=\"Publish articles\",\n +)\n def make_published(modeladmin, request, queryset):\n ...\n\n -make_published.short_description = \"Publish articles\"\n\n # \u2026and within classes:\n\n @admin.register(Book)\n class BookAdmin(admin.ModelAdmin):\n + @admin.action(\n + description=\"Unpublish articles\",\n + permissions=(\"unpublish\",),\n + )\n def make_unpublished(self, request, queryset):\n ...\n\n - make_unpublished.allowed_permissions = (\"unpublish\",)\n - make_unpublished.short_description = \"Unpublish articles\"\n\n``@admin.display()``\n~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``admin_decorators``\n\nRewrites functions that have admin display attributes assigned to them to use the new |@admin.display decorator|_.\nThis only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.\n\n.. |@admin.display decorator| replace:: ``@admin.display()`` decorator\n.. _@admin.display decorator: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.display\n\n.. code-block:: diff\n\n from django.contrib import admin\n\n # Module-level display functions:\n\n +@admin.display(\n + description=\"NAME\",\n +)\n def upper_case_name(obj):\n ...\n\n -upper_case_name.short_description = \"NAME\"\n\n # \u2026and within classes:\n\n @admin.register(Book)\n class BookAdmin(admin.ModelAdmin):\n + @admin.display(\n + description='Is Published?',\n + boolean=True,\n + ordering='-publish_date',\n + )\n def is_published(self, obj):\n ...\n\n - is_published.boolean = True\n - is_published.admin_order_field = '-publish_date'\n - is_published.short_description = 'Is Published?'\n\n``BaseCommand.requires_system_checks``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``management_commands``\n\nRewrites the ``requires_system_checks`` attributes of management command classes from bools to ``\"__all__\"`` or ``[]`` as appropriate.\nThis only applies in command files, which are heuristically detected as files with ``management/commands`` somewhere in their path.\n\n.. code-block:: diff\n\n from django.core.management.base import BaseCommand\n\n class Command(BaseCommand):\n - requires_system_checks = True\n + requires_system_checks = \"__all__\"\n\n class SecondCommand(BaseCommand):\n - requires_system_checks = False\n + requires_system_checks = []\n\n``EmailValidator``\n~~~~~~~~~~~~~~~~~~\n\n**Name:** ``email_validator``\n\nRewrites the ``whitelist`` keyword argument to its new name ``allowlist``.\n\n.. code-block:: diff\n\n from django.core.validators import EmailValidator\n\n -EmailValidator(whitelist=[\"example.com\"])\n +EmailValidator(allowlist=[\"example.com\"])\n\n``default_app_config``\n~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``default_app_config``\n\nRemoves module-level ``default_app_config`` assignments from ``__init__.py`` files:\n\n.. code-block:: diff\n\n -default_app_config = 'my_app.apps.AppConfig'\n\nDjango 3.1\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/3.1/releases/3.1/>`__\n\n``JSONField``\n~~~~~~~~~~~~~\n\n**Name:** ``compatibility_imports``\n\nRewrites imports of ``JSONField`` and related transform classes from those in ``django.contrib.postgres`` to the new all-database versions.\nIgnores usage in migration files, since Django kept the old class around to support old migrations.\nYou will need to make migrations after this fix makes changes to models.\n\n.. code-block:: diff\n\n -from django.contrib.postgres.fields import JSONField\n +from django.db.models import JSONField\n\n``PASSWORD_RESET_TIMEOUT_DAYS``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``password_reset_timeout_days``\n\nRewrites the setting ``PASSWORD_RESET_TIMEOUT_DAYS`` to ``PASSWORD_RESET_TIMEOUT``, adding the multiplication by the number of seconds in a day.\n\nSettings files are heuristically detected as modules with the whole word \u201csettings\u201d somewhere in their path.\nFor example ``myproject/settings.py`` or ``myproject/settings/production.py``.\n\n.. code-block:: diff\n\n -PASSWORD_RESET_TIMEOUT_DAYS = 4\n +PASSWORD_RESET_TIMEOUT = 60 * 60 * 24 * 4\n\n``Signal``\n~~~~~~~~~~\n\n**Name:** ``signal_providing_args``\n\nRemoves the deprecated documentation-only ``providing_args`` argument.\n\n.. code-block:: diff\n\n from django.dispatch import Signal\n -my_cool_signal = Signal(providing_args=[\"documented\", \"arg\"])\n +my_cool_signal = Signal()\n\n``get_random_string``\n~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``crypto_get_random_string``\n\nInjects the now-required ``length`` argument, with its previous default ``12``.\n\n.. code-block:: diff\n\n from django.utils.crypto import get_random_string\n -key = get_random_string(allowed_chars=\"01234567899abcdef\")\n +key = get_random_string(length=12, allowed_chars=\"01234567899abcdef\")\n\n``NullBooleanField``\n~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``null_boolean_field``\n\nTransforms the ``NullBooleanField()`` model field to ``BooleanField(null=True)``.\nApplied only in model files, not migration files, since Django kept the old class around to support old migrations.\nYou will need to make migrations after this fix makes changes to models.\n\n.. code-block:: diff\n\n -from django.db.models import Model, NullBooleanField\n +from django.db.models import Model, BooleanField\n\n class Book(Model):\n - valuable = NullBooleanField(\"Valuable\")\n + valuable = BooleanField(\"Valuable\", null=True)\n\n``ModelMultipleChoiceField``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``forms_model_multiple_choice_field``\n\nReplace ``list`` error message key with ``list_invalid`` on forms ``ModelMultipleChoiceField``.\n\n.. code-block:: diff\n\n -forms.ModelMultipleChoiceField(error_messages={\"list\": \"Enter multiple values.\"})\n +forms.ModelMultipleChoiceField(error_messages={\"invalid_list\": \"Enter multiple values.\"})\n\nDjango 3.0\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/3.0/releases/3.0/>`__\n\n``django.utils.encoding`` aliases\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``utils_encoding``\n\nRewrites ``smart_text()`` to ``smart_str()``, and ``force_text()`` to ``force_str()``.\n\n.. code-block:: diff\n\n -from django.utils.encoding import force_text, smart_text\n +from django.utils.encoding import force_str, smart_str\n\n\n -force_text(\"yada\")\n -smart_text(\"yada\")\n +force_str(\"yada\")\n +smart_str(\"yada\")\n\n``django.utils.http`` deprecations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``utils_http``:\n\nRewrites the ``urlquote()``, ``urlquote_plus()``, ``urlunquote()``, and ``urlunquote_plus()`` functions to the ``urllib.parse`` versions.\nAlso rewrites the internal function ``is_safe_url()`` to ``url_has_allowed_host_and_scheme()``.\n\n.. code-block:: diff\n\n -from django.utils.http import urlquote\n +from urllib.parse import quote\n\n -escaped_query_string = urlquote(query_string)\n +escaped_query_string = quote(query_string)\n\n``django.utils.text`` deprecation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``utils_text``\n\nRewrites ``unescape_entities()`` with the standard library ``html.escape()``.\n\n.. code-block:: diff\n\n -from django.utils.text import unescape_entities\n +import html\n\n -unescape_entities(\"some input string\")\n +html.escape(\"some input string\")\n\n``django.utils.translation`` deprecations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``utils_translation``\n\nRewrites the ``ugettext()``, ``ugettext_lazy()``, ``ugettext_noop()``, ``ungettext()``, and ``ungettext_lazy()`` functions to their non-u-prefixed versions.\n\n.. code-block:: diff\n\n -from django.utils.translation import ugettext as _, ungettext\n +from django.utils.translation import gettext as _, ngettext\n\n -ungettext(\"octopus\", \"octopodes\", n)\n +ngettext(\"octopus\", \"octopodes\", n)\n\nDjango 2.2\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/2.2/releases/2.2/>`__\n\n``HttpRequest.headers``\n~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``request_headers``\n\nRewrites use of ``request.META`` to read HTTP headers to instead use |request.headers|_.\nHeader lookups are done in lowercase per `the HTTP/2 specification <https://httpwg.org/specs/rfc9113.html#HttpHeaders>`__.\n\n.. |request.headers| replace:: ``request.headers``\n.. _request.headers: https://docs.djangoproject.com/en/stable/ref/request-response/#django.http.HttpRequest.headers\n\n.. code-block:: diff\n\n -request.META['HTTP_ACCEPT_ENCODING']\n +request.headers['accept-encoding']\n\n -self.request.META.get('HTTP_SERVER', '')\n +self.request.headers.get('server', '')\n\n -request.META.get('CONTENT_LENGTH')\n +request.headers.get('content-length')\n\n -\"HTTP_SERVER\" in request.META\n +\"server\" in request.headers\n\n``QuerySetPaginator``\n~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``queryset_paginator``\n\nRewrites deprecated alias ``django.core.paginator.QuerySetPaginator`` to ``Paginator``.\n\n.. code-block:: diff\n\n -from django.core.paginator import QuerySetPaginator\n +from django.core.paginator import Paginator\n\n -QuerySetPaginator(...)\n +Paginator(...)\n\n\n``FixedOffset``\n~~~~~~~~~~~~~~~\n\n**Name:** ``timezone_fixedoffset``\n\nRewrites deprecated class ``FixedOffset(x, y))`` to ``timezone(timedelta(minutes=x), y)``\n\nKnown limitation: this fixer will leave code broken with an ``ImportError`` if ``FixedOffset`` is called with only ``*args`` or ``**kwargs``.\n\n.. code-block:: diff\n\n -from django.utils.timezone import FixedOffset\n -FixedOffset(120, \"Super time\")\n +from datetime import timedelta, timezone\n +timezone(timedelta(minutes=120), \"Super time\")\n\n``FloatRangeField``\n~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``postgres_float_range_field``\n\nRewrites model and form fields using ``FloatRangeField`` to ``DecimalRangeField``, from the relevant ``django.contrib.postgres`` modules.\n\n.. code-block:: diff\n\n from django.db.models import Model\n -from django.contrib.postgres.fields import FloatRangeField\n +from django.contrib.postgres.fields import DecimalRangeField\n\n class MyModel(Model):\n - my_field = FloatRangeField(\"My range of numbers\")\n + my_field = DecimalRangeField(\"My range of numbers\")\n\n``TestCase`` class database declarations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``testcase_databases``\n\nRewrites the ``allow_database_queries`` and ``multi_db`` attributes of Django\u2019s ``TestCase`` classes to the new ``databases`` attribute.\nThis only applies in test files, which are heuristically detected as files with either \u201ctest\u201d or \u201ctests\u201d somewhere in their path.\n\nNote that this will only rewrite to ``databases = []`` or ``databases = \"__all__\"``.\nWith multiple databases you can save some test time by limiting test cases to the databases they require (which is why Django made the change).\n\n.. code-block:: diff\n\n from django.test import SimpleTestCase\n\n class MyTests(SimpleTestCase):\n - allow_database_queries = True\n + databases = \"__all__\"\n\n def test_something(self):\n self.assertEqual(2 * 2, 4)\n\nDjango 2.1\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/2.1/releases/2.1/>`__\n\nNo fixers yet.\n\nDjango 2.0\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/2.0/releases/2.0/>`__\n\nURL\u2019s\n~~~~~\n\n**Name:** ``django_urls``\n\nRewrites imports of ``include()`` and ``url()`` from ``django.conf.urls`` to ``django.urls``.\n``url()`` calls using compatible regexes are rewritten to the |new path() syntax|_, otherwise they are converted to call ``re_path()``.\n\n.. |new path() syntax| replace:: new ``path()`` syntax\n.. _new path() syntax: https://docs.djangoproject.com/en/2.0/releases/2.0/#simplified-url-routing-syntax\n\n.. code-block:: diff\n\n -from django.conf.urls import include, url\n +from django.urls import include, path, re_path\n\n urlpatterns = [\n - url(r'^$', views.index, name='index'),\n + path('', views.index, name='index'),\n - url(r'^about/$', views.about, name='about'),\n + path('about/', views.about, name='about'),\n - url(r'^post/(?P<slug>[-a-zA-Z0-9_]+)/$', views.post, name='post'),\n + path('post/<slug:slug>/', views.post, name='post'),\n - url(r'^weblog', include('blog.urls')),\n + re_path(r'^weblog', include('blog.urls')),\n ]\n\nExisting ``re_path()`` calls are also rewritten to the ``path()`` syntax when eligible.\n\n.. code-block:: diff\n\n -from django.urls import include, re_path\n +from django.urls import include, path, re_path\n\n urlpatterns = [\n - re_path(r'^about/$', views.about, name='about'),\n + path('about/', views.about, name='about'),\n re_path(r'^post/(?P<slug>[\\w-]+)/$', views.post, name='post'),\n ]\n\nThe compatible regexes that will be converted to use `path converters <https://docs.djangoproject.com/en/stable/topics/http/urls/#path-converters>`__ are the following:\n\n* ``[^/]+`` \u2192 ``str``\n* ``[0-9]+`` \u2192 ``int``\n* ``[-a-zA-Z0-9_]+`` \u2192 ``slug``\n* ``[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`` \u2192 ``uuid``\n* ``.+`` \u2192 ``path``\n\nThese are taken from the path converter classes.\n\nFor some cases, this change alters the type of the arguments passed to the view, from ``str`` to the converted type (e.g. ``int``).\nThis is not guaranteed backwards compatible: there is a chance that the view expects a string, rather than the converted type.\nBut, pragmatically, it seems 99.9% of views do not require strings, and instead work with either strings or the converted type.\nThus, you should test affected paths after this fixer makes any changes.\n\nNote that ``[\\w-]`` is sometimes used for slugs, but is not converted because it might be incompatible.\nThat pattern matches all Unicode word characters, such as \u201c\u03b1\u201d, unlike Django's ``slug`` converter, which only matches Latin characters.\n\n``lru_cache``\n~~~~~~~~~~~~~\n\n**Name:** ``compatibility_imports``\n\nRewrites imports of ``lru_cache`` from ``django.utils.functional`` to use ``functools``.\n\n.. code-block:: diff\n\n -from django.utils.functional import lru_cache\n +from functools import lru_cache\n\n``ContextDecorator``\n~~~~~~~~~~~~~~~~~~~~\n\nRewrites imports of ``ContextDecorator`` from ``django.utils.decorators`` to use ``contextlib``.\n\n.. code-block:: diff\n\n -from django.utils.decorators import ContextDecorator\n +from contextlib import ContextDecorator\n\n``<func>.allow_tags = True``\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``admin_allow_tags``\n\nRemoves assignments of ``allow_tags`` attributes to ``True``.\nThis was an admin feature to allow display functions to return HTML without marking it as unsafe, deprecated in Django 1.9.\nIn practice, most display functions that return HTML already use |format_html()|_ or similar, so the attribute wasn\u2019t necessary.\nThis only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.\n\n.. |format_html()| replace:: ``format_html()``\n.. _format_html(): https://docs.djangoproject.com/en/stable/ref/utils/#django.utils.html.format_html\n\n.. code-block:: diff\n\n from django.contrib import admin\n\n def upper_case_name(obj):\n ...\n\n -upper_case_name.allow_tags = True\n\nDjango 1.11\n-----------\n\n`Release Notes <https://docs.djangoproject.com/en/1.11/releases/1.11/>`__\n\nCompatibility imports\n~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``compatibility_imports``\n\nRewrites some compatibility imports:\n\n* ``django.core.exceptions.EmptyResultSet`` in ``django.db.models.query``, ``django.db.models.sql``, and ``django.db.models.sql.datastructures``\n* ``django.core.exceptions.FieldDoesNotExist`` in ``django.db.models.fields``\n\nWhilst mentioned in the `Django 3.1 release notes <https://docs.djangoproject.com/en/3.1/releases/3.1/#id1>`_, these have been possible since Django 1.11.\n\n.. code-block:: diff\n\n -from django.db.models.query import EmptyResultSet\n +from django.core.exceptions import EmptyResultSet\n\n -from django.db.models.fields import FieldDoesNotExist\n +from django.core.exceptions import FieldDoesNotExist\n\nDjango 1.10\n-----------\n\n`Release Notes <https://docs.djangoproject.com/en/1.10/releases/1.10/>`__\n\n``request.user`` boolean attributes\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``request_user_attributes``\n\nRewrites calls to ``request.user.is_authenticated()`` and ``request.user.is_anonymous()`` to remove the parentheses, per `the deprecation <https://docs.djangoproject.com/en/1.10/releases/1.10/#using-user-is-authenticated-and-user-is-anonymous-as-methods>`__.\n\n.. code-block:: diff\n\n -request.user.is_authenticated()\n +request.user.is_authenticated\n\n -self.request.user.is_anonymous()\n +self.request.user.is_anonymous\n\nCompatibility imports\n~~~~~~~~~~~~~~~~~~~~~\n\nRewrites some compatibility imports:\n\n* ``django.templatetags.static.static`` in ``django.contrib.staticfiles.templatetags.staticfiles``\n\n (Whilst mentioned in the `Django 2.1 release notes <https://docs.djangoproject.com/en/2.1/releases/2.1/#features-deprecated-in-2-1>`_, this has been possible since Django 1.10.)\n\n* ``django.urls.*`` in ``django.core.urlresolvers.*``\n\n.. code-block:: diff\n\n -from django.contrib.staticfiles.templatetags.staticfiles import static\n +from django.templatetags.static import static\n\n -from django.core.urlresolvers import reverse\n +from django.urls import reverse\n\n -from django.core.urlresolvers import resolve\n +from django.urls import resolve\n\nDjango 1.9\n-----------\n\n`Release Notes <https://docs.djangoproject.com/en/stable/releases/1.9/>`__\n\n``on_delete`` argument\n~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``on_delete``\n\nAdd ``on_delete=models.CASCADE`` to ``ForeignKey`` and ``OneToOneField``:\n\n.. code-block:: diff\n\n from django.db import models\n\n -models.ForeignKey(\"auth.User\")\n +models.ForeignKey(\"auth.User\", on_delete=models.CASCADE)\n\n -models.OneToOneField(\"auth.User\")\n +models.OneToOneField(\"auth.User\", on_delete=models.CASCADE)\n\nThis fixer also support from-imports:\n\n.. code-block:: diff\n\n -from django.db.models import ForeignKey\n +from django.db.models import CASCADE, ForeignKey\n\n -ForeignKey(\"auth.User\")\n +ForeignKey(\"auth.User\", on_delete=CASCADE)\n\n``DATABASES``\n~~~~~~~~~~~~~\n\n**Name:** ``settings_database_postgresql``\n\nUpdate the ``DATABASES`` setting backend path ``django.db.backends.postgresql_psycopg2`` to use the renamed version ``django.db.backends.postgresql``.\n\nSettings files are heuristically detected as modules with the whole word \u201csettings\u201d somewhere in their path.\nFor example ``myproject/settings.py`` or ``myproject/settings/production.py``.\n\n.. code-block:: diff\n\n DATABASES = {\n \"default\": {\n - \"ENGINE\": \"django.db.backends.postgresql_psycopg2\",\n + \"ENGINE\": \"django.db.backends.postgresql\",\n \"NAME\": \"mydatabase\",\n \"USER\": \"mydatabaseuser\",\n \"PASSWORD\": \"mypassword\",\n \"HOST\": \"127.0.0.1\",\n \"PORT\": \"5432\",\n }\n }\n\nCompatibility imports\n~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``compatibility_imports``\n\nRewrites some compatibility imports:\n\n* ``django.forms.utils.pretty_name`` in ``django.forms.forms``\n* ``django.forms.boundfield.BoundField`` in ``django.forms.forms``\n* ``django.forms.widgets.SelectDateWidget`` in ``django.forms.extras``\n\nWhilst mentioned in the `Django 3.1 release notes <https://docs.djangoproject.com/en/3.1/releases/3.1/#id1>`_, these have been possible since Django 1.9.\n\n.. code-block:: diff\n\n -from django.forms.forms import pretty_name\n +from django.forms.utils import pretty_name\n\nDjango 1.8\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/stable/releases/1.8/>`__\n\nNo fixers yet.\n\nDjango 1.7\n----------\n\n`Release Notes <https://docs.djangoproject.com/en/stable/releases/1.7/>`__\n\nAdmin model registration\n~~~~~~~~~~~~~~~~~~~~~~~~\n\n**Name:** ``admin_register``\n\nRewrites ``admin.site.register()`` calls to the new |@admin.register|_ decorator syntax when eligible.\nThis only applies in files that use ``from django.contrib import admin`` or ``from django.contrib.gis import admin``.\n\n.. |@admin.register| replace:: ``@admin.register()``\n.. _@admin.register: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#the-register-decorator\n\n.. code-block:: diff\n\n from django.contrib import admin\n\n +@admin.register(MyModel1, MyModel2)\n class MyCustomAdmin(admin.ModelAdmin):\n ...\n\n -admin.site.register(MyModel1, MyCustomAdmin)\n -admin.site.register(MyModel2, MyCustomAdmin)\n\nThis also works with custom admin sites.\nSuch calls are detected heuristically based on three criteria:\n\n1. The object whose ``register()`` method is called has a name ending with ``site``.\n2. The registered class has a name ending with ``Admin``.\n3. The filename has the word ``admin`` somewhere in its path.\n\n.. code-block:: diff\n\n from myapp.admin import custom_site\n from django.contrib import admin\n\n +@admin.register(MyModel)\n +@admin.register(MyModel, site=custom_site)\n class MyModelAdmin(admin.ModelAdmin):\n pass\n\n -custom_site.register(MyModel, MyModelAdmin)\n -admin.site.register(MyModel, MyModelAdmin)\n\nIf a ``register()`` call is preceded by an ``unregister()`` call that includes the same model, it is ignored.\n\n.. code-block:: python\n\n from django.contrib import admin\n\n\n class MyCustomAdmin(admin.ModelAdmin):\n ...\n\n\n admin.site.unregister(MyModel1)\n admin.site.register(MyModel1, MyCustomAdmin)\n\nCompatibility imports\n~~~~~~~~~~~~~~~~~~~~~\n\nRewrites some compatibility imports:\n\n* ``django.contrib.admin.helpers.ACTION_CHECKBOX_NAME`` in ``django.contrib.admin``\n* ``django.template.context.BaseContext``, ``django.template.context.Context``, ``django.template.context.ContextPopException`` and ``django.template.context.RequestContext`` in ``django.template.base``\n\n.. code-block:: diff\n\n -from django.contrib.admin import ACTION_CHECKBOX_NAME\n +from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME\n\n -from django.template.base import Context\n +from django.template.context import Context\n",
"bugtrack_url": null,
"license": null,
"summary": "Automatically upgrade your Django project code.",
"version": "1.22.2",
"project_urls": {
"Changelog": "https://github.com/adamchainz/django-upgrade/blob/main/CHANGELOG.rst",
"Funding": "https://adamj.eu/books/",
"Repository": "https://github.com/adamchainz/django-upgrade"
},
"split_keywords": [
"django"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "98647b1432a7eca9e8e24b27ef89b9badca59add35a6dc785a63aa886b167c9d",
"md5": "cef23951b2f9feff7815b48a236e2ec8",
"sha256": "fb76b183b2e8186bb1157734083569a4b754dce2e812c2a723454bd248fb1373"
},
"downloads": -1,
"filename": "django_upgrade-1.22.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "cef23951b2f9feff7815b48a236e2ec8",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 69114,
"upload_time": "2024-12-02T15:10:58",
"upload_time_iso_8601": "2024-12-02T15:10:58.967842Z",
"url": "https://files.pythonhosted.org/packages/98/64/7b1432a7eca9e8e24b27ef89b9badca59add35a6dc785a63aa886b167c9d/django_upgrade-1.22.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "f34624e32ffdad682df8c273e2f434096246c89d265948a2fd214307f31eeeec",
"md5": "df137cb8e3a30dfba0f32ea2e6dbe8c2",
"sha256": "8643abdde2961e3c60173ebee84f48371ffbec5181095a38cac080947af0d2ae"
},
"downloads": -1,
"filename": "django_upgrade-1.22.2.tar.gz",
"has_sig": false,
"md5_digest": "df137cb8e3a30dfba0f32ea2e6dbe8c2",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 67932,
"upload_time": "2024-12-02T15:11:00",
"upload_time_iso_8601": "2024-12-02T15:11:00.875441Z",
"url": "https://files.pythonhosted.org/packages/f3/46/24e32ffdad682df8c273e2f434096246c89d265948a2fd214307f31eeeec/django_upgrade-1.22.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-02 15:11:00",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "adamchainz",
"github_project": "django-upgrade",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "django-upgrade"
}