如何在表单向导中使用 ModelFormSet

Posted

技术标签:

【中文标题】如何在表单向导中使用 ModelFormSet【英文标题】:How to work with ModelFormSet in Form Wizard 【发布时间】:2015-01-20 03:56:21 【问题描述】:

Django 文档没有很好地记录这个主题。事实上,他们在文档中的唯一参考是这一段:

如何使用 ModelForm 和 ModelFormSet

WizardView.instance_dict. WizardView 支持 ModelForms 和 ModelFormSets。除了 initial_dict 之外,as_view() > 方法采用 instance_dict 参数,该参数应包含基于 >ModelForm 的步骤的模型实例和基于 ModelFormSet 的步骤的查询集。

我还没有找到任何关于如何使用它的好而清晰的例子。有人可以帮我解决这个问题吗?

具体来说:

forms.py 中要做什么? 如果我只在表单的某些步骤而不是所有步骤中需要 ModelFormSet 怎么办? 我需要在views.py 和模板中做什么?

以我正在做的一个用例和小项目为例,我分享我的代码:

用例: 用户想要以多步骤形式注册,在第一步中他介绍了他的姓名和姓氏。 在第二步中他介绍了他的护照号码和酒店登记,他还想登记他的儿子和妻子,他们将与他一起去这家酒店(这里我想使用来自HotelRegistration模型的模型集)。 在第三步中,他键入了他的航班信息。如果表单有效并保存在数据库中,则会收到确认消息。

这是我的代码:

models.py

class Event(models.Model):

    name = models.CharField(max_length=100)
    date_from = models.DateField(auto_now=False)
    date_to = models.DateField(auto_now=False)
    description = models.TextField()

    def __unicode__(self):
       return self.name


class Hotel(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=100)

    def __unicode__(self):
        return self.name


class HotelRegistration(models.Model):
    pax_first_name = models.CharField(max_length=50)
    pax_last_name = models.CharField(max_length=50)
    hotel = models.ForeignKey(Hotel)

    def __unicode__(self):
         return self.pax_first_name


class Registration(models.Model):

    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    passport = models.CharField(max_length=15)
    city_origin = models.CharField(max_length=50)
    flight_date_from = models.DateField(auto_now=False)
    flight_date_to = models.DateField(auto_now=False)
    require_transfer = models.BooleanField(default=None)
    event = models.ForeignKey(Event)
    hotel_registration = models.ForeignKey(HotelRegistration, blank=True, null=True)

    def __unicode__(self):
        return self.first_name

forms.py

from django import forms

from .models import Registration

class FormStep1(forms.ModelForm):
    class Meta:
        model = Registration
        fields = ['first_name', 'last_name']
        widgets = 
            'first_name': forms.TextInput(attrs='placeholder': 'Nombre de la persona que   esta reservando', 'label_tag': 'Nombre'),
        'last_name': forms.TextInput(attrs='placeholder': 'Apellido')
    

class FormStep2(forms.ModelForm):
    class Meta:
        model = Registration
        fields = ['passport', 'hotel_registration']
        exclude = ('first_name', 'last_name', 'event' , 'city_origin', 'flight_date_from', 'flight_date_to', 'require_transfer')
        widgets = 
             'passport': forms.NumberInput(attrs='placeholder':'Escriba su pasaporte')
        

class FormStep3(forms.ModelForm):
    class Meta:
        model = Registration
        fields = ['city_origin', 'flight_date_from', 'flight_date_to', 'require_transfer', 'event']
        exclude = ('first_name', 'last_name', 'hotel_registration')
        widgets = 
            'city_origin': forms.TextInput(attrs='placeholder':'Ciudad desde donde esta viajando'),
            'flight_date_from': forms.DateInput(format=('%d-%m-%Y'), attrs='class':'myDateClass', 'placeholder':'Select a date'),
            'flight_date_to': forms.DateInput(format=('%d-%m-%Y'), attrs='class':'myDateClass', 'placeholder':'Select a date'),
            'require_transfer': forms.Select(),
            'event': forms.Select()
    

views.py

from django.shortcuts import render
from django.contrib.formtools.wizard.views import SessionWizardView
from django.http import HttpResponseRedirect
from django.views.generic import TemplateView
from django.forms.models import inlineformset_factory

from .models import Registration, HotelRegistration
from .forms import FormStep1, FormStep2, FormStep3


FORMS = [
    ("step1", FormStep1),
        ("step2", FormStep2),
        ("step3", FormStep3)
]

TEMPLATES = 
    "step1" : "wizard/step1.html",
    "step2" : "wizard/step2.html",
    "step3" : "wizard/step3.html"



class TestFormWizard(SessionWizardView):

    instance = None

def get_form_instance(self, step):
    if self.instance is None:
        self.instance = Registration()
    return self.instance


def get_form(self, step=None, data=None, files=None):
    form = super(TestFormWizard, self).get_form(step, data, files)
    HotelRegistFormSet = inlineformset_factory(HotelRegistration, Registration, can_delete=True, extra=1)

    # determine the step if not given
    if step is None:
        step = self.steps.current

    if step == '2':
        hotel_registration_formset = HotelRegistFormSet(self.steps.current, self.steps.files, prefix="step2")
    return form


def get_template_names(self):
    return [TEMPLATES[self.steps.current]]

def done(self, form_list, **kwargs):
    self.instance.save()
    return HttpResponseRedirect('/register/confirmation')


class ConfirmationView(TemplateView):
    template_name = 'wizard/confirmation.html'

模板

   % extends "base.html" %
   % load i18n %


   % block content %
   <p>Step  wizard.steps.step1  of  wizard.steps.count </p>
   <form action="" method="post">% csrf_token %
   <table>
         wizard.management_form 
             % if wizard.form.forms %
         wizard.form.management_form 
         % for form in wizard.form.forms %
              form 
         % endfor %
     % else %
          wizard.form 
     % endif %
    </table>
     % if wizard.steps.prev %
     <button name="wizard_goto_step" type="submit" value=" wizard.steps.first ">% trans   "first step" %</button>
    <button name="wizard_goto_step" type="submit" value=" wizard.steps.prev ">% trans "prev step" %</button>
    % endif %
    <input type="submit" value="% trans "submit" %"/>
    </form>
    % endblock %

【问题讨论】:

您解决了这个问题吗?我即将放弃使用带有向导视图的模型表单集。 您解决了这个问题吗?你呢,Nostalgio? 【参考方案1】:

forms.py 中要做什么?

覆盖 SessionWizardView 的 get_form_instance 方法。这是 FormWizard 用来确定模型实例是否与模型表单一起使用的方法

WizardView.get_form_instance(step) 仅当 ModelForm 用作 step step 的表单时才会调用此方法。 返回一个 Model 对象,在为 step step 实例化 ModelForm 时,该对象将作为实例参数传递。如果初始化表单向导时没有提供实例对象,则返回 None。

这可以在 SessionWizardView 实现中的每个步骤有条件地完成。我不明白你想要做的足够好来给你一个确切的例子,所以这里有一个更通用的例子。

def get_form_instance(self, step):
    if step == u'3':
        past_data =  self.get_cleaned_data_for_step(u'2')
        hotel_name = past_data['hotel_field']
        hotel = Hotel.objects.get(name=hotel_name)
        return hotel #do NOT set self.instance, just return the model instance you want
    return self.instance_dict.get(step, None) # the default implementation

如果我只在表单的某些步骤而不是所有步骤中需要 ModelFormSet 怎么办?

见上文;使用 'if step == (form/step name)' 表达式来确定每一步会发生什么。

在views.py和模板中我需要做什么?

使用 ModelForm 并向其传递模型对象实例将设置初始表单值。你需要更多吗?

希望这向您展示了 FormWizard 中预期的结构。与我使用过的 Django 的任何其他部分相比,FormWizard 需要一个非常特定的结构并且是一个整体类。

【讨论】:

以上是关于如何在表单向导中使用 ModelFormSet的主要内容,如果未能解决你的问题,请参考以下文章

django:如何在表单向导中使用 inlineformset?

如何修复modelformset错误:__init __()缺少1个必需的位置参数:'user'

使用状态机时如何在向导中配置动态表单字段

具有动态表单添加的 Modelformset 仅保存第一个表单

如何使用默认文件存储(S3)通过 Django FormTools 表单向导上传文件?

Django:如何让表单向导在创建表单时接收请求对象?