覆盖Django表单字段的名称attr

时间:2012-01-10 10:39:21

标签: python django forms

我已经构建了一个Django表单,该表单提交到另一个域(我无法控制)的页面。我的想法是,我有一个很好的风格,整齐生成的形式,整齐地适合我自己的网站,并在提交时将用户带到其他地方。

然而,

  • 如果其他表单更改了其任何字段的名称,我需要更改表单中字段的名称,然后在我的应用程序中的其他位置更改这些名称 - 因为name attr已耦合到用于该字段的属性的名称。
  • 如果远程表单使用愚​​蠢的名称,那么我的表单对象也必须具有愚蠢名称的属性,这会污染我的应用程序代码。
  • 如果这些名称恰好是reserved words in Python(例如from),则很难或不可能创建Django表单对象表示。

当显示字段时,有没有办法指定用于'name'属性的不同字符串?因此,将HTML表单与表示它的类分离。

这需要两个组成部分:

  1. 覆盖小部件中的名称值
  2. 使表单在绑定时从request.POST读取此值
  3. 在这种情况下我只需要第1步,但第2步与解决我上面列出的问题相关

5 个答案:

答案 0 :(得分:29)

这是一个非常可怕的API滥用,但是有一个名为add_prefix的表单方法被调用以确定每个字段的HTML名称应该是什么,考虑到表单的前缀(如果有的话)。您可以覆盖它,以便它在某个字典中查找字段名称并返回您想要的名称 - 不要忘记保留现有的前缀行为:

FIELD_NAME_MAPPING = {
    'field1': 'html_field1',
    'field2': 'html_field2'
}

class MyForm(forms.ModelForm):
    def add_prefix(self, field_name):
        # look up field name; return original if not found
        field_name = FIELD_NAME_MAPPING.get(field_name, field_name)
        return super(MyForm, self).add_prefix(field_name)

答案 1 :(得分:6)

我实现了一个简单的函数,它覆盖了widget render方法并分配了一个自定义名称:

def namedWidget(input_name, widget=forms.CharField):
    if isinstance(widget, type):
        widget = widget()

    render = widget.render

    widget.render = lambda name, value, attrs=None: \
        render(input_name, value, attrs)

    return widget

用法很简单:

class AliasCreationForm(forms.Form):
    merchant_id = forms.CharField(
        max_length=30,
        widget=namedWidget('PSPID', forms.HiddenInput),
    )

答案 2 :(得分:3)

  

名称attr与用于该字段的属性的名称相关联。

根据您的描述(“如果其他表单更改了其任何字段的名称,我需要更改表单中字段的名称,然后在我的应用程序中的其他位置更改这些名称 - 因为”,“如果远程表单使用愚​​蠢的名称然后我的表单对象也必须具有愚蠢名称的属性,这会污染我的应用程序的代码。“)你认为这是一个脆弱的设计决定。

您应该考虑到您的视图功能完全以一种微不足道的方式解决了这个问题。

不要在远程应用程序和应用程序之间对齐名称,而是使用视图函数将好名称映射到可怕的名称。

这是视图功能的用途。

为了更进一步,您的视图功能可以做三件事。

  1. 验证输入。也许可以将它们保存在某个本地数据库中。
  2. 将表单中的数据映射到其请求结构。
  3. 发出远程请求(通过httpliburllib2或其他)。
  4. 第1项和第3项的变化不大。

    第2项是从request.POST到字典的逐字段映射,然后你将url lib.urlencode创建POST请求。 (或者无论协议是什么。)

    因此,将第2项分解为您在设置中指定的灵活内容。

    设置

    MY_MAPPING_FUNCTION = module.function
    

    在views.py

    def submit( request ):
       if request.method == POST:
           form = SomeForm( request.POST )
           if is_valid(form):
               form.save() 
               to_be_submitted = settings.MY_MAPPING_FUNCTION( form )
               remote_post( to_be_submitted ) # or whatever your protocol is
    

    将映射模块添加到您的应用程序

    module.py

    def version_1_2( form ):
        return { 
            'silly_name_1': form.cleaned_data['your_nice_name'], 
            'from': form.cleaned_data['another_nice_name'],
        }
    
    def version_2_1( form ):
        return {
             'much_worse_name': form.cleaned_data['your_nice_name'], 
             'from': form.cleaned_data['another_nice_name'],
        }
    

答案 3 :(得分:1)

我意识到这个问题很老,但这是另一种(可能更清洁)的方法。这里的大多数答案都涉及猴子修补或覆盖超类方法。这也是如此,但我认为这是一种干净的方式。

创建一个新的Widget,扩展您需要的小部件,例如TextInput:

class TextInputSansName(forms.TextInput):
    def build_attrs(self, extra_attrs=None, **kwargs):
        if extra_attrs:
            extra_attrs.pop('name', None)
        kwargs.pop('name', None)
        return super().build_attrs(extra_attrs, **kwargs)

Django在render()操作期间在窗口小部件上调用build_attrs。在该方法中,如果它在任一字典中,我将删除'name'。这有效地在呈现之前删除了名称。

这个答案尽可能少地覆盖了Django API。

在我的某个网站中,付款处理器需要输入无名称。这是表单类中的控件声明:

card_number = forms.IntegerField(
    label='Card Number', 
    widget=TextInputSansName(attrs={ 'data-stripe': 'number' })
) 

干杯。

答案 4 :(得分:1)

很简单。只需使用类Meta的属性“ labels”即可:

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ('name', 'title', 'birth_date')
        labels = {
            'name': 'new_name',
        }

其中“新名称”将是html中显示的名称。