【问题标题】:Override Django form field's name attr覆盖 Django 表单字段的名称 attr
【发布时间】:2012-02-06 18:49:09
【问题描述】:

我已经构建了一个 Django 表单,它可以提交到另一个域(我无法控制)上的页面。我的想法是,我有一个样式精美、生成整齐的表单,可以完美地融入我自己的网站,并在提交时将用户带到其他地方。

然而,

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

有没有办法在显示字段时为“name”属性指定不同的字符串?因此,将 HTML 表单与代表它的类解耦。

这需要两个组件:

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

在这种情况下我只需要第 1 步,但第 2 步更适用于解决我上面列出的问题

【问题讨论】:

    标签: python django forms


    【解决方案1】:

    这很简单。只需使用 Meta 类的属性“标签”:

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

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

    【讨论】:

    • field.label 与输入字段的 html 属性 name="field_name" 不同。这是一个字符串名称,将在相关的<label> 中用于字段的<input>
    【解决方案2】:

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

    新建一个Widget,扩展你需要的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。在该方法中,如果“名称”在任一字典中,我将删除它。这有效地在它呈现之前删除了名称。

    此答案尽可能少地覆盖 Django API。

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

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

    干杯。

    【讨论】:

    • 这很好,但问题是如何为字段使用 不同的 名称(指定),而不是如何完全删除它们。感谢您的回答!
    【解决方案3】:

    我已经实现了一个简单的函数,它覆盖了小部件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),
        )
    

    【讨论】:

    • 对解决方案的一个小修正,小部件必须作为第一个参数传递给 lambda 函数调用。 widget.render = lambda name, value, attrs=None: \render(widget, input_name, value, attrs)
    【解决方案4】:

    这是对 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)
    

    【讨论】:

    • 这是解决我的问题以及字段名称属性与字段对象的属性名称相关联的一般问题的一个很好的解决方案。但是,如果您需要这个,那么您可能会遇到问题。首先检查 S.Lott 的答案是否适合您。这对我不起作用,这就是我问这个问题的原因。
    • @Daniel:我最近做了你在回答中发布的确切事情。您能否澄清一下为什么您认为这是对 API 的可怕滥用?谢谢。
    • 老实说,这已经是近四年前的事了,我不记得我为什么这么想了。
    【解决方案5】:

    名称 attr 与用于字段的属性名称耦合。

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

    您应该认为您的视图函数以一种微不足道的方式完全解决了这个问题。

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

    这就是视图函数的用途。

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

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

    第 1 项和第 3 项变化不大。

    第 2 项是从 request.POST 到字典的逐字段映射,然后您可以通过 url lib.urlencode 创建 POST 请求。 (或任何协议。)

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

    设置

    MY_MAPPING_FUNCTION = module.function
    

    在你的意见中.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
    

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

    模块.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'],
        }
    

    【讨论】:

    • 谢谢,但我意识到如果我要发布到我控制的视图,这很简单。关键是,当表单提交时,用户必须被带到另一个站点。你说过remote_post。在服务器上这样做很好,但我不能向客户端发出“后重定向”,所以除非我想重新实现他们的业务逻辑(我不想),否则我不得不将表单的操作设置为他们的网站。
    • “我无法向客户端发出“后重定向””。那没有必要。您可以通过urllib2 提交表单,获取响应,然后将其他站点的响应页面从您的应用程序下载到浏览器。重定向似乎已经发生。这样做最困难的部分是确保远程站点的 cookie 包含在向用户浏览器的传输中。您正在构建“中间人”攻击。
    • 我已经对此进行了调查,但不幸的是我不能。响应包含相对资产 URL,因此我必须编写一个解析器来将响应中的所有路径转换为其域上的绝对 URL。 “然后随便下载其他站点的响应页面”不是这样的!
    • “我无法向客户端发出“后重定向””不太可能是真的。您提交表单,收集 cookie,使用包含 cookie 的重定向进行响应,并在远程站点上执行正常的 GET。从理论上讲,远程站点可能非常糟糕,以至于无法执行合理的 GET,但是,在这种情况下,我没有更多建议。
    • 我要和 adamnfish 一起去;我使用的支付网关支持称为“别名网关”的功能,其中 CC 详细信息直接发送到网关。在这种情况下,没有(合法的)方式可以代表用户提交表单。
    猜你喜欢
    • 1970-01-01
    • 2021-09-09
    • 2020-09-16
    • 2016-02-02
    • 2013-01-31
    • 2017-05-05
    • 2015-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多