我无法使用formset运行单元测试。
我尝试做一个测试:
class NewClientTestCase(TestCase):
def setUp(self):
self.c = Client()
def test_0_create_individual_with_same_adress(self):
post_data = {
'ctype': User.CONTACT_INDIVIDUAL,
'username': 'dupond.f',
'email': 'new@gmail.com',
'password': 'pwd',
'password2': 'pwd',
'civility': User.CIVILITY_MISTER,
'first_name': 'François',
'last_name': 'DUPOND',
'phone': '+33 1 34 12 52 30',
'gsm': '+33 6 34 12 52 30',
'fax': '+33 1 34 12 52 30',
'form-0-address1': '33 avenue Gambetta',
'form-0-address2': 'apt 50',
'form-0-zip_code': '75020',
'form-0-city': 'Paris',
'form-0-country': 'FRA',
'same_for_billing': True,
}
response = self.c.post(reverse('client:full_account'), post_data, follow=True)
self.assertRedirects(response, '%s?created=1' % reverse('client:dashboard'))
我有这个错误:
ValidationError:[u'ManagementForm数据丢失或已经丢失 篡改了']
我的观点:
def full_account(request, url_redirect=''):
from forms import NewUserFullForm, AddressForm, BaseArticleFormSet
fields_required = []
fields_notrequired = []
AddressFormSet = formset_factory(AddressForm, extra=2, formset=BaseArticleFormSet)
if request.method == 'POST':
form = NewUserFullForm(request.POST)
objforms = AddressFormSet(request.POST)
if objforms.is_valid() and form.is_valid():
user = form.save()
address = objforms.forms[0].save()
if url_redirect=='':
url_redirect = '%s?created=1' % reverse('client:dashboard')
logon(request, form.instance)
return HttpResponseRedirect(url_redirect)
else:
form = NewUserFullForm()
objforms = AddressFormSet()
return direct_to_template(request, 'clients/full_account.html', {
'form':form,
'formset': objforms,
'tld_fr':False,
})
和我的表单文件:
class BaseArticleFormSet(BaseFormSet):
def clean(self):
msg_err = _('Ce champ est obligatoire.')
non_errors = True
if 'same_for_billing' in self.data and self.data['same_for_billing'] == 'on':
same_for_billing = True
else:
same_for_billing = False
for i in [0, 1]:
form = self.forms[i]
for field in form.fields:
name_field = 'form-%d-%s' % (i, field )
value_field = self.data[name_field].strip()
if i == 0 and self.forms[0].fields[field].required and value_field =='':
form.errors[field] = msg_err
non_errors = False
elif i == 1 and not same_for_billing and self.forms[1].fields[field].required and value_field =='':
form.errors[field] = msg_err
non_errors = False
return non_errors
class AddressForm(forms.ModelForm):
class Meta:
model = Address
address1 = forms.CharField()
address2 = forms.CharField(required=False)
zip_code = forms.CharField()
city = forms.CharField()
country = forms.ChoiceField(choices=CountryField.COUNTRIES, initial='FRA')
答案 0 :(得分:22)
特别是,我发现ManagmentForm验证器正在寻找要发布的以下项目:
form_data = {
'form-TOTAL_FORMS': 1,
'form-INITIAL_FORMS': 0
}
答案 1 :(得分:14)
每个Django formset都附带一个需要包含在帖子中的管理表单。 official docs很好地解释了它。要在单元测试中使用它,您需要自己编写。 (我提供的链接显示了一个示例),或者调用输出数据的formset.management_form
。
答案 2 :(得分:5)
事实上,通过检查响应的上下文,很容易重现formset中的任何内容。
请考虑以下代码(self.client
是常规test client):
url = "some_url"
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
# data will receive all the forms field names
# key will be the field name (as "formx-fieldname"), value will be the string representation.
data = {}
# global information, some additional fields may go there
data['csrf_token'] = response.context['csrf_token']
# management form information, needed because of the formset
management_form = response.context['form'].management_form
for i in 'TOTAL_FORMS', 'INITIAL_FORMS', 'MIN_NUM_FORMS', 'MAX_NUM_FORMS':
data['%s-%s' % (management_form.prefix, i)] = management_form[i].value()
for i in range(response.context['form'].total_form_count()):
# get form index 'i'
current_form = response.context['form'].forms[i]
# retrieve all the fields
for field_name in current_form.fields:
value = current_form[field_name].value()
data['%s-%s' % (current_form.prefix, field_name)] = value if value is not None else ''
# flush out to stdout
print '#' * 30
for i in sorted(data.keys()):
print i, '\t:', data[i]
# post the request without any change
response = self.client.post(url, data)
如果在调用data
之前修改self.client.post
,则可能会改变数据库。因此,对self.client.get
的后续调用可能不会产生相同的数据,特别是对于管理表单和表单集中表单的顺序(因为它们可以按不同的顺序排序,具体取决于基础查询集)。这意味着
data[form-3-somefield]
并致电self.client.get
,则相同的字段可能会显示在data[form-8-somefield]
,data
之前修改self.client.post
,则无法使用相同的self.client.post
再次致电data
:您必须致电self.client.get
并重建再次data
。答案 3 :(得分:1)
您可以在测试类[Python 3代码]中添加以下测试助手方法:
def build_formset_form_data(self, form_number, **data):
form = {}
for key, value in data.items():
form_key = f"form-{form_number}-{key}"
form[form_key] = value
return form
def build_formset_data(self, forms, **common_data):
formset_dict = {
"form-TOTAL_FORMS": f"{len(forms)}",
"form-MAX_NUM_FORMS": "1000",
"form-INITIAL_FORMS": "1"
}
formset_dict.update(common_data)
for i, form_data in enumerate(forms):
form_dict = self.build_formset_form_data(form_number=i, **form_data)
formset_dict.update(form_dict)
return formset_dict
并在测试中使用它们
def test_django_formset_post(self):
forms = [{"key1": "value1", "key2": "value2"}, {"key100": "value100"}]
payload = self.build_formset_data(forms=forms, global_param=100)
print(payload)
# self.client.post(url=url, data=payload)
您将获取正确的有效载荷,这会使Django ManagementForm满意
{
"form-INITIAL_FORMS": "1",
"form-TOTAL_FORMS": "2",
"form-MAX_NUM_FORMS": "1000",
"global_param": 100,
"form-0-key1": "value1",
"form-0-key2": "value2",
"form-1-key100": "value100",
}
利润
答案 4 :(得分:0)
这似乎根本不是一个表格。 Formset将始终在每个POSTed值上都有某种前缀,以及Bartek提到的ManagementForm。如果您发布了您要测试的视图的代码以及它使用的表单/表单集,则可能会有所帮助。
答案 5 :(得分:0)
我的情况可能是一个异常值,但有些情况实际上错过了库存“contrib”管理表单/模板中设置的字段导致错误
“ManagementForm数据丢失或已被篡改”
保存时。
问题在于 unicode 方法(SomeModel:[错误的Unicode数据]),我发现它正在调查缺少的内联。
我吸取的教训是不要使用MS角色地图。我的问题是粗俗的分数(¼,½,¾),但我认为它可能会出现许多不同的方式。对于特殊字符,从w3 utf-8页面复制/粘贴修复它。
答案 6 :(得分:0)
这里有几个非常有用的答案,例如pymen和Raffi,显示了如何使用测试客户端为表单集帖子构建格式正确的有效载荷。
但是,所有这些仍然至少需要一些前缀的手动编码,处理现有对象等,这并不理想。
或者,我们可以使用从get()
请求中获得的post()为response创建有效负载:
def create_formset_post_data(response, new_form_data=None):
if new_form_data is None:
new_form_data = []
csrf_token = response.context['csrf_token']
formset = response.context['formset']
prefix_template = formset.empty_form.prefix # default is 'form-__prefix__'
# extract initial formset data
management_form_data = formset.management_form.initial
form_data_list = formset.initial # this is a list of dict objects
# add new form data and update management form data
form_data_list.extend(new_form_data)
management_form_data['TOTAL_FORMS'] = len(form_data_list)
# initialize the post data dict...
post_data = dict(csrf_token=csrf_token)
# add properly prefixed management form fields
for key, value in management_form_data.items():
prefix = prefix_template.replace('__prefix__', '')
post_data[prefix + key] = value
# add properly prefixed data form fields
for index, form_data in enumerate(form_data_list):
for key, value in form_data.items():
prefix = prefix_template.replace('__prefix__', f'{index}-')
post_data[prefix + key] = value
return post_data
输出(post_data
)也将包含任何现有对象的表单字段。
这是在Django TestCase
中使用它的方式:
def test_post_formset_data(self):
url_path = '/my/post/url/'
user = User.objects.create()
self.client.force_login(user)
# first GET the form content
response = self.client.get(url_path)
self.assertEqual(HTTPStatus.OK, response.status_code)
# specify form data for test
test_data = [
dict(first_name='someone', email='someone@email.com', ...),
...
]
# convert test_data to properly formatted dict
post_data = create_formset_post_data(response, new_form_data=test_data)
# now POST the data
response = self.client.post(url_path, data=post_data, follow=True)
# some assertions here
...
一些注意事项:
我们可以使用'TOTAL_FORMS'
来导入TOTAL_FORM_COUNT
,而不是使用django.forms.formsets
字符串文字,但这似乎并不公开(至少在Django 2.2中)。 / p>
还要注意,如果can_delete为'DELETE'
,则表单集会向每个表单添加一个True
字段。要测试删除现有项目,您可以在测试中执行以下操作:
...
post_data = create_formset_post_data(response)
post_data['form-0-DELETE'] = True
# then POST, etc.
...