2014-10-24 7 views
9

Ich verwende Django 1.7-Migrationen und möchte insbesondere eine neu erstellte Datenbank mit Anfangsdaten füllen. Daher verwende ich hierfür eine Datenmigration. Es sieht wie folgt aus:Wie kann ich Signale von Django-Migrationen senden?

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), 
    ] 

Zur gleichen Zeit habe ich eine Instanz des UserDetails Modell für jeden neuen Benutzer haben wollen:

@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) 

Aber: Dieses Signal funktioniert nur außerhalb der Migration . Der Grund ist, dass apps.get_model("auth", "User") sich von django.contrib.auth.models.User unterscheidet, dass kein Signal gesendet wird. Wenn ich versuche, es manuell zu tun, wie diese, versagt es:

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

Dies schlägt fehl, da dann versucht der Signal-Handler ein neueUserDetails zeigt mit O2O zu einem historischenUser zu erstellen:

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

Bummer.

Okay, ich könnte den Signal-Handler direkt aufrufen. Aber ich musste die historische Klasse in einem Schlüsselwortargument (und anderen historischen Klassen, die es benötigt) übergeben. Außerdem ist die App mit der nicht die mit dieser Datenmigration, so dass dies eine hässliche Abhängigkeit wäre, die leicht brechen kann, z. wenn die App aus INSTALLED_APPS entfernt wird.

Also, ist das einfach eine Strombegrenzung, die ich mit hässlichem Code und einem FixMe-Kommentar ansprechen muss? Oder gibt es eine Möglichkeit, Signale aus Datenmigrationen zu senden?

+0

Haben Sie eine Abhilfe gefunden? –

+2

Ja, mit signal.post_migrate, weil * dies * aufgerufen wird. Aber es braucht immer noch Code, der nicht notwendig sein sollte. –

+0

Sie sollten die Antwort posten und Ihre eigene Antwort akzeptieren, da diese Frage an der Spitze der unbeantworteten Django-Fragen steht. – dotcomly

Antwort

3

Sie können dies nicht tun (und sollten dies auch nicht), da Ihre bei der Ausführung der Migration wirklich anders sein kann als beim Schreiben dieser Migration. Dies ist der Grund, warum Django (und Süden) "eingefrorene Modelle" verwendet, die mit denen identisch sind, als Sie die Migration geschrieben haben.

"Leider", Sie müssen Ihren Signalcode in Ihrer Migration einfrieren, um das Verhalten zu dem Zeitpunkt, an dem Sie die Migration schreiben, beizubehalten.

Eine einfache exemple zu verstehen, warum es wichtig ist, nicht reale Modelle (oder Signale etc.) innerhalb einer Migration zu verwenden:

Heute konnte ich dieses haben:

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') 

Dann habe ich eine haben Datenmigration (genannt "populate_users"), die neue Benutzer erstellen und erzwinge die Ausführung von add_user_details in ihm. Es ist in Ordnung: es funktioniert heute.

Morgen fixiere ich meine typo_fild ->typo_field innerhalb UserDetails und innerhalb add_user_details. Eine neue Schemamigration wird erstellt, um das Feld in der Datenbank umzubenennen.

An dieser Stelle meiner Migration „populate_users“ werden scheitern, weil, wenn ein neuer Benutzer angelegt wird, wird versuchen, es existiert in der Datenbank noch nicht ein neues UserDetails mit einem Feld „typo_field“ Weicht zu erstellen: Diese Das Feld wird bei den nächsten Migrationen nur im DB umbenannt.

Also, wenn ich eine gute Migration behalten will, die jederzeit funktioniert, muss ich das Verhalten von add_user_details innerhalb der Migration kopieren. Dieses Einfrieren von add_user_details muss das gefrorene Modell UserDetails über apps.get_model("myapp", "UserDetails") verwenden und ein neues UserDetails mit dem typo_fild erstellen, das auch eingefroren ist.

+1

Sie können immer in Ihren Fuß schießen, wenn Sie es wünschen. Schließlich werden andere Signale * gesendet, und ich benutze sie jetzt, um dieses Problem zu lösen. Wenn Sie problematische Änderungen am Modell vornehmen, können Sie die Introspektion im Signalhandler verwenden. "Machen Sie den häufigen Fall einfach und den seltenen Fall möglich." –