重写Django表单字段的名称属性

22 投票
5 回答
22631 浏览
提问于 2025-04-17 09:55

我创建了一个Django表单,它会提交到一个我无法控制的其他网站。我的想法是,做一个样式好、生成整齐的表单,让它看起来很符合我自己网站的风格,提交后再把用户带到其他地方。

但是,

  • 如果那个其他网站的表单改了字段的名称,我就得在我的表单里也改字段名称,然后在我应用的其他地方也得改,因为name属性是和字段的属性名称绑定在一起的。
  • 如果远程表单使用了一些奇怪的名称,那么我的表单对象也得用这些奇怪的名称,这样就会让我的应用代码变得混乱。
  • 如果这些名称恰好是Python中的保留字(比如from),那么创建一个Django表单对象就会变得很困难,甚至不可能。

有没有办法在显示字段时指定一个不同的字符串来作为'name'属性呢?这样就可以把HTML表单和表示它的类解耦。

这需要两个部分:

  1. 在小部件中覆盖名称值
  2. 让表单在绑定时从request.POST中读取这个值

在这种情况下,我只需要第一步,但第二步对于更普遍地解决我上面列出的问题也是相关的。

5 个回答

3

attr这个名字是和字段使用的属性名字关联在一起的。

从你的描述来看(“如果那个远程表单更改了任何字段的名字,我就需要在我的表单中也更改字段的名字,然后在我的应用程序的其他地方也要更改这些名字”,“如果远程表单使用了奇怪的名字,那么我的表单对象也必须有奇怪的属性名字,这样会污染我应用程序的代码。”),你意识到这是一个脆弱的设计选择。

你应该考虑到,你的视图函数可以非常简单地解决这个问题。

与其让远程应用和你的应用之间的名字保持一致,不如用你的视图函数把你喜欢的名字映射到他们那些糟糕的名字上。

这就是视图函数的作用。

更进一步,你的视图函数做了三件事。

  1. 验证输入。也许还会把它们保存在某个本地数据库中。
  2. 把你的表单数据映射到他们的请求结构。
  3. 发起远程请求(通过httpliburllib2或其他工具)。

第1和第3项变化不大。

第2项是将请求中的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'],
    }
6

我实现了一个简单的功能,它覆盖了小部件的 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),
    )
35

这段话说的是,虽然这样用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)

撰写回答