2009-03-07 5 views
8

Eine weitere Django-Formfrage.Weitere Django-Formulare: Fremdschlüssel im versteckten Feld

Meine Form:

class PlanForm(forms.ModelForm):  
    owner = forms.ModelChoiceField(label="", 
            queryset=Profile.objects.all(), 
            widget=forms.HiddenInput()) 
    etc... 

    class Meta: 
     model = Plan 

Besitzer, in dem Modell ist ein ForeignKey zu einem Profil.

Wenn ich dieses Formular einstelle, setze ich den Wert von "Besitzer" als ein Profil-Objekt.

Aber wenn dies auf dem Formular herauskommt, so scheint es, den Namen des Profils wie folgt enthalten:

<input type="hidden" name="owner" value="phil" id="id_owner" /> 

Wenn das Formular abgeschickt wird, und wird zurück zu meinem views.py Ich versuche, handhaben es so:

form = PlanForm(request.POST) 
    ... 
    if form.is_valid():     
     plan = form.save() 
     return HttpResponseRedirect('/plans/%s'%plan.id) # Redirect after POST 

Doch was ich bekommen, ist eine Typkonvertierungsfehler, da es die Zeichenfolge „phil“, um nicht (der Name des Benutzers, der in den „Besitzer“ Feld gespeichert wurde) in eine Int um es in den ForeignKey zu verwandeln.

Also was ist hier los. Sollte ein ModelForm einen Fremdschlüssel als Nummer darstellen und transparent behandeln? Oder muss ich die ID selbst in das Besitzerfeld des Formulars extrahieren? Und wenn ja, wie und wann ordne ich es zurück, BEVOR ich versuche, das Formular zu validieren?

+0

wie werden Sie den Wert Besitzer Einstellung, wenn Sie das Formular festgelegt. Können wir das sehen? – defrex

Antwort

15

ich nehme an, daß die __unicode__ Methode zur Profilmodellinstanz oder die repr davon ist auf einen anderen Wert als self.id zurückzukehren. Zum Beispiel habe ich dies nur ein:

# models.py 
class Profile(models.Model): 
    name = models.CharField('profile name', max_length=10) 

    def __unicode__(self): 
     return u'%d' % self.id 

class Plan(models.Model): 
    name = models.CharField('plan name', max_length=10) 
    profile = models.ForeignKey(Profile, related_name='profiles') 

    def __unicode__(self): 
     return self.name 


# forms.py 
class PlanForm(forms.ModelForm): 
    profile = forms.ModelChoiceField(queryset=Profile.objects.all(), 
      widget=forms.HiddenInput()) 

    class Meta: 
     model = Plan 

# views.py 
def add_plan(request): 

    if request.method == 'POST': 
     return HttpResponse(request.POST['profile']) 


    profile = Profile.objects.all()[0] 
    form = PlanForm(initial={'profile':profile}) 
    return render_to_response('add_plan.html', 
      { 
       'form':form, 
      }, 
      context_instance=RequestContext(request)) 

Damit sehe ich PlanForm.profile so in der Vorlage gemacht:

<input type="hidden" name="profile" value="1" id="id_profile" /> 
1

Normalerweise ist es nicht erforderlich, verwandte Objekte in das Formularfeld zu stellen. Es gibt einen besseren Weg und dies ist die Angabe der Eltern-ID in der Formular-URL.

Angenommen, Sie müssen ein Formular für ein neues Plan-Objekt rendern und dann ein Formular erstellen, wenn das Formular übertragen wird. Hier ist, wie Sie Ihre URLconf aussehen würde:

(r"/profile/(?P<profile_id>\d+)/plan/new", view.new_plan), # uses profile_id to define proper form action 
(r"/profile/(?P<profile_id>\d+)/plan/create", view.create_plan) # uses profile_id as a Plan field 

Und wenn Sie ändern vorhandene Objekt, alles, was Sie brauchen, ist plan_id, können Sie einen beliebigen verknüpften Datensatz daraus abzuleiten.

+0

das ist ein interessanter Ansatz. Es ist nicht richtig für mich in diesem Fall, weil Pläne nicht konzeptionell "unter" Eigentümerprofilen sind (und ich kann viele andere verwandte Objekte haben), aber ich kann es in einigen Fällen funktionieren sehen. – interstar

+0

abgeordnet. Dies ist der Ansatz, den ich am liebsten auch selbst verwende. – ayaz

+0

Entlang der gleichen Linien könnte der URLConf (r 'Plan/create', view.plan.create), sein und das Profil und/oder andere Planinformationen als GET übergeben. Ihre Sicht prüft auf GET-Parameter und füllt das Formular mit ihnen aus. Die Planerstellung ist immer noch idempotent, da die GET-Anfrage keinen neuen Plan erstellt, sondern nur das Formular sät. –

6

Wenn Sie dem Formular ein Profilobjekt zuweisen, schreibt Django es und verwendet die Ausgabe als Wert im Formular. Was Sie jedoch vermeiden würden, ist, dass Django stattdessen die ID des Objekts verwendet.

Zum Glück ist die Abhilfe einfach: einfach dem Formular Primärschlüsselwert gibt das Profil stattdessen Objekte:

form = PlanForm(initial={'profile': profile.pk}) 

Am anderen Ende, wenn Sie mit gebundenen Formen arbeiten, aber sie arbeiten viel sinnvoller:

form = PlanForm(request.POST) 
if form.is_valid(): 
    print form.cleaned_data['profile'] # the appropriate Profile object 
+1

OK ... das hilft. Seltsamerweise müssen Sie die .pk-Datei explizit beim Erstellen des Formulars verwenden, aber nicht erneut referenzieren, wenn Sie – interstar

9

Hmm ...

Dies könnte tatsächlich sein, Sicherheitsloch.

Angenommen, ein böswilliger Angreifer hat einen POST erstellt (z. B. mithilfe von XmlHttpRequest von FireBug) und den Profilterm auf einen verrückten Wert gesetzt, z. B. Ihre Profil-ID. Wahrscheinlich nicht was du wolltest?

Wenn möglich, möchten Sie das Profil möglicherweise aus dem Anforderungsobjekt selbst und nicht aus den POST-Werten erhalten.

form = PlanForm(request.POST) 
if form.is_valid(): 
    plan = form.save(commit=False) 
    plan.owner = request.user.get_profile() 
    plan.save() 
    form.save_m2m() # if neccesary 
+0

+1 lesen. POST-Daten können einfach temperiert werden. Aber warum referenzieren Sie das Profil, wenn wir auf den Benutzer verweisen können? – muhuk

+1

Der 'commit = False' Trick rettete meinen Tag, danke. – NiKo

2

Seit ModelChoiceField von ChoiceFIeld erbt, sollten Sie das MultipleHiddenInput Widget für diesen Einsatz:

class PlanForm(forms.ModelForm):  
    owner = forms.ModelChoiceField(
      queryset=Profile.objects.all(), 
      widget=forms.MultipleHiddenInput()) 

    class Meta: 
    model = Plan