【问题标题】:django inline form with custom forms带有自定义表单的 django 内联表单
【发布时间】:2011-07-07 11:32:49
【问题描述】:

嗨 我有一个在 Django 应用程序中使用的域模型,我想在一个表单上呈现它。我已经使用自定义 ModelForms 创建了我的应用程序(没有太大变化,排除了一些字段等)。模型的依赖如下:

Complaint
   \
    .--- CarInfo
    .--- Customer

我的视图函数如下所示:

def make(request):
  if request.method == 'POST':
    parameters = copy.copy(request.POST)
    complaint = Complaint()
    carInfo = CarInfo()
    customer = Customer()

    customer_form = CustomerForm(parameters, instance=customer)
    carInfo_form = CarInfoForm(parameters, instance=carInfo)
    parameters['complaint_date'] = get_current_date()
    parameters['customer'] = 1 # dummy value to allow validation success
    parameters['car_info'] = 1 # dummy value to allow validation success
    form = ComplaintForm(parameters, instance=complaint)
    if form.is_valid() and customer_form.is_valid() and carInfo_form.is_valid():
      carInfo_form.save()
      customer_form.save()
      parameters['customer'] = customer.id
      parameters['car_info'] = carInfo.id
      form = ComplaintForm(parameters, instance=complaint)
      form.save()
      return index(request)
  else:
    form = ComplaintForm()
    carInfo_form = CarInfoForm()
    customer_form = CustomerForm()
  return render_to_response('complaints/make_complaint.html', {'complaint_form' : form, 'customer_form' : customer_form, 'carInfo' : carInfo_form})

我不太喜欢这种方法,而且它并不适用于所有环境——我还没有找到它不起作用的原因。我一直在研究修复这段代码,发现类似 inline formset (http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#inline-formsets) .这个解决方案看起来不错,但由于我的表单是定制的,我可以使用它。

也许有人可以就如何正确解决这种情况给我一些建议。非常感谢更清洁的解决方案。

已编辑 对我来说有一个案例,这个解决方案不起作用。尽管在外键上设置了虚拟值,但当我调用 is_valid() 时,我得到 FALSE,错误消息指出这些字段未设置。我正在观察 django 1.2.5 的这个问题 - 它发生在我打算运行这个应用程序的服务器上,但是我的笔记本电脑(也是 django 1.2.5)没有这个问题。

【问题讨论】:

  • 你能发布你的模型类和表单吗?

标签: django django-forms


【解决方案1】:

我认为您已经有了最简洁的方法,但是如果您想使用表单集,请尝试以下链接:

编辑。我想您可能会因为这些虚拟值而遇到问题(并且修改 request.POST,我可以继续猜测:),但@kriegar 展示了如何避免这种情况。无论如何,在一个视图中保存多个表单并不难; Django Forms 很好地支持这种情况。我的观点是,明确地这样做是最干净和最简单的方法,formset 不会对情况有太大​​改善。

【讨论】:

  • 既然你说这完全没问题,也许你知道奇怪行为的原因吗? - 我在我的问题中添加了一些注释(查看已编辑的内容)
【解决方案2】:

您可以将您的投诉模型的投诉日期更改为类似的内容

complaint_date = models.DateField(default=datetime.date.today())

这样你就可以摆脱

parameters['complaint_date'] = get_current_date()

对于您的多表单视图,您可以使用未绑定的表单来实现所需的行为。

通过在投诉表格中排除汽车和客户的 fk,表格应有效。同时检查所有 3 个表单的 .is_valid(),然后保存您的投诉对象所依赖的 2 个表单,创建没有提交到数据库的投诉对象 (commit=False),添加客户的 id 和汽车到那个对象,然后保存。

在你看来。

def make(request):
    if request.method == 'POST':
        customer_form = CustomerForm(request.POST)
        carInfo_form = CarInfoForm(request.POST)
        form = ComplaintForm(request.POST)

        if form.is_valid() and customer_form.is_valid() and carInfo_form.is_valid():
            car_instance = carInfo_form.save()
            customer_instance = customer_form.save()

            complaint_instance = form.save(commit=False)
            complaint_instance.car_info = car_instance
            complaint_instance.customer_info = customer_instance          
            complaint_instance.save()

            return index(request)
    else:
        form = ComplaintForm()
        carInfo_form = CarInfoForm()
        customer_form = CustomerForm()

    context = { 'complaint_form' : form,
                'customer_form' : customer_form, 
                'carInfo' : carInfo_form,
              }
    return render_to_response('complaints/make_complaint.html', context, context_instance=RequestContext(request))

编辑:

模型如下所示:

class CarInfo(models.Model):
    some_car_info = models.CharField()

class Customer(models.Model):
    some_customer_info = models.CharField()

class Complaint(models.Model):
    car_info = models.ForeignKey(CarInfo)
    customer_info = models.ForeignKey(Customer)
    some_complaint_info = models.CharField()

forms.py 应如下所示:

class CarInfoForm(forms.ModelForm):
    class Meta:
        model = CarInfo

class CustomerForm(forms.ModelForm):
    class Meta:
        model = Customer

class ComplaintForm(forms.ModelForm):
    class Meta:
        model = Complaint
        exclude = ('car_info', 'customer_info',) # or include = ('some_complaint_info',)

让我们看看我上面写的视图: form in view docs

  • 在第一次通过时,没有 request.method,因此我们创建了 3 个unbound 表单。

    else:
        form = ComplaintForm()
        carInfo_form = CarInfoForm()
        customer_form = CustomerForm()
    
  • 这些表单被传递给模板并被渲染。

  • 当使用 request.method == "POST" 评估为 true 再次调用视图时,我们会使用 request.POST 中的数据创建 3 个绑定的表单实例。

    if request.method == 'POST':
        customer_form = CustomerForm(request.POST)
        carInfo_form = CarInfoForm(request.POST)
        form = ComplaintForm(request.POST)
    
  • 接下来,我们在每个 form 上调用 .is_valid() 方法。在我们的示例中,因为我们在投诉模型表单中排除了“customer_info”和“car_info”外键字段,所以每个表单仅检查 char 输入字段是否有效。

  • 如果验证全部通过,那么我们可以开始将表单保存到模型中,在此我们需要小心填充我们的投诉所需的 fk:

        if form.is_valid() and customer_form.is_valid() and carInfo_form.is_valid():
            car_instance = carInfo_form.save()
            customer_instance = customer_form.save()
    
  • 使用这两种形式,我们可以像往常一样调用 .save()。但是,我们会将返回值分配给 car_instance 和 customer_instance。这些将包含我们刚刚使用表单上的 .save() 方法创建的 CarInfo 和 Customer 模型的实例。

  • 接下来,使用 .save() 方法中的 commit=False 参数,我们能够从绑定的表单(包含 request.POST 数据)创建一个对象,而不是将其保存到数据库中。

            complaint_instance = form.save(commit=False)
            complaint_instance.car_info = car_instance
            complaint_instance.customer_info = customer_instance          
            complaint_instance.save()
    
  • 为了更清楚地说明这一点,您还可以像这样创建一个新的投诉对象:

    complaint_info = form.cleaned_data.get('some_complaint_info')
    complaint_instance = Complaint(car_info=car_instance, customer_info=customer_instance, some_complaint_info=some_complaint_info)
    complaint_instance.save()
    
  • 渲染

【讨论】:

  • 这看起来与我的解决方案没有太大不同。 form 应该如何在没有外键的情况下验证 carInfo 和客户集?
  • @Marcin Cylke 我相信 .is_valid() 正在检查表单中的字段是否有效。验证表单和验证模型是两件不同的事情。 docs.djangoproject.com/en/dev/ref/models/instances/#id1
  • @Marcin,ComplaintForm 应该可以正常验证,因为这些字段是 excluded。试一试。
  • 对于DateField,更好的方法是DateField(auto_now_add=True) -- docs.djangoproject.com/en/dev/ref/models/fields/…
  • "按照目前的实现,将 auto_now 或 auto_now_add 设置为 True 将导致该字段设置为 editable=False 和 blank=True。"小心这一点,确保这不是问题。
【解决方案3】:

可能表单集工厂和内联表单集应该可以解决您的问题...您可以修改或覆盖从模型创建的表单字段,对于子模型,您可以使用内联表单集...

Formsets and inline formsets...

一个可能的解决方案:

在您的表单定义中:

class CarInfoFrm(forms.ModelForm):
    class Meta:
        model = CarInfo
        fields = (....)
carInfoForm = inlineformset_factory(Complaint, CarInfo, form=carInfoFrm,)
CustomerForm = inlineformset_factory(Complaint, Customer, form=carInfoFrm,)

在你看来:

complaint = Complaint()
carInfo = CarInfo()
customer = Customer()

cus_form = CustomerForm(parameters, instance=complaint)
car_form = CarInfoForm(parameters, instance=complaint)
comp_form = ComplaintForm(parameters, instance=complaint)

if cus_form.is_valid() and ...... :
    comp = comp_form.save(commit=False)#do not save it yet
    comp.<attr> = "some_value" #you can edit your data before save...
    comp.save()
    car = car_form(commit=False)
    # do as complaint form... edit and save... 

编辑:我在保存内联表单时定义实例参数时犯了一个错误。所以我纠正它...

当你更新现有记录时,你不会有问题,但最好像这样使用它:

if comp_form.is_valid():
    comp = comp_form.save(commit=False)
    comp.<attr> = "some_value"
    comp.save()
if car_form.is_valid():
    # edit if neccessary then save...

if cust_form.is_Valid():
    # edit if neccessary then save...

更容易的是,当您定义表单时,您可以通过外键设置父表单

carInfoForm = inlineformset_factory(Complaint, CarInfo, form=carInfoFrm,)

当您使用内联表单进行更新时,您会使用父 Compalaint 记录对其进行初始化,

car_form = CarInfoForm(parameters, instance=complaint)

所以,car_form 不接受 carInfo 实例,而是一个投诉实例(这是我在第一个答案中的错误,所以我更正了它)。如果它创建一个新记录,它会自动将它绑定到相关的投诉记录。如果是更新,它只会更新您想要的字段。

对我来说,最好使用框架的方式而不是自己编写。通过这样做,您将保证所有的验证检查都由 django 完成。

【讨论】:

  • 这不是我写的更复杂的版本吗?你能总结一下你的方法的区别吗?我仍然担心如果不设置 fk,form 将无法验证。今天晚些时候会试试这个。
  • 阅读我上面关于表单和模型验证的评论,看起来还有另一个问题。请发布您的模型和表格。
猜你喜欢
  • 2018-02-22
  • 2011-07-03
  • 1970-01-01
  • 2011-09-17
  • 1970-01-01
  • 2021-06-22
  • 2014-02-06
  • 1970-01-01
  • 2011-06-02
相关资源
最近更新 更多