我正在用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 }}
我看到了正确的选择。但是,刷新页面后,我在选择框中看到列表三次。再次点击刷新会导致列表在选择框中显示10次!
以下是我的观点:
@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,}))
我做错了什么?
谢谢,皮特
答案 0 :(得分:7)
这实际上是Python的一个功能,可以吸引很多人。
当您使用filters = []
定义类的变量时,在最初定义类时会计算表达式的右半部分。因此,当您的代码首次运行时,它将在内存中创建一个新列表并返回对此列表的引用。因此,每个AssignmentFilterForm
实例都有自己的过滤器变量,但它们都将指向内存中的相同列表。要解决此问题,只需将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']
答案 1 :(得分:1)
您将附加到PER-CLASS变量self.filters。通过在self.filters = []
开头__init__
,将其变为PER-INSTANCE变量。
答案 2 :(得分:1)
我拿起了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))
清除选择有效,但肯定会导致线程问题。
答案 3 :(得分:0)
如上所述,您需要将过滤器初始化为实例变量:
def __init__(...):
self.filters = []
self.filters.append(...)
# ...
如果您想了解有关Form类如何工作的更多信息,您应该在Django wiki中阅读此页面:
它讨论了Model类的内部,但是你会发现字段的一般设置有点类似于Form(减去数据库的东西)。它有点过时(2006年),但我认为基本原则仍然适用。如果你是新手,那么元类的东西可能有点令人困惑。
答案 4 :(得分:0)
从其他一些答案中澄清:
字段是,而且必须是类变量。他们通过元类得到各种各样的事情,这是定义它们的正确方法。
但是,您的filters
变量不需要是类var。它很容易成为一个实例var - 只需从类中删除定义并将其放在__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)]