2010-02-13 6 views
6

Ich habe eine Methode auf meinem Benutzeranmeldeformular, das wie folgt aussieht:Djangokaskade speichern?

def save(self): 
    user = User(
     username = self.cleaned_data['username'], 
     email = self.cleaned_data['email1'], 
     first_name = self.cleaned_data['first_name'], 
     last_name = self.cleaned_data['last_name'], 
    ) 
    user.set_password(self.cleaned_data['password1']) 
    user.profile = Profile(
     primary_phone = self.cleaned_data['phone'], 
    ) 
    user.profile.address = Address(
     country = self.cleaned_data['country'], 
     province = self.cleaned_data['province'], 
     city = self.cleaned_data['city'], 
     postal_code = self.cleaned_data['postal_code'], 
     street1 = self.cleaned_data['street1'], 
     street2 = self.cleaned_data['street2'], 
     street3 = self.cleaned_data['street3'], 
    ) 
    user.save() 
    return user 

Problem ist, wenn ich form.save() nenne es schafft das user Objekt wie erwartet, aber nicht sein Profil oder Adresse speichern. Warum kaskadiert und speichert es alle Untermodelle? Ich vermute, dass ich user.profile.save() und user.profile.address.save() manuell aufrufen könnte, aber ich möchte, dass das Ganze erfolgreich ist oder zusammen versagt. Was ist der beste Weg, dies zu tun?


Aktuelle Lösung:

def save(self): 
    address = Address(
     country = self.cleaned_data['country'], 
     province = self.cleaned_data['province'], 
     city = self.cleaned_data['city'], 
     postal_code = self.cleaned_data['postal_code'], 
     street1 = self.cleaned_data['street1'], 
     street2 = self.cleaned_data['street2'], 
     street3 = self.cleaned_data['street3'], 
    ) 
    address.save() 

    user = User(
     username = self.cleaned_data['username'], 
     email = self.cleaned_data['email1'], 
     first_name = self.cleaned_data['first_name'], 
     last_name = self.cleaned_data['last_name'], 
    ) 
    user.set_password(self.cleaned_data['password1']) 
    user.save() 

    profile = Profile(
     primary_phone = self.cleaned_data['phone'], 
    ) 
    profile.address = address 
    profile.user = user 
    profile.save() 

Ich hatte profile das "zentrale" Objekt zu machen. Benötigt, um profile.user = user statt user.profile = profile zu setzen, damit es funktioniert (ich denke, weil der Schlüssel auf dem Profilmodell ist, nicht auf dem Benutzermodell).


Neuere Lösung:

Ich nahm einen Hauch von this article vorgeschlagen in this answer.

Jetzt habe ich mein Modell Formen getrennt und bewegt die Logik in die Ansicht:

def register(request): 
    if request.POST: 
     account_type_form = forms.AccountTypeForm(request.POST) 
     user_form = forms.UserForm(request.POST) 
     profile_form = forms.ProfileForm(request.POST) 
     address_form = forms.AddressForm(request.POST) 

     if user_form.is_valid() and profile_form.is_valid() and address_form.is_valid(): 
      user = user_form.save() 
      address = address_form.save() 
      profile = profile_form.save(commit=False) 
      profile.user = user 
      profile.address = address 
      profile.save() 
      return HttpResponseRedirect('/thanks/') 
    else: 
     account_type_form = forms.AccountTypeForm() 
     user_form = forms.UserForm() 
     profile_form = forms.ProfileForm() 
     address_form = forms.AddressForm() 

    return render_to_response(
     'register.html', 
     {'account_type_form': account_type_form, 'user_form': user_form, 'address_form': address_form, 'profile_form': profile_form}, 
     context_instance=RequestContext(request) 
    ) 

Ich bin nicht allzu gern die Last auf die Ansicht verschieben, aber ich denke, ich ein bisschen mehr Flexibilität erhalten diese Weg?

+0

Jolly gute Show! – jathanism

Antwort

5

Es spielt keine Kaskade sparen, weil es, ob die anderen Objekte eigentlich nicht wissen Notwendigkeit zu speichern.

, es zu tun in einem Rutsch, erste start a transaction:

@transaction.commit_on_success 
def save(self): 
    .... 

dann die Teilobjekte, um sparen:

user.profile.address.save() 
    user.profile.save() 
    user.save() 
+0

Warum kann es nicht herausfinden, ob sie gespeichert werden müssen?Sie haben noch nicht einmal 'id's ... es ist ein einfacher Check. Ist es wirklich notwendig, Transaktionen für etwas so Einfaches zu verwenden? Ich hatte alle möglichen Probleme mit Transaktionen, die andere Fehler maskierten. – mpen

+0

Das Festlegen einer PK reicht nicht aus, um zu bestimmen, dass ein Objekt nicht gespeichert werden soll. Entweder wird der PK erzwungen oder das Objekt muss aktualisiert werden, wobei beide einen Aufruf von 'save() 'erfordern. –

+0

Oh ... und das größere Problem ist, dass 'profile.user_id' nicht null sein kann. 'profile.user_id' ist nie gesetzt, obwohl' profile' ein Attribut von 'user' ist ... – mpen

1

Das Problem ist, dass Sie versuchen, in einem zu erstellen oder zu aktualisieren Felder Benutzerobjekt, das noch nicht einmal existiert. Die anderen Felder werden also nicht wirklich aktualisiert, da sie keinen Primärschlüsseln der untergeordneten Felder zugeordnet sind.

Jedes Mal, wenn Sie ein neues Modellfeld instanziieren, müssen Sie sicherstellen, dass Sie speichern, damit ein untergeordnetes Modellfeld über eine ID (Primärschlüssel) verfügt.

Sie brauchen mehr etwas wie folgt aus:

def save(self): 
    user = User(
     username = self.cleaned_data['username'], 
     email = self.cleaned_data['email1'], 
     first_name = self.cleaned_data['first_name'], 
     last_name = self.cleaned_data['last_name'], 
    ) 
    ## save user so we get an id 
    user.save() 

    ## make sure we have a user.id 
    if user.id: 
     ## this doesn't save the password, just updates the working instance 
     user.set_password(self.cleaned_data['password1']) 
     user.profile = Profile(
      primary_phone = self.cleaned_data['phone'], 
     ) 
     ## save the profile so we get an id 
     user.profile.save() 

    ## make sure we have a profile.id 
    if user.profile.id: 
     user.profile.address = Address(
      country = self.cleaned_data['country'], 
      province = self.cleaned_data['province'], 
      city = self.cleaned_data['city'], 
      postal_code = self.cleaned_data['postal_code'], 
      street1 = self.cleaned_data['street1'], 
      street2 = self.cleaned_data['street2'], 
      street3 = self.cleaned_data['street3'], 
     ) 
     ## save the profile address 
     user.profile.address.save() 

    ## final save to commit password and profile changes 
    user.save() 
    return user 

Diese Kaskadierung save(), was Sie hier haben geht einfach nicht richtig anfühlt. Sie sind anfällig für viel zu viele Fehler dort, wenn eines der Felder nicht speichern Sie am Ende mit einer teilweise vollständigen Benutzerinstanz und am Ende mit Doppelendigkeiten, wenn der Benutzer muss zurück gehen und es erneut versuchen. Kein Spaß!

Bearbeiten: Die zweite Hälfte davon entfernt, weil es nicht genau war.

+0

Es sieht besser aus, weil Sie die Interna des UserFormSet nicht gepostet haben, Sie haben die View-Methode gepostet, die mehr oder weniger identisch mit dem aussieht, was ich jetzt habe. Ich muss tiefer in Formsets schauen, um zu sehen, ob sie die richtige Wahl sind. Ich habe immer gedacht, dass sie zum Erstellen vieler Objekte auf einmal verwendet werden, nicht viele Formen zum Erstellen eines Objekts. Meine kaskadierende Idee zielte darauf ab, Duplikate zu verhindern, indem man alles zusammen speichert oder versagt, und daher nur einen Aufruf von 'save()' hat. – mpen

+0

Ja, da hast du Recht. Ich weiß nicht, was ich dachte! Ich werde diese zweite Hälfte aus der Antwort entfernen. – jathanism

Verwandte Themen