重写Django表单字段的名称属性
我创建了一个Django表单,它会提交到一个我无法控制的其他网站。我的想法是,做一个样式好、生成整齐的表单,让它看起来很符合我自己网站的风格,提交后再把用户带到其他地方。
但是,
- 如果那个其他网站的表单改了字段的名称,我就得在我的表单里也改字段名称,然后在我应用的其他地方也得改,因为
name
属性是和字段的属性名称绑定在一起的。 - 如果远程表单使用了一些奇怪的名称,那么我的表单对象也得用这些奇怪的名称,这样就会让我的应用代码变得混乱。
- 如果这些名称恰好是Python中的保留字(比如
from
),那么创建一个Django表单对象就会变得很困难,甚至不可能。
有没有办法在显示字段时指定一个不同的字符串来作为'name'属性呢?这样就可以把HTML表单和表示它的类解耦。
这需要两个部分:
- 在小部件中覆盖名称值
- 让表单在绑定时从request.POST中读取这个值
在这种情况下,我只需要第一步,但第二步对于更普遍地解决我上面列出的问题也是相关的。
5 个回答
attr这个名字是和字段使用的属性名字关联在一起的。
从你的描述来看(“如果那个远程表单更改了任何字段的名字,我就需要在我的表单中也更改字段的名字,然后在我的应用程序的其他地方也要更改这些名字”,“如果远程表单使用了奇怪的名字,那么我的表单对象也必须有奇怪的属性名字,这样会污染我应用程序的代码。”),你意识到这是一个脆弱的设计选择。
你应该考虑到,你的视图函数可以非常简单地解决这个问题。
与其让远程应用和你的应用之间的名字保持一致,不如用你的视图函数把你喜欢的名字映射到他们那些糟糕的名字上。
这就是视图函数的作用。
更进一步,你的视图函数做了三件事。
- 验证输入。也许还会把它们保存在某个本地数据库中。
- 把你的表单数据映射到他们的请求结构。
- 发起远程请求(通过
httplib
或urllib2
或其他工具)。
第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'],
}
我实现了一个简单的功能,它覆盖了小部件的 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),
)
这段话说的是,虽然这样用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)