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;