Database migrations =================== We use `Django migrations `__ to manage database schema changes, and the `django-safemigrate `__ package to ensure that migrations are run in a given order to avoid downtime. To make sure that migrations don't cause downtime, the following rules should be followed for each case. Adding a new field ------------------ **When adding a new field to a model, it should be nullable.** This way, the database can be migrated without downtime, and the field can be populated later. Don't forget to make the field non-nullable in a separate migration after the data has been populated. You can achieve this by following these steps: #. Set the new field as ``null=True`` and ``blank=True`` in the model. .. code-block:: python class MyModel(models.Model): new_field = models.CharField( max_length=100, null=True, blank=True, default="default" ) #. Make sure that the field is always populated with a proper value in the new code, and the code handles the case where the field is null. .. code-block:: python if my_model.new_field in [None, "default"]: pass # If it's a boolean field, make sure that the null option is removed from the form. class MyModelForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["new_field"].widget = forms.CheckboxInput() self.fields["new_field"].empty_value = False #. Create the migration file (let's call this migration ``app 0001``), and mark it as ``Safe.before_deploy()``. .. code-block:: python from django.db import migrations, models from django_safemigrate import Safe class Migration(migrations.Migration): safe = Safe.before_deploy() #. Create a data migration to populate all null values of the new field with a proper value (let's call this migration ``app 0002``), and mark it as ``Safe.after_deploy()``. .. code-block:: python from django.db import migrations def migrate(apps, schema_editor): MyModel = apps.get_model("app", "MyModel") MyModel.objects.filter(new_field=None).update(new_field="default") class Migration(migrations.Migration): safe = Safe.after_deploy() operations = [ migrations.RunPython(migrate), ] #. After the deploy has been completed, create a new migration to set the field as non-nullable (let's call this migration ``app 0003``). Run this migration on a new deploy, you can mark it as ``Safe.before_deploy()`` or ``Safe.always()``. #. Remove any handling of the null case from the code. At the end, the deploy should look like this: - Deploy web-extra. - Run ``django-admin safemigrate`` to run the migration ``app 0001``. - Deploy the webs - Run ``django-admin migrate`` to run the migration ``app 0002``. - Create a new migration to set the field as non-nullable, and apply it on the next deploy. Removing a field ---------------- **When removing a field from a model, all usages of the field should be removed from the code before the field is removed from the model, and the field should be nullable.** You can achieve this by following these steps: #. Remove all usages of the field from the code. #. Set the field as ``null=True`` and ``blank=True`` in the model. .. code-block:: python class MyModel(models.Model): field_to_delete = models.CharField(max_length=100, null=True, blank=True) #. Create the migration file (let's call this migration ``app 0001``), and mark it as ``Safe.before_deploy()``. .. code-block:: python from django.db import migrations, models from django_safemigrate import Safe class Migration(migrations.Migration): safe = Safe.before_deploy() #. Create a migration to remove the field from the database (let's call this migration ``app 0002``), and mark it as ``Safe.after_deploy()``. .. code-block:: python from django.db import migrations, models from django_safemigrate import Safe class Migration(migrations.Migration): safe = Safe.after_deploy() At the end, the deploy should look like this: - Deploy web-extra. - Run ``django-admin safemigrate`` to run the migration ``app 0001``. - Deploy the webs - Run ``django-admin migrate`` to run the migration ``app 0002``.