Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I send signals from within Django migrations?

I use Django 1.7 migrations, and in particular, want to populate a newly-created database with initial data. Thus, I use a data migration for this. It looks like this:

def populate_with_initial_data(apps, schema_editor):
    User = apps.get_model("auth", "User")
    new_user = User.objects.create(username="nobody")

class Migration(migrations.Migration):

    ...

    operations = [
        migrations.RunPython(populate_with_initial_data),
    ]

At the same time, I want to have an instance of the UserDetails model for every new user:

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

But: This signal works only outside the migration. The reason is that apps.get_model("auth", "User") is different enough from django.contrib.auth.models.User that no signal is sent. If I try to do it manually, like this, it fails:

signals.post_save.send(django.contrib.auth.models.User, instance=julia, created=True)

This fails because then, the signal handler tries to create a new UserDetails pointing with O2O to a historical User:

ValueError: Cannot assign "<User: User object>": "UserDetails.user" must be a "User" instance.

Bummer.

Okay, I could call the signal handler directly. But I had to pass the historical UserDetails class in a keyword argument (and other historical classes that it needs). Besides, the app with the UserDetails is not the one with this data migration, so this would be an ugly dependency which easily may break, e.g. if the UserDetails app is removed from INSTALLED_APPS.

So, is this simply a current limitation that I have to address with ugly code and a FixMe comment? Or is there a way to send signals out of data migrations?

like image 760
Torsten Bronger Avatar asked Oct 24 '14 06:10

Torsten Bronger


People also ask

Where is Pre_save signal in Django?

pre_save. This is sent at the beginning of a model's save() method. Arguments sent with this signal: sender.

Which of the following are inbuilt signals in Django?

Using Django built-in (pre_save, post_save, pre_delete, post_delete, receiver) and custom signals. - MicroPyramid.


1 Answers

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.

like image 122
DylannCordel Avatar answered Nov 20 '22 05:11

DylannCordel