Django表单:隐藏字段中的外键

时间:2009-03-07 02:59:09

标签: python django forms

我的表格:

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

    class Meta:
        model = Plan

模型中的所有者是个人资料的ForeignKey。

当我设置此表单时,我将“owner”的值设置为Profile对象。

但是当它出现在表单上时,它似乎包含了配置文件的名称,如下所示:

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

当提交表单并返回我的views.py时,我尝试像这样处理它:

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

但是,我得到的是类型转换错误,因为它无法将字符串“phil”(保存到“owner”字段中的用户名称)转换为Int以将其转换为ForeignKey。

那么这里发生了什么。 ModelForm是否应将外键表示为数字并透明地处理它?或者我是否需要将自己的ID提取到表单的所有者字段中?如果是这样,在我尝试验证表单之前,如何以及何时将其映射回去?

5 个答案:

答案 0 :(得分:18)

我怀疑Profile模型实例的__unicode__方法或其repr设置为返回self.id以外的值。例如,我只是设置了它:

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

有了这个,我看到在模板中呈现了PlanForm.profile:

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

答案 1 :(得分:11)

嗯...

这实际上可能是一个安全漏洞。

假设恶意攻击者制作了一个POST(例如,通过使用FireBug中的XmlHttpRequest)并将配置文件术语设置为某个古怪的值,例如您的个人资料ID。可能不是你想要的?

如果可能,您可能希望从请求对象本身获取配置文件,而不是从POST值提交的配置文件。

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

答案 2 :(得分:8)

当您将Profile对象分配给表单时,Django会对其进行字符串化并将输出用作表单中的值。你会期望的是Django使用对象的ID代替。

幸运的是,解决方法很简单:只需给出Profile对象的表单主键值:

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

另一方面,当你使用绑定表格时,他们的工作更明智:

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

答案 3 :(得分:2)

由于ModelChoiceField继承自ChoiceFIeld,因此您应该使用MultipleHiddenInput小部件:

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

  class Meta:
    model = Plan

答案 4 :(得分:1)

通常不需要将相关对象放入表单字段中。有一种更好的方法,这是在表单URL中指定父ID。

假设您需要为新的Plan对象呈现一个表单,然后在表单被填充时创建一个表单。这是你的urlconf的样子:

(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

如果您要更改现有对象,您只需要plan_id,就可以从中推断出任何相关记录。