需要澄清使用 Django 1.4 表单向导,特别是预填充和保存

Posted

技术标签:

【中文标题】需要澄清使用 Django 1.4 表单向导,特别是预填充和保存【英文标题】:Need clarification on using Django 1.4 Form Wizards, specifically pre-filling and saving 【发布时间】:2012-04-19 06:31:52 【问题描述】:

我们正在使用 Django 1.4 的新表单向导功能构建一个向导。 这方面的文档非常简洁,我们找不到任何高级示例。我们正在使用一个命名的步骤向导(需要支持我们使用的列表视图/数据网格)和一个会话后端。 该向导旨在编辑角色和链接权限,旨在提供添加和编辑功能。我们通过在第一步询问用户是否要添加或编辑来做到这一点。

下一步取决于该选择; 如果用户想要编辑,则会出现一个搜索屏幕,然后是显示结果的listview/datagrid。然后用户可以选择其中一个结果并转到详细信息屏幕,然后是FilteredSelectMultiple 页面,允许他/她将权限链接到该角色。

如果用户想要添加新角色,则会跳过搜索和结果屏幕,直接进入详细信息屏幕,然后是链接屏幕。 这一切都很好,在 urls.py 中使用 condition_dict,但我们想知道一些关于一般功能的事情:

When a specific pre-existing role is selected, how can we fill the details and the link-screen with the corresponding data?

我们是否实例化一个角色对象并以某种方式将其传递给两个表单,如果是,我们在哪里实例化它,我们是否需要分别为每个表单执行此操作(这似乎有点过头了)?

保存时,通常的做法是创建角色对象的另一个实例,将表单数据添加到其中并保存,还是我们可以以某种方式重新使用表单中使用的对象?

我们尝试过重载 get_form_instance 以返回角色实例,并且我们在文档中查看了 instance_dict,但感觉方法错误且没有示例可以在网上找到,我们甚至不确定这些是否用于预填充数据,或者即使我们走在正确的轨道上。

从逻辑上讲,我会说在选择现有角色的步骤中,我需要使用所选对象的实例来填充向导变量,并且这些变量会显示在表单中。在向导结束时,我们反转该过程并从向导变量中获取所有数据,并将它们添加到新实例化的角色对象中并保存它。理想情况下,此实例将自行确定是否需要执行 INSERT 或 UPDATE,具体取决于是否填充了主键。

如果有人能提供一个例子,或者向正确的方向轻推,将不胜感激。

views.py中wizardview类的代码如下:

class RolesWizard(NamedUrlSessionWizardView):

def get_template_names(self):
    # get template for each step...
    if self.steps.current == 'choice':
        return 'clubassistant/wizard_neworeditrole.html'
    if self.steps.current == 'search':
        return 'clubassistant/wizard_searchrole.html'
    if self.steps.current == 'results':
        return 'clubassistant/wizard_pickrole.html'
    if self.steps.current == 'details':
        return 'clubassistant/wizard_detailsrole.html'
    elif self.steps.current == 'rights':
        return 'clubassistant/wizard_roles.html'

def get_context_data(self, form, **kwargs):
    # get context data to be passed to the respective templates
    context = super(RolesWizard, self).get_context_data(form=form, **kwargs)

    # add the listview in the results screen
    if self.steps.current == 'results':
        # get search text from previous step
        cleaned_data = self.get_cleaned_data_for_step('search')
        table = RolesTable(Roles.objects.filter(
            role_name__contains=cleaned_data['searchrole'])
        )
        RequestConfig(self.request, paginate=
            "per_page": 4,
            ).configure(table)
        # add the listview with results
        context.update('table': table)

    # add a role instance based on the chosen primary key
    if self.steps.current == 'rights':
        cleaned_data = self.get_cleaned_data_for_step('results')
        role_id = cleaned_data['role_uuid']
        role = get_object_or_404(Roles, pk=role_id)
        context.update('role': role)

    return context

def done(self, form_list, **kwargs):
    # this code is executed when the wizard needs to be completed

    # combine all forms into a single dictionary
    wizard = self.get_all_cleaned_data()

    if wizard.get("neworeditrole")=="add":
        role = Roles()
    else:
        role = get_object_or_404(Roles, pk=wizard.get("role_uuid"))

    # many-to-many rights/roles
    role.role_rights_new_style.clear()
    for each_right in wizard.get('role_rights_new_style'):
        RightsRoles.objects.create(role=role, right=each_right,)

    # other properties
    for field, value in self.get_cleaned_data_for_step('details'):
        setattr(role, field, value)

    role.save()

    # return to first page of wizard...
    return HttpResponseRedirect('/login/maintenance/roles/wizard/choice/')

【问题讨论】:

【参考方案1】:

对于未来的谷歌员工:

我在使用 get_form() 方面取得了一些成功,因为它是在呈现表单之前调用的。从几个 ModelForms 开始:

class Wizard1(models.ModelForm): 
    class Meta:
        model = MyModel
        fields = ('field0', 'model0')
class Wizard2(models.ModelForm): 
    class Meta:
        model = MyModel
        excludes = ('field0', 'model0')

然后,在您的 SessionWizardView 中:

class MyWizard(SessionWizardView):
    def get_form(self, step=None, data=None, files=None):
        form = super(ExtensionCreationWizard, self).get_form(step, data, files)

        if step is not None and data is not None:
            # get_form is called for validation by get_cleaned_data_for_step()
            return form

        if step == "0":
            # you can set initial values or tweak fields here

        elif step == "1":
            data = self.get_cleaned_data_for_step('0')
            if data is not None:
                form.fields['field1'].initial = data.get('field0')
                form.fields['field2'].widget.attrs['readonly'] = True
                form.fields['field3'].widget.attrs['disabled'] = True
                form.fields['model1'].queryset = Model1.objects.filter(name="foo")

        return form

操作都在第 1 步中。您从第 0 步请求经过验证的数据(这会触发对第 0 步的另一个 get_form() 调用,所以要小心),然后您可以访问在第 0 步中设置的任何值。

我提供了几个可以在字段上更改的设置示例。您可以更新查询集以限制 ChoiceField 中的值,或者再次重新显示值但将其设为只读。我注意到一个警告...... readonly 在 ChoiceField 上不起作用。您可以将其禁用,但提交表单时不会传播该值。

【讨论】:

hmm,在我看来,“get_form”方法在这里有点不幸,因为任何与表单相关的东西都应该保存在表单类本身中。更好的方法是使用 get_form_kwargs 设置“标志”,然后使用它们自定义表单 init 方法中的字段。【参考方案2】:

让我们看看我是否可以提供帮助。我做了一个表单向导,根据答案添加步骤。在每个步骤中,我都将所有表单保存在会话变量中,如下所示:

def process_step(self, request, form, step):
  request.session['form_list'] = self.form_list
  request.session['initial'] = self.initial

然后,每次呈现该视图时,我都会使用所有以前的数据实例化一个新的表单向导:

def dynamic_wizard(request):
  if not request.session.get('form_list'):
        form = Wizard([Form1])
    else:
        form = Wizard(request.session.get('form_list'), initial = request.session['initial'])
    return form(context=RequestContext(request), request=request)

【讨论】:

以上是关于需要澄清使用 Django 1.4 表单向导,特别是预填充和保存的主要内容,如果未能解决你的问题,请参考以下文章

Django 表单向导调度程序

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

django 表单向导:全局名称“请求”未在 done() 方法中定义

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

django 向导,在同一步骤中使用表单和表单集

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