Notas de lanzamiento de Django 4.2

3 de abril de 2023

Bienvenido a Django 4.2!

Estas notas de lanzamiento cubren las nuevas características, así como algunos cambios incompatibles con la retrocompatibilidad que desecharás estar al tanto cuando actualices desde Django 4.1 o una versión anterior. Hemos comenzado el proceso de desactivación para algunas características <deprecated-features-4.2>`.

Consulte la guía Cómo actualizar Django a una versión más reciente si estás actualizando un proyecto existente.

Django 4.2 se designa como un lanzamiento con soporte a largo plazo. Recibirá actualizaciones de seguridad durante al menos tres años después de su lanzamiento. El soporte para la LTS anterior, Django 3.2, terminará en abril de 2024.

Compatibilidad con Python

Django 4.2 admite Python 3.8, 3.9, 3.10, 3.11 y 3.12 (a partir de 4.2.8). Recomendamos y solo oficialmente apoyamos la última versión de cada serie.

Qué hay de nuevo en Django 4.2

Soporte para Psycopg 3

Django ahora admite la versión 3.1.8 o superior de psycopg. Para actualizar tu código, instala la biblioteca psycopg, no necesitas cambiar el parámetro ENGINE ya que django.db.backends.postgresql admite ambas bibliotecas.

Soporte para psycopg2 probablemente será deprecado y eliminado en algún punto del futuro.

Ten en cuenta que psycopg 3 introduce algunas modificaciones disruptivas sobre psycopg2. Como consecuencia, es posible que debas realizar algunos cambios para tener en cuenta las **diferencias de psycopg2**_.

Comentarios sobre columnas y tablas

El nuevo Field.db_comment y las opciones Meta.db_table_comment permiten crear comentarios en columnas y tablas, respectivamente. Por ejemplo:

from django.db import models


class Question(models.Model):
    text = models.TextField(db_comment="Poll question")
    pub_date = models.DateTimeField(
        db_comment="Date and time when the question was published",
    )

    class Meta:
        db_table_comment = "Poll questions"


class Answer(models.Model):
    question = models.ForeignKey(
        Question,
        on_delete=models.CASCADE,
        db_comment="Reference to a question",
    )
    answer = models.TextField(db_comment="Question answer")

    class Meta:
        db_table_comment = "Question answers"

También se permite cambiar los comentarios de la tabla mediante la nueva operación AlterModelTableComment, que define los comentarios de la tabla definidos en el atributo Meta.db_table_comment <django.db.models.Options.db_table_comment>.

Mitigación para el ataque BREACH

GZipMiddleware incluye ahora una mitigación para el ataque BREACH. Agrega hasta 100 bytes aleatorios a las respuestas gzip para hacer que los ataques BREACH sean más difíciles. Lee más sobre la técnica de mitigación en el paper Heal The Breach (HTB).

Almacenamiento en memoria

La traducción de los textos es la siguiente:

Almacenamientos de archivos personalizados

La nueva configuración STORAGES permite configurar múltiples almacenadores de archivos personalizados. También controla los motores de almacenamiento para gestionar los archivos (la clave "default") y los archivos estáticos (la clave "staticfiles").

Las configuraciones antiguas DEFAULT_FILE_STORAGE y STATICFILES_STORAGE están descontinuadas a partir de esta versión.

Características menores

django.contrib.admin

  • La tema de color claro o oscuro del administrador ahora se puede cambiar en la interfaz de usuario, así como establecerse para seguir el ajuste del sistema.

  • El conjunto de fuentes del administrador ahora prefiere las fuentes UI del sistema y ya no requiere descargar fuentes. Además, están disponibles variables CSS para sobreescribir fácilmente las familias de fuentes por defecto.

  • El template admin/delete_confirmation.html ahora tiene algunos bloques adicionales y conexiones de scripting para facilitar la personalización.

  • Las opciones elegidas de los widgets filter_horizontal y filter_vertical ahora son filtrables.

  • El template admin/base.html ahora tiene un nuevo bloque nav-breadcrumbs que contiene el landmark de navegación y el bloque breadcrumbs.

  • La propiedad ModelAdmin.list_editable ahora utiliza transacciones atómicas al realizar ediciones.

  • jQuery se actualiza de la versión 3.6.0 a 3.6.4.

django.contrib.auth

  • La cuenta de iteraciones por defecto para el hasheador de contraseña PBKDF2 se incrementa desde 390,000 hasta 600,000.

  • UserCreationForm ahora guarda los campos formularios muchos-a-muchos para un modelo de usuario personalizado.

  • La nueva BaseUserCreationForm es ahora la clase base recomendada para personalizar el formulario de creación de usuarios.

django.contrib.gis

  • El serializador GeoJSON GeoJSON serializer ahora produce la clave id para las características serializadas, que por defecto es la llave primaria de los objetos.

  • La clase GDALRaster ahora admite pathlib.Path.

  • La clase GeoIP2 ahora admite archivos .mmdb descargados desde DB-IP.

  • El widget de plantilla OpenLayers ya no incluye CSS inline (lo que también elimina el bloque map_css anterior) para cumplir mejor con una política de seguridad del contenido estricta.

  • La clase OpenLayersWidget ahora se basa en OpenLayers 7.2.2 (anteriormente 4.6.5).

  • El nuevo lookup isempty y la expresión de clase IsEmpty() <django.contrib.gis.db.models.functions.IsEmpty> permiten filtrar geometrías vacías en PostGIS.

  • Los textos traducidos son:

django.contrib.postgres

:modulo:`django.contrib.sitemaps`

django.contrib.archivos_estáticos

  • La clase ManifestStaticFilesStorage ahora tiene un soporte experimental para reemplazar las rutas a módulos JavaScript en import y export con sus contrapartes hashadas. Si quieres probarlo, hereda de ManifestStaticFilesStorage y establece la propiedad support_js_module_import_aggregation en True.

  • La nueva atributo ManifestStaticFilesStorage.manifest_hash proporciona un hash sobre todos los archivos en el manifiesto y cambia cada vez que uno de los archivos cambie.

Base de datos

  • Ahora se admite la opción "assume_role" en OPTIONS en PostgreSQL para permitir especificar el rol de sesión.

  • Ahora se admite la opción "server_side_binding" en OPTIONS en PostgreSQL con psycopg 3.1.8+ para permitir utilizar los cursores de parámetros servidor <database-server-side-parameters-binding>`.

Información sobre errores

  • La página de depuración ahora muestra las notas de excepción :pep:`<678>` y las ubicaciones de errores detalladas :pep:`<657>` en Python 3.11+.

  • Los cookies de sesión se tratan como credenciales y por lo tanto están ocultos y reemplazados con estrellas (**********) en los informes de error.

Formularios

  • ModelForm ahora acepta la nueva opción Meta formfield_callback para personalizar los campos del formulario.

  • modelform_factory() ahora respeta el atributo formfield_callback de la Meta del form.

Internacionalización

  • Se ha agregado soporte y traducciones para el idioma kurdo central (sorani).

Registro de eventos

  • El django.db.backends logger ahora registra consultas de gestión de transacciones (BEGIN, COMMIT y ROLLBACK) a nivel DEBUG.

Comandos de Gestión

  • La orden del comando makemessages ahora admite locales con subetiquetas privadas como nl_NL-x-informal.

  • La nueva opción makemigrations --update combina cambios de modelos en la última migración y optimiza las operaciones resultantes.

Migraciones

  • Las migraciones ahora admiten serialización de objetos enum.Flag.

Modelos

  • El conjunto de consultas QuerySet ahora admite extensivamente filtrado contra Funciones de ventana con la excepción de consultas de filtro disyuntivo contra funciones de ventana al realizar agregación.

  • prefetch_related() ahora admite objetos Prefetch con conjuntos de consultas rebanadas.

  • La registro de lookups en instancias de Field ahora está soportado.

  • La traducción de los textos es la siguiente:

  • La nueva expresión KT() representa el valor de texto de una clave, índice o transformación de ruta de la clase JSONField.

  • La clase Now ahora admite precisión en microsegundos en MySQL y precisión en milisegundos en SQLite.

  • Las expresiones F() que devuelven un BooleanField pueden ser negadas ahora utilizando el operador de inversión ~F().

  • La clase Model proporciona ahora versiones asíncronas de algunos métodos que utilizan la base de datos, con un prefijo a: adelete(), arefresh_from_db() y asave().

  • Los administradores relacionados proporcionan ahora versiones asíncronas de métodos que cambian un conjunto de objetos relacionados, con un prefijo a: aadd(), aclear(), aremove() y aset().

  • La propiedad CharField.max_length ya no es necesaria para ser configurada en PostgreSQL, que admite columnas VARCHAR ilimitadas.

Solicitudes y respuestas

  • La clase StreamingHttpResponse ahora admite iteradores asíncronos cuando Django se sirve a través de ASGI.

Pruebas

  • La opción test --debug-sql ahora formatea consultas SQL con sqlparse.

  • Las clases RequestFactory, AsyncRequestFactory, Client y AsyncClient ahora admiten el parámetro headers, que acepta un diccionario de nombres y valores de encabezados. Esto permite una sintaxis más natural para declarar encabezados.

    # Before:
    self.client.get("/home/", HTTP_ACCEPT_LANGUAGE="fr")
    await self.async_client.get("/home/", ACCEPT_LANGUAGE="fr")
    
    # After:
    self.client.get("/home/", headers={"accept-language": "fr"})
    await self.async_client.get("/home/", headers={"accept-language": "fr"})
    

Utilidades

  • La traducción de los textos es la siguiente:

  • La copia interna vendida privadamente de urllib.parse.urlsplit() ahora elimina '\r', '\n' y '\t' (consulte CVE 2022-0391 y bpo-43882). Esto se hace para proteger proyectos que pueden estar utilizando incorrectamente la función interna url_has_allowed_host_and_scheme(), en lugar de utilizar una de las funciones documentadas para manejar redirecciones de URL. Las funciones Django no estaban afectadas.

  • La nueva función django.utils.http.content_disposition_header() devuelve un valor del encabezado HTTP Content-Disposition según lo especificado por RFC 6266.

Validadores

  • La lista de contraseñas comunes utilizada por CommonPasswordValidator se actualiza a la versión más reciente.

Cambios incompatibles con versiones anteriores en 4.2

Backend de base de datos API

Esta es la traducción de los textos:

  • Se elimina DatabaseFeatures.allows_group_by_pk ya que solo permanecía para adaptarse a una extensión de MySQL que ha sido reemplazada por la detección de dependencias funcionales en MySQL 5.7.15. Tenga en cuenta que DatabaseFeatures.allows_group_by_selected_pks sigue siendo compatible y debe estar habilitado si su backend admite la detección de dependencias funcionales en cláusulas GROUP BY según lo especificado por el estándar SQL:1999.

  • La herramienta inspectdb ahora utiliza display_size de DatabaseIntrospection.get_table_description() en lugar de internal_size para CharField.

Se elimina el soporte para MariaDB 10.3

El soporte upstream para MariaDB 10.3 termina en mayo de 2023. Django 4.2 admite MariaDB 10.4 y versiones superiores.

Se elimina el soporte para MySQL 5.7

La traducción de los textos es la siguiente:

Se ha dejado de admitir PostgreSQL 11

El soporte upstream para PostgreSQL 11 termina en noviembre de 2023. Django 4.2 admite PostgreSQL 12 y superior.

La configuración de update_fields en Model.save() puede ser ahora necesaria

Para evitar actualizar columnas innecesarias, QuerySet.update_or_create() pasa ahora update_fields a las llamadas a Model.save(). Como consecuencia, cualquier campo modificado en los métodos de save() personalizados debe agregarse al argumento de palabra clave update_fields antes de llamar a super(). Consulte Sobreescribiendo métodos predefinidos del modelo para obtener más detalles.

Se ha dejado de admitir agrupaciones brutas en MySQL

MySQL 8+ permite dependencias funcionales en columnas GROUP BY, por lo que se elimina el trabajo alrededor previo a Django 4.2 de agrupar por claves primarias de la tabla principal. Como consecuencia, utilizar agregaciones RawSQL() ya no está admitido en MySQL ya que no hay forma de determinar si tales agregaciones son necesarias o válidas en la cláusula GROUP BY. Utilice Funciones de agregación en su lugar.

Miscelánea

  • La función documentada django.http.multipartparser.parse_header() se ha eliminado. Use django.utils.http.parse_header_parameters() en su lugar.

  • El resultado de {% blocktranslate asvar %} ahora está marcado como seguro para fines de salida (HTML).

  • Se ha eliminado el atributo HTML autofocus en la caja de búsqueda del administrador ya que puede ser confuso para los lectores de pantalla.

  • La traducción de los textos es la siguiente:

  • Se ha eliminado el argumento alias para la función Expression.get_group_by_cols().

  • La versión mínima soportada de sqlparse se incrementa desde 0.2.2 a 0.3.1.

  • Se ha eliminado el parámetro no documentado negated de la expresión Exists.

  • Se ha eliminado el argumento is_summary de la función no documentada Query.add_annotation().

  • La versión mínima soportada de SQLite se incrementa desde 3.9.0 a 3.21.0.

  • La versión mínima soportada de asgiref se incrementa desde 3.5.2 a 3.6.0.

  • La clase UserCreationForm ahora rechaza nombres de usuario que difieren solo en mayúsculas y minúsculas. Si necesitas el comportamiento anterior, utiliza la clase BaseUserCreationForm en su lugar.

  • La versión mínima soportada de mysqlclient se incrementa desde 1.4.0 a 1.4.3.

  • La versión mínima soportada de argon2-cffi se incrementa desde 19.1.0 a 19.2.0.

  • La versión mínima soportada de Pillow se incrementa desde 6.2.0 hasta 6.2.1.

  • La versión mínima soportada de jinja2 se incrementa desde 2.9.2 hasta 2.11.0.

  • La versión mínima soportada de redis-py se incrementa desde 3.0.0 hasta 3.4.0.

  • Los objetos WSGIRequest instanciados manualmente deben proporcionarse un objeto file-like para wsgi.input. Anteriormente, Django era más laxo que el comportamiento esperado según la especificación WSGI.

  • Se elimina el soporte para PROJ < 5.

  • La clase EmailBackend ahora verifica un hostname y certificados. Si necesita el comportamiento anterior, que es menos restrictivo y no recomendado, sobreescriba la propiedad ssl_context de EmailBackend.

Características deprecadas en 4.2

La opción index_together está deprecada a favor de indexes

La opción Meta.index_together está deprecada a favor de la opción indexes.

Deben manejarse las migraciones existentes de index_together como una migración. Por ejemplo:

class Author(models.Model):
    rank = models.IntegerField()
    name = models.CharField(max_length=30)

    class Meta:
        index_together = [["rank", "name"]]

Debería convertirse en:

class Author(models.Model):
    rank = models.IntegerField()
    name = models.CharField(max_length=30)

    class Meta:
        indexes = [models.Index(fields=["rank", "name"])]

Ejecutar el comando :djadmin:makemigrations generará una migración que contenga una operación de renombramiento del índice existente mediante la clase RenameIndex. A continuación, considere fusionar las migraciones para eliminar index_together de las migraciones históricas.

La operación de migración AlterIndexTogether ahora está oficialmente soportada solo para archivos de migraciones pre-Django 4.2. Por razones de compatibilidad hacia atrás, sigue siendo parte de la API pública y no hay planes para deprecarla o eliminarla, pero no debe usarse en nuevas migraciones. Utilice las operaciones AddIndex y RemoveIndex en su lugar.

Pasando cadenas literales de JSON codificadas a JSONField está desaconsejado.

JSONField y sus consultas y agregados asociadas se utilizaban para permitir pasar cadenas literales codificadas en JSON, lo que causaba ambigüedad sobre si las cadenas literales ya estaban codificadas desde la perspectiva del backend de base de datos.

Durante el período de desuso, se intentará decodificar literales de cadena como JSON y se emitirá una advertencia en caso de éxito que apunta a pasar formas no codificadas en su lugar.

Código que antiguamente pasaba cadenas literales codificadas en JSON:

Document.objects.bulk_create(
    Document(data=Value("null")),
    Document(data=Value("[]")),
    Document(data=Value('"foo-bar"')),
)
Document.objects.annotate(
    JSONBAgg("field", default=Value("[]")),
)

Debería convertirse en:

Document.objects.bulk_create(
    Document(data=Value(None, JSONField())),
    Document(data=[]),
    Document(data="foo-bar"),
)
Document.objects.annotate(
    JSONBAgg("field", default=[]),
)

Desde Django 5.1+, las cadenas literales se interpretarán implícitamente como cadenas literales de JSON.

Miscelánea

  • La función BaseUserManager.make_random_password() está descontinuada. Consulta recetas y mejores prácticas para utilizar el módulo secrets de Python para generar contraseñas.

  • La plantilla de filtro length_is está descontinuada en favor del filtro length y el operador == dentro de una etiqueta <if>. Por ejemplo

    {% if value|length == 4 %}{% endif %}
    {% if value|length == 4 %}True{% else %}False{% endif %}
    

    En lugar de:

    {% if value|length_is:4 %}{% endif %}
    {{ value|length_is:4 }}
    
  • Las clases django.contrib.auth.hashers.SHA1PasswordHasher, django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher y django.contrib.auth.hashers.UnsaltedMD5PasswordHasher están descontinuadas.

  • La clase django.contrib.postgres.fields.CICharField está descontinuada en favor de la clase CharField(db_collation="…") con una collación no determinista e ininsensible a mayúsculas y minúsculas.

  • La clase django.contrib.postgres.fields.CIEmailField está descontinuada en favor de la clase EmailField(db_collation="…") con una collación no determinista e ininsensible a mayúsculas y minúsculas.

  • La clase django.contrib.postgres.fields.CITextField está descontinuada en favor de la clase TextField(db_collation="…") con una collación no determinista e ininsensible a mayúsculas y minúsculas.

  • El mixin django.contrib.postgres.fields.CIText está descontinuado.

  • Los atributos map_height y map_width de la clase BaseGeometryWidget están descontinuados, utiliza CSS para tamaño los widgets de mapas en su lugar.

  • La función SimpleTestCase.assertFormsetError() está descontinuada en favor de la función assertFormSetError().

  • La función TransactionTestCase.assertQuerysetEqual() está descontinuada en favor de la función assertQuerySetEqual().

  • Pasando argumentos posicionales a las clases Signer y TimestampSigner están descontinuadas en favor de argumentos solo palabras clave.

  • Los DEFAULT_FILE_STORAGE de configuración está descontinuado en favor de STORAGES["default"].

  • El ajuste STATICFILES_STORAGE está descontinuado en favor de STORAGES["staticfiles"].

  • La función django.core.files.storage.get_storage_class() está descontinuada.