Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditional Django migration based on a field only present in new version

My app that currently depends on Postgres and Django's Postgres-only JSONField. The field works well and I've no interest in another project, but I have prospective-users who want to use my app, but can't while it relies on Postgres.

Django 3.1 has a cross-platform version of this field —which will work for my needs— but I don't want to force everybody up to Django 3.1; I would like to offer people a choice between Postgres or Django 3.1+. On paper, this is simple enough with a conditional import...

try:
    from django.db.models import JSONField
except ImportError:
    from django.contrib.postgres.fields import JSONField

And if I installed Django 3.1 and generated a migration, it could take me from django.contrib.postgres.fields.JSONField to django.db.models.JSONField. But...

  • New users will still execute the initial migration. I will still have a dependency on Postgres.
  • Sub-Django 3.1 users won't be able to execute the new migration. I now have a dependency on Django 3.1.

This is worse than when I started. How do I do this sort of field migration in a way that will work for everybody?

like image 724
Oli Avatar asked Jul 06 '20 07:07

Oli


People also ask

How do you add a new field to a model with new Django migrations?

To answer your question, with the new migration introduced in Django 1.7, in order to add a new field to a model you can simply add that field to your model and initialize migrations with ./manage.py makemigrations and then run ./manage.py migrate and the new field will be added to your DB. Save this answer.

How do I fix migration issues in Django?

you can either: temporarily remove your migration, execute python manage.py migrate, add again your migration and re-execute python manage.py migrate. Use this case if the migrations refer to different models and you want different migration files for each one of them.

What is the difference between Makemigrations and migrate in Django?

migrate , which is responsible for applying and unapplying migrations. makemigrations , which is responsible for creating new migrations based on the changes you have made to your models.


1 Answers

All new migrations will be correct automatically if this solution with a deconstruct() method will be used.

You can create a compatible custom JSONField that encapsulates both variants.

Create a small file fields.py in your application:

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

Change a line in your models.py:

from my_app.fields import JSONField

Edit all existing migrations that use a JSONField:

import my_app.fields
    ...
        ('stuff', my_app.fields.JSONField()),

  (or equivalently)

from my_app.fields import JSONField
    ...
        ('stuff', JSONField()).

No migration is created after that because no difference is found by makemigrations.

All future makemigrations after a changed model will be created automatically with my_app.fields.JSONField(). Compatibility is a benefit of this solution.


Reflection about Django Release Notes 3.1 and future:

They describe a plain upgrade that requires to create a new migration that only upgrades the import path, but it generates no SQL e.g. by sqlmigrate. It is easier than to edit old migrations.

Maybe you you also upgrade in two years to Django >= 3.1 only and you will have two alternatives:

A) Only the import in models.py will be edited and you create a new nearly empty formal migration similarly to release notes. You will be not able to remove the import path my_app.fields.JSONField because it must be importable from old migrations similarly that Django can never remove the path django.contrib.postgres.fields.JSONField. The file my_app/fields.py could be simplified to one line from django.db.models import JSONField as JSONField after an unconditional upgrade of requirements.

B) Editing old migrations again to only the new JSONField is possible, but unreasonable.

(It is every user's responsibility that an edit in a migrations file doesn't require a changed database state and all migrations remain consistent each other. That is why not much about it can be found, except such clear cases.)

like image 131
hynekcer Avatar answered Nov 06 '22 06:11

hynekcer