Django 中基于类的通用视图的问题和解决方法

62 阅读2分钟

在 Django 中使用基于类的通用视图时,我遇到一个奇怪的问题:某个视图的初始数据似乎会在不同的视图和请求之间持续存在,直到服务器重新启动。我可以将问题简化为以下代码:

huake2_00017_.png

# models.py
from django.db import models

class Foo(models.Model):
    bug = models.CharField(max_length=10)

# forms.py
from django import forms
from .models import Foo

class CreateForm(forms.ModelForm):
    class Meta:
        model = Foo

class UpdateForm(forms.ModelForm):
    class Meta:
        model = Foo

    def __init__(self, *args, **kwargs):
        kwargs.setdefault('initial', {})
        kwargs['initial'].update({'bug': 'WHY??'})
        super(UpdateForm, self).__init__(*args, **kwargs)

# views.py
from django.views.generic.edit import CreateView, UpdateView
from .forms import CreateForm, UpdateForm
from .models import Foo

class FooCreateView(CreateView):
    form_class = CreateForm
    template_name = 'foobar/foo_form.html'

create = FooCreateView.as_view()

class FooUpdateView(UpdateView):
    form_class = UpdateForm
    template_name = 'foobar/foo_form.html'
    queryset = Foo.objects.all()

update = FooUpdateView.as_view()

# urls.py
from django.conf.urls.defaults import *

urlpatterns = patterns('foobar.views',
    ('^$', 'create'),
    (r'^(?P<pk>\d+)/$', 'update'),
)

# foo_form.html
<form action="" method="post">
{{ form.as_p }}
<input type="submit" />
{% csrf_token %}
</form>

如果按照以下步骤操作,就可以复现这个问题:

  1. 将 foobar 应用程序添加到 settings.INSTALLED_APPS
  2. 运行 syncdb
  3. foobar.urls 添加到根 URL 配置文件中。
  4. 导航到 /foobar/(实际的 URL 取决于根 URL 配置文件)。
  5. 提交表单(从而创建一个新的 Foo 对象)。
  6. 导航到 /foobar/1/。注意表单字段已预先填充(这是预期的)。
  7. 导航到 /foobar/。注意,表单字段仍然填充(这是意外的)。

问题是,某个视图的初始数据似乎会在不同的视图和请求之间持续存在,直到服务器重新启动。

2、解决方案

这个问题的原因是,我们在 UpdateForm 类中直接修改了传递给 __init__() 方法的 kwargs 字典,而这个字典来自视图类中的类级属性。所以,当我们修改 kwargs 字典时,实际上是修改了视图类的类级属性,导致这种持续存在的问题。

要解决这个问题,我们可以通过复制 kwargs 字典,然后对副本进行更新,而不直接修改 kwargs 字典。修改后的代码如下:

# forms.py
from django import forms
from .models import Foo

class CreateForm(forms.ModelForm):
    class Meta:
        model = Foo

class UpdateForm(forms.ModelForm):
    class Meta:
        model = Foo

    def __init__(self, *args, **kwargs):
        initial_defaults = {'bug': 'no'}
        initial_defaults.update(kwargs.get('initial', {}))
        defaults = kwargs.copy()
        defaults['initial'] = initial_defaults 
        super(UpdateForm, self).__init__(*args, **defaults)

这样就可以保证不会直接修改视图类的类级属性,从而解决这个问题。