Mi aplicación que actualmente depende de Postgres y Django's Postgres-only JSONField . El campo funciona bien y no tengo ningún interés en otro proyecto, pero tengo usuarios potenciales que quieren usar mi aplicación, pero no pueden mientras dependa de Postgres.
Django 3.1 tiene una versión multiplataforma de este campo —que funcionará para mis necesidades— pero no quiero obligar a todo el mundo a usar Django 3.1; Me gustaría ofrecer a la gente la posibilidad de elegir entre Postgres o Django 3.1+. Sobre el papel, esto es bastante simple con una importación condicional...
try: from django.db.models import JSONField except ImportError: from django.contrib.postgres.fields import JSONField Y si instalé Django 3.1 y generé una migración, podría llevarme de django.contrib.postgres.fields.JSONField a django.db.models.JSONField . Pero...
Esto es peor que cuando empecé. ¿Cómo hago este tipo de migración de campo de una manera que funcione para todos?
Las migraciones son solo código. El hecho de que se generen automáticamente no significa que no deba cambiarlos. Lo alentamos a que, al menos, verifique que se generen correctamente, pero tampoco hay nada de malo en escribirlos usted mismo.
Esto funciona para mí:
Modelo:
from django.db import models try: from django.db.models import JSONField except ImportError: from django.contrib.postgres.fields import JSONField class MyModel(models.Model): stuff = JSONField()Migración:
from django.db import migrations, models try: from django.db.models import JSONField except ImportError: from django.contrib.postgres.fields import JSONField class Migration(migrations.Migration): dependencies = [('testapp', '0001_initial')] operations = [ migrations.CreateModel( name='MyModel', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('stuff', JSONField()), ], ), ]Tenga en cuenta que si necesita cambiar este campo en el futuro, deberá realizar este proceso nuevamente.
Tengo esto del código fuente de Django
from django.db.models import JSONField as BuiltinJSONField class JSONField(BuiltinJSONField): system_check_deprecated_details = { 'msg': ( 'django.contrib.postgres.fields.JSONField is deprecated. Support ' 'for it (except in historical migrations) will be removed in ' 'Django 4.0.' ), 'hint': 'Use django.db.models.JSONField instead.', 'id': 'fields.W904', }
Esto indica que django.contrib.postgres.fields.JSONField quedará en desuso. Además, Django usa django.db.models.JSONField como JSONField especial de postgres.
Aparte de eso, generé el comando SQL usando el comando sqlmigrate y fue como,
BEGIN; -- -- Create model MyModel -- CREATE TABLE "myapp_mymodel" ("id" serial NOT NULL PRIMARY KEY, "stuff" jsonb NOT NULL); COMMIT; Sorprendentemente, tengo el mismo comando SQL usando Django==3.0 y Django==3.1 y en la base de datos, el campo es de tipo jsonb
Esta información concluye que no tiene que preocuparse por esta nueva actualización de JSONField.
No necesita generar un nuevo archivo de migración , pero edite los archivos de migración existentes que tienen una referencia django.contrib.postgres.fields.JSONField con el bloque try...except .
¡¡¡Eso es todo!!!
Le di un punto a Tom Carrick por su respuesta, pero creo que también debería incluir el uso deMeta.required_db_vendor = 'postgres' en Django <3.1
# models.py from django.db import models try: from django.db.models import JSONField postgres_only = False except ImportError: from django.contrib.postgres.fields import JSONField postgres_only = True class MyModel(models.Model): stuff = JSONField() class Meta: if postgres_only: required_db_vendor = 'postgres' # migrations/0001_initial.py from django.db import migrations, models try: from django.db.models import JSONField postgres_only = False except ImportError: from django.contrib.postgres.fields import JSONField postgres_only = True class Migration(migrations.Migration): dependencies = [('testapp', '0001_initial')] operations = [ migrations.CreateModel( name='MyModel', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('stuff', JSONField()), ], options={'required_db_vendor': 'postgres'} if postgres_only else None, ), ]Esto asegurará que los usuarios de su aplicación reutilizable no puedan usarla en una base de datos diferente a PostgreSQL en Django < 3.1 y habilitará sin problemas el soporte en 3.1 sin requerir una migración en los usuarios de PostgreSQL que usen su biblioteca y actualicen a Django 3.1.
Todas las nuevas migraciones se corregirán automáticamente si se utiliza esta solución con un método deconstruct() .
Puede crear un JSONField personalizado compatible que encapsule ambas variantes.
Cree un pequeño archivo fields.py en su aplicación:
try: from django.db.models import JSONField as OrigJSONField except ImportError: from django.contrib.postgres.fields import JSONField as OrigJSONField class JSONField(OrigJSONField): def deconstruct(self): # the original path was 'django.db.models.JSONField' or 'django.contrib.postgres.fields....' name, path, args, kwargs = super().deconstruct() # Substitute 'my_app' by your application name everywhere. path = 'my_app.fields.JSONField' return name, path, args, kwargs Cambie una línea en su models.py :
from my_app.fields import JSONFieldEdite todas las migraciones existentes que usan un JSONField:
import my_app.fields ... ('stuff', my_app.fields.JSONField()),(o equivalente)
from my_app.fields import JSONField ... ('stuff', JSONField()).No se crea ninguna migración después de eso porque makemigrations no encuentra ninguna diferencia.
Todas las futuras makemigrations después de un modelo modificado se crearán automáticamente con my_app.fields.JSONField() . La compatibilidad es un beneficio de esta solución.
Reflexión sobre Django Release Notes 3.1 y futuras:
Describen una actualización simple que requiere crear una nueva migración que solo actualice la ruta de importación, pero no genera SQL, por ejemplo, mediante sqlmigrate . Es más fácil que editar migraciones antiguas.
Tal vez usted también actualice en dos años a Django >= 3.1 solamente y tendrá dos alternativas:
A) Solo se editará la importación en models.py y creará una nueva migración formal casi vacía de manera similar a las notas de la versión. No podrá eliminar la ruta de importación my_app.fields.JSONField porque debe poder importarse de migraciones antiguas de manera similar que Django nunca puede eliminar la ruta django.contrib.postgres.fields.JSONField . El archivo my_app/fields.py podría simplificarse a una línea from django.db.models import JSONField as JSONField después de una actualización incondicional de requisitos.
B) Es posible volver a editar las migraciones antiguas solo al nuevo JSONField, pero no es razonable.
(Es responsabilidad de cada usuario que una edición en un archivo de migraciones no requiera un estado de base de datos modificado y todas las migraciones permanezcan consistentes entre sí. Es por eso que no se puede encontrar mucho al respecto, excepto casos claros).