Django的Form类是否维护状态?

1 投票
5 回答
835 浏览
提问于 2025-04-15 13:35

我正在用Django构建我的第一个表单,结果遇到了一些我完全没想到的情况。我定义了一个表单类:

class AssignmentFilterForm(forms.Form):
filters = []
filter = forms.ChoiceField()

def __init__(self, *args, **kwargs):
    super(forms.Form, self).__init__(*args, **kwargs)
    self.filters.append(PatientFilter('All'))
    self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
    self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))

    for i, f in enumerate(self.filters):
        self.fields["filter"].choices.append((i, f.name))

当我用以下方式把这个表单输出到模板时:

{{ form.as_p }}

我看到的选项是正确的。但是,刷新页面后,我发现下拉框里的选项出现了三次。再刷新一次,选项就变成十次了!

这是我的视图代码:

@login_required
def assign_test(request):
pg = PhysicianGroup.objects.get(pk=physician_group)

if request.method == 'POST':
    form = AssignmentFilterForm(request.POST)
    if form.is_valid():
        yes = False
else:
    form = AssignmentFilterForm()
    patients = pg.allPatients().order_by('bed__room__unit', 'bed__room__order', 'bed__order' )

return render_to_response('hospitalists/assign_test.html', RequestContext(request,  {'patients': patients, 'form': form,}))

我哪里做错了呢?

谢谢,Pete

5 个回答

1

为了更清楚地说明一些其他回答的内容:

这些字段必须是类变量。因为它们会被元类进行各种操作,所以这样定义是正确的。

不过,你的 filters 变量不一定要是类变量。它完全可以是实例变量——只需要把定义从类中移除,放到 __init__ 方法里就行。或者,更好的做法是,根本不把它当作属性,而是直接在 __init__ 方法中定义为一个局部变量。这样的话,你就不需要往 filters.choices 中添加内容,而是直接重新赋值就可以了。

def __init__(self, *args, **kwargs):
        super(forms.Form, self).__init__(*args, **kwargs)
        filters = []
        filters.append(PatientFilter('All'))
        filters.append(PatientFilter('Assigned', 'service__isnull', False))
        filters.append(PatientFilter('Unassigned', 'service__isnull', True))

        self.fields["filter"].choices = [(i, f.name) for i, f in enumerate(filters)]    
7

其实这是Python的一个特性,很多人都会被这个搞糊涂。

当你在类里面定义变量,比如用 filters = [] 这样的方式时,等于是在类最开始定义的时候就计算了右边的内容。所以当你的代码第一次运行时,它会在内存中创建一个新的列表,并返回这个列表的引用。结果就是,每个 AssignmentFilterForm 的实例都有自己的filters变量,但它们都指向同一个内存中的列表。要解决这个问题,只需要把 self.filters 的初始化放到 __init__ 方法里面。

大多数情况下,你不会遇到这个问题,因为你使用的类型并不是以引用的方式存储。比如数字、布尔值等都是以它们的值存储的。字符串是以引用的方式存储的,但字符串是不可变的,这意味着每次改变字符串时,内存中必须创建一个新的字符串,并返回一个新的引用。

在脚本语言中,指针不常出现,所以当它们出现时,往往会让人感到困惑。

下面是一个简单的IDLE会话示例,来展示发生了什么

>>> class Test():
    myList = []
    def __init__( self ):
        self.myList.append( "a" )


>>> Test.myList
[]
>>> test1 = Test()
>>> Test.myList
['a']
>>> test1.myList
['a']
>>> test2 = Test()
>>> test2.myList
['a', 'a']
>>> test1.myList
['a', 'a']
>>> Test.myList
['a', 'a']
2

我看了本书《Pro Django》,里面回答了这个问题。这本书非常好,我强烈推荐!

解决办法是把选择字段和我的辅助变量都设置为实例变量:

class AssignmentFilterForm(forms.Form):
def __init__(self, pg, request = None):
    super(forms.Form, self).__init__(request)
    self.filters = []

    self.filters.append(PatientFilter('All'))
    self.filters.append(PatientFilter('Assigned', 'service__isnull', False))
    self.filters.append(PatientFilter('Unassigned', 'service__isnull', True))
    self.addPhysicians(pg)

    self.fields['filter'] = forms.ChoiceField()
    for i, f in enumerate(self.filters):
        self.fields['filter'].choices.append((i, f.name))

清空选择项是可行的,但肯定会导致线程问题。

撰写回答