Django FormWizard如何动态更改form_list
Posted
技术标签:
【中文标题】Django FormWizard如何动态更改form_list【英文标题】:Django FormWizard how to change the form_list dynamically 【发布时间】:2013-10-04 19:05:13 【问题描述】:我可以to dynamically call one form related to the data I chose from the step ealier。
但是当我使用done
方法时,我可以看到我的form_list
保持不变。
这就是我所做的:
def get_form_list(request, form_list=None):
if form_list is None:
form_list = [ProviderForm, DummyForm, ConsummerForm, DummyForm, \
ServicesDescriptionForm]
return UserServiceWizard.as_view(form_list=form_list)(request)
class UserServiceWizard(SessionWizardView):
instance = None
def __init__(self, **kwargs):
self.form_list = kwargs.pop('form_list')
return super(UserServiceWizard, self).__init__(**kwargs)
def get_form_instance(self, step):
if self.instance is None:
self.instance = UserService()
return self.instance
def get_context_data(self, form, **kwargs):
data = self.get_cleaned_data_for_step(self.get_prev_step(
self.steps.current))
if self.steps.current == '1':
service_name = str(data['provider']).split('Service')[1]
form = class_for_name('th_' + service_name.lower() + '.forms',
service_name + 'ProviderForm')
self.form_list['1'] = form #here my form is correctly change I can see
elif self.steps.current == '3':
service_name = str(data['consummer']).split('Service')[1]
form = class_for_name('th_' + service_name.lower() + '.forms',
service_name + 'ConsummerForm')
self.form_list['3'] = form
context = super(UserServiceWizard, self).get_context_data(form=form,
**kwargs)
return context
def done(self, form_list, **kwargs):
print self.form_list #here form_list contains ProviderForm, DummyForm, ConsummerForm, DummyForm, ServicesDescriptionForm
在第 0 步,我的 form_list 没问题:
u'0': <class 'django_th.forms.wizard.ProviderForm'>,
u'1': <class 'django_th.forms.wizard.DummyForm'>,
u'2': <class 'django_th.forms.wizard.ConsummerForm'>,
u'3': <class 'django_th.forms.wizard.DummyForm'>,
u'4': <class 'django_th.forms.base.ServicesDescriptionForm'>
在第 1 步,我的 form_list 没问题:我们可以看到第二个表单是我预期的
u'0': <class 'django_th.forms.wizard.ProviderForm'>,
u'1': <class 'th_rss.forms.RssProviderForm'>,
u'2': <class 'django_th.forms.wizard.ConsummerForm'>,
u'3': <class 'django_th.forms.wizard.DummyForm'>,
u'4': <class 'django_th.forms.base.ServicesDescriptionForm'>
在第 2 步,我的 form_list 是 ko ;与第 0 步相同:我的第二种形式是返回 DummyForm
u'0': <class 'django_th.forms.wizard.ProviderForm'>,
u'1': <class 'django_th.forms.wizard.DummyForm'>,
u'2': <class 'django_th.forms.wizard.ConsummerForm'>,
u'3': <class 'django_th.forms.wizard.DummyForm'>,
u'4': <class 'django_th.forms.base.ServicesDescriptionForm'>
如何更改 self.form_list 并将我在 get_context_data
中所做的更改保留到向导结束而不是每一步?
编辑 这是与 Rohan 的建议完美配合的完整代码:
def get_form(self, step=None, data=None, files=None):
"""
change the form instance dynamically from the data we entered
at the previous step
"""
if step is None:
step = self.steps.current
if step == '1':
# change the form
prev_data = self.get_cleaned_data_for_step('0')
service_name = str(prev_data['provider']).split('Service')[1]
class_name = 'th_' + service_name.lower() + '.forms'
form_name = service_name + 'ProviderForm'
form_class = class_for_name(class_name, form_name)
form = form_class(data)
elif step == '3':
# change the form
prev_data = self.get_cleaned_data_for_step('2')
service_name = str(prev_data['consummer']).split('Service')[1]
class_name = 'th_' + service_name.lower() + '.forms'
form_name = service_name + 'ConsummerForm'
form_class = class_for_name(class_name, form_name)
form = form_class(data)
else:
# get the default form
form = super(UserServiceWizard, self).get_form(step, data, files)
return form
def done(self, form_list, **kwargs):
"""
Save info to the DB
The process is :
1) get the infos for the Trigger from step 0, 2, 4
2) save it to TriggerService
3) get the infos from the "Provider" and "Consummer" services
at step 1 and 3
4) save all of them
"""
# get the datas from the form for TriggerService
i = 0
for form in form_list:
# cleaning
data = form.cleaned_data
# get the service we selected at step 0 : provider
if i == 0:
trigger_provider = UserService.objects.get(
name=data['provider'],
user=self.request.user)
model_provider = get_service_model('provider', data)
# get the service we selected at step 2 : consummer
elif i == 2:
trigger_consummer = UserService.objects.get(
name=data['consummer'],
user=self.request.user)
model_consummer = get_service_model('consummer', data)
# get the description we gave for the trigger
elif i == 4:
trigger_description = data['description']
i += 1
# save the trigger
trigger = TriggerService(
provider=trigger_provider, consummer=trigger_consummer,
user=self.request.user, status=True,
description=trigger_description)
trigger.save()
model_fields =
# get the datas from the form for Service related
# save the related models to provider and consummer
i = 0
for form in form_list:
model_fields =
data = form.cleaned_data
# get the data for the provider service
if i == 1:
for field in data:
model_fields.update(field: data[field])
model_fields.update('trigger_id': trigger.id, 'status': True)
model_provider.objects.create(**model_fields)
# get the data for the consummer service
elif i == 3:
for field in data:
model_fields.update(field: data[field])
model_fields.update('trigger_id': trigger.id, 'status': True)
model_consummer.objects.create(**model_fields)
i += 1
return HttpResponseRedirect('/')
【问题讨论】:
【参考方案1】:与其在get_context_data()
中更改表单列表等,我认为更合适的是在您的向导视图中实现get_form()
方法并根据步骤和以前的数据返回不同的表单实例。
类似这样的:
class UserServiceWizard(SessionWizardView):
instance = None
def get_form(self, step=None, data=None, files=None):
if step is None:
step = self.steps.current
prev_data = self.get_cleaned_data_for_step(self.get_prev_step(
self.steps.current))
if step == '1':
service_name = str(prev_data['provider']).split('Service')[1]
form_class = class_for_name('th_' + service_name.lower() + '.forms',
service_name + 'ProviderForm')
form = form_class(data)
elif step == '3':
service_name = str(prev_data['consummer']).split('Service')[1]
form_class = class_for_name('th_' + service_name.lower() + '.forms',
service_name + 'ConsummerForm')
form = form_class(data)
else:
form = super(UserServiceWizard, self).get_form(step, data, files)
return form
这里的技巧是不要更改表单列表的长度(您已经正确完成了),而只是更改表单实例。为此,Django 提供了覆盖get_form()
方法的方法。 Django 将尊重此方法并始终使用它来获取该方法的表单实例。
【讨论】:
为您答复。我试试看。我也用 get_form 做了一些测试,但没有成功。事实上,我用表单向导的每个功能测试了很多解决方案,结果我什么也没看到。 所以我放弃了我的 get_context_data,我的第一个测试在 step1 没有返回任何数据; prev_data 是空的,所以 prev_data['provider'] 失败,'NoneType' object has no attribute '__getitem__'
是的!我几乎达到了目标。我会尽快确认!
@FoxMaSk,不要认为这个错误是相关的。如果您仍然收到该错误,也许您可以发布您的实际代码。
我确认您的回答并感谢您的帮助。现在我还有其他问题,但这里的主要问题已经解决了!【参考方案2】:
我不确定这是否是您正在寻找的解决方案,但如果您在 process_step 而不是在 get_context_data 中修改 form_list 它应该可以工作。您必须更改您的代码,因为 process_step 在提交表单后执行。
根据 Django doc https://docs.djangoproject.com/en/1.5/ref/contrib/formtools/form-wizard/ process_step 是“修改向导内部状态的钩子”,至少对于 self.kwargs vars(实际上你的 form_list 在 self.kwargs["form_list"])我已经测试过了get_context_data 中的所有修改都将被忽略,因此我认为 self.form_list 的行为方式应该相同。
【讨论】:
我将 pdb 放入循环中以跟踪向导所遵循的路径。所以第一个是__init__
of course ;) 其次是 get_context_data
,最后是 process_step
,除了最后一个步骤,路径变为__init__
,然后是process_step
,然后是done
。但在我这边,让我即时更改表单的唯一功能不是process_step
,而是get_context_data
。 process_step 没有注意到...以上是关于Django FormWizard如何动态更改form_list的主要内容,如果未能解决你的问题,请参考以下文章
Django:如何使用 ModelFormSet 和 FormWizard 过滤 ForeignKey 选择(例如使用 request.user)?
django manytomany 字段使用 through 和 formwizard