【问题标题】:Difference between returning modified class and using type()返回修改后的类和使用 type() 的区别
【发布时间】:2010-11-18 02:06:47
【问题描述】:

我想这更像是一个 python 问题而不是 django 问题,但我无法在其他任何地方复制这种行为,所以我将使用无法按预期工作的确切代码。

当我在 django 中处理一些动态表单时,我发现了这个工厂函数 sn-p:

def get_employee_form(employee):
    """Return the form for a specific Board."""
    employee_fields = EmployeeFieldModel.objects.filter(employee = employee).order_by   ('order')
    class EmployeeForm(forms.Form):
        def __init__(self, *args, **kwargs):
            forms.Form.__init__(self, *args, **kwargs)
            self.employee = employee
        def save(self):
            "Do the save"
    for field in employee_fields:
        setattr(EmployeeForm, field.name, copy(type_mapping[field.type]))
    return type('EmployeeForm', (forms.Form, ), dict(EmployeeForm.__dict__))

[来自:http://uswaretech.com/blog/2008/10/dynamic-forms-with-django/]

还有一件事我不明白,为什么返回修改后的 EmployeeForm 不起作用? 我的意思是这样的:

def get_employee_form(employee):
    #[...]same function body as before

    for field in employee_fields:
        setattr(EmployeeForm, field.name, copy(type_mapping[field.type]))
    return EmployeeForm

当我尝试返回修改后的类时,django 忽略了我的附加字段,但返回 type() 的结果效果很好。

【问题讨论】:

    标签: python django class types


    【解决方案1】:

    值得注意的是,这段代码 sn-p 是达到预期目的的一种非常糟糕的方法,并且涉及到对 Django Form 对象的常见误解——Form 对象应该与 HTML 表单一对一地映射。做这样的事情的正确方法(不需要弄乱任何元类魔法)是使用多个 Form 对象和一个inline formset

    或者,如果出于某种奇怪的原因您真的想将内容保存在单个 Form 对象中,只需在 Form 的 __init__ 方法中操作 self.fields。

    【讨论】:

    • formset_factory 创建同一个表单的多个实例并透明地保存它。此示例根据员工部门在表单上显示不同的字段。当您说前者可用于后者时,我不确定我是否理解您的意思。你能详细说明吗?提前致谢。
    • 如果您的意图是显示一个允许编辑部门所有员工姓名的 html 表单,那么内联表单集就是您想要的。但是,如果您想从某个部门显示佣金,而不是为其他部门,这就是这样做的方法。两者都是正交的,可以匹配在一起。
    【解决方案2】:

    Lennart 的假设是正确的:元类确实是罪魁祸首。无需猜测,只需查看the sources:元类是DeclarativeFieldsMetaclass,当前位于该文件的第53 行,并根据类在创建时具有的属性添加属性base_fields 和可能的media。在第 329 行 ff 你看到:

    class Form(BaseForm):
        "A collection of Fields, plus their associated data."
        # This is a separate class from BaseForm in order to abstract the way
        # self.fields is specified. This class (Form) is the one that does the
        # fancy metaclass stuff purely for the semantic sugar -- it allows one
        # to define a form using declarative syntax.
        # BaseForm itself has no way of designating self.fields.
        __metaclass__ = DeclarativeFieldsMetaclass
    

    这意味着用type 创建一个新类有一些脆弱性——提供的黑魔法可能会或可能不会通过!一种更可靠的方法是使用 EmployeeForm 的类型,它将选择可能涉及的任何元类——即:

    return type(EmployeeForm)('EmployeeForm', (forms.Form, ), EmployeeForm.__dict__)
    

    (无需复制 __dict__,顺便说一句)。区别是微妙但重要的:我们不是直接使用type 的3-args 形式,而是使用1-arg 形式来获取表单类的类型(即元类),然后在3-args 形式。

    确实很神奇,但这就是框架的缺点,它使用“纯粹用于语义糖的花哨的元类东西”&c:只要你想做框架支持的事情,你就在三叶草中,但是要摆脱这种支持,即使是一点点也可能需要抵消魔法(这在某种程度上解释了为什么我经常宁愿使用轻量级、透明的设置,例如 werkzeug,而不是像 Rails 或Django 做:我精通黑魔法并不意味着我很乐意在普通的生产代码中使用它……但是,这是另一个讨论;-)。

    【讨论】:

    • 我认为 dict(EmployeeForm.__dict__) 的原因是早期的 forms.Form 用于返回 DictProxy 而不是 dict。
    【解决方案3】:

    我只是用直接的非 django 类尝试了这个,它起作用了。所以这不是 Python 问题,而是 Django 问题。

    在这种情况下(尽管我不是 100% 确定),这是一个 Form 类在类创建期间做了什么的问题。我认为它有一个元类,并且这个元类将在类创建期间完成表单初始化。这意味着您在创建类后添加的任何字段都将被忽略。

    因此您需要创建一个新类,就像使用 type() 语句所做的那样,以便涉及元类的类创建代码,现在使用新字段。

    【讨论】:

      猜你喜欢
      • 2021-05-07
      • 1970-01-01
      • 2014-08-29
      • 2016-04-25
      • 2015-07-17
      • 2021-04-20
      • 2016-12-28
      • 2016-08-13
      • 1970-01-01
      相关资源
      最近更新 更多