[Fixed]-How can I send signals from within Django migrations?

13👍

You can’t (and should not) do this because when your migration is executed, your UserDetails could be really different than when you wrote this migration.
This is why django (and south) use “frozen models” which are identical to when you wrote the migration.

“Unfortunately”, you have to freeze your signal code in your migration to keep the behaviour expected at the time you write the migration.

A simple exemple to understand why it’s important to not use real models (or signals etc.) inside a migration :

Today, I could have this :

class UserDetails(models.Model):
    user = models.ForeignKey(...)
    typo_fild = models.CharField(...)

@receiver(signals.post_save, sender=django.contrib.auth.models.User)
def add_user_details(sender, instance, created, **kwargs):
    if created:
        UserDetails.objects.create(user=instance, typo_fild='yo')

Then, I have a data migration (called “populate_users”) which create new users and I force the execution of add_user_details inside it. It’s okay : it works today.

Tomorrow, I fix my typo_fild -> typo_field inside UserDetails and inside add_user_details. A new schema migration is created to rename the field in the database.

At this point, my migration “populate_users” will fail because when a new user will be created, it will try to create a new UserDetails with a field “typo_field” wich does not yet exist in the database : this field will only be rename in the DB with the next migrations.

So, if I want to keep a good migration wich will work at anytime, I have to copy the behaviour of add_user_details inside the migration. This freeze of add_user_details will have to use the frozen model UserDetails via apps.get_model("myapp", "UserDetails") and create a new UserDetails with the typo_fild which is frozen too.

0👍

Another option is to simply import the model as you normally would inside your populate_with_initial_data function:

def populate_with_initial_data(apps, schema_editor):
    from django.auth.models import User
    new_user = User.objects.create(username="nobody")

This has the significant drawback that the migration might break if you try to run it at at later time and the database schema has changed in a significant way since. In the case your are mentioning that seems unlikely but that must be assessed on a case by case basis.

We have several models where there are so many things happening on save() and in signals that we feel non frozen imports is the best option. In practice it’s not a big problem (for us) and to mitigate the problem further we regularly prune all our migrations.

Leave a comment