Django Migrations

FileSystemStorage

Django makemigrations auto-generates the location for a FileField as follows:

migrations.AlterField(
    model_name="awards",
    name="certificate",
    field=models.FileField(
        blank=True,
        storage=django.core.files.storage.FileSystemStorage(
            location=pathlib.PurePosixPath(
                "/home/patrick/dev/project/hatherleigh_info/media-private"
            )
        ),
        upload_to="awards/certificate/user",
    ),
),

The location appears to do nothing (when used with our private_file_store. For details, see private_file_store)

To solve the issue (until we know better) use the following for the storage parameter:

storage=django.core.files.storage.FileSystemStorage(),

Swappable Dependency

To use a swappable dependency in a migration, e.g. CONTACT_MODEL:

from django.conf import settings

dependencies = [
    migrations.swappable_dependency(settings.CONTACT_MODEL),
]

operations = [
    migrations.CreateModel(
        name='Candidate',
        fields=[
            ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
            # ...
            # replace:
            # ('contact', models.OneToOneField(to='example_job.Contact')),
            # with:
            ('contact', models.OneToOneField(to=settings.CONTACT_MODEL)),
        ],
        # ...
    ),

Workflow

Create an automatic migration:

django-admin.py makemigrations yourappname

Create a data migration:

django-admin.py makemigrations --empty yourappname

Run:

django-admin.py migrate

Default Value for Foreign Keys

To set-up default states for foreign keys…

Create a default function e.g:

def default_payment_state():
    return PaymentState.objects.get(slug=PaymentState.DUE).pk

Warning

This function must return an integer (the primary key) or it won’t work with migrations.

Then… follow one of two strategies…

1) Create All Models

Create all the models without defaults - then add the defaults later.

Create your models and allow the foreign key to be set to null e.g:

class Payment(TimeStampedModel):
    state = models.ForeignKey(
        PaymentState,
        #default=default_payment_state,
        blank=True,
        null=True
    )

Create the migrations for all your models

Create a data migration and use it to set-up the defaults for your state model e.g:

def _init_state(model, slug, name):
    try:
        model.objects.get(slug=slug)
    except model.DoesNotExist:
        instance = model(**dict(name=name, slug=slug))
        instance.save()
        instance.full_clean()

def default_state(apps, schema_editor):
    state = apps.get_model('pay', 'PaymentState')
    _init_state(state, 'approved', 'Approved')
    _init_state(state, 'pending', 'Pending')
    _init_state(state, 'rejected', 'Rejected')

class Migration(migrations.Migration):

    dependencies = [
    ]

    operations = [
        migrations.RunPython(default_state),
    ]

Set the foreign key so it has a default and no longer accepts null e.g:

class Payment(TimeStampedModel):
    state = models.ForeignKey(
        PaymentState,
        default=default_payment_state,
        #blank=True,
        #null=True
    )

Update the migrations so the default value is set.

2) Lookup Model First

Create the lookup model - then add the dependant models later

This strategy is simple and logical, but isn’t suitable if you are moving from South and creating the first migration. To move from South, all current models need to be in the 0001_initial.py file.

Create the model which will contain the default value (don’t create the model which depends on it) e.g:

class PaymentState(TimeStampedModel):
    DUE = 'due'
    name = models.CharField(max_length=100)
    slug = models.SlugField(unique=True)

Create migrations for this model

Create a data migration and use it to set-up the defaults for your state model (e.g. django_migrations_defaults from the example above).

Create the model which uses the foreign key e.g:

class Payment(TimeStampedModel):
    state = models.ForeignKey(PaymentState, default=default_payment_state)

Create the migration for this model

Remove an app from a project

Note

I can’t find a good way to do this. Most recently I used Method 2.

Method #1

Removing an app from a project does not remove the tables from the database automatically. To remove the tables use the command:

django-admin migrate <app> zero

If the app has already been removed and you removed the tables manually, the migration tables may be out of step with the database which will cause an issue if you want to reintroduce the app. To fix this use the command:

django-admin migrate <app> zero --fake

Method #2

Warning

Do NOT drop the table unless you are sure you don’t need the data!

If you can’t get the above working, then you can just delete the app, then run the following to remove the migrations and drop the tables e.g:

delete from django_migrations where app = 'report';
drop table report_reportdata;
drop table report_report;