Django formset单元测试

时间:2009-10-27 13:19:20

标签: django unit-testing formset

我无法使用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')

7 个答案:

答案 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页面复制/粘贴修复它。

postscript-utf-8

答案 6 :(得分:0)

这里有几个非常有用的答案,例如pymenRaffi,显示了如何使用测试客户端为表单集帖子构建格式正确的有效载荷。

但是,所有这些仍然至少需要一些前缀的手动编码,处理现有对象等,这并不理想。

或者,我们可以使用从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.
      ...