如何通过字符串动态实例化Django表单?

时间:2014-08-12 10:34:31

标签: python django forms

我正在尝试为Django编写应用程序。我希望我的用户能够收集某些类型的数据,例如样本,视频等......该应用程序称为收集器,对于每种类型的项目,都有一个类和一个与之相关的表单。

示例类:

class CreateTextView(CreateItemView):    
    form_class = TextForm
    model = Text

    def get_context_data(self, **kwargs):
        context = super(CreateTextView, self).get_context_data(**kwargs)
        context['item_type'] = 'text' 
        return context

示例表单:

class TextForm(forms.ModelForm):
    class Meta:
        model = Text

        fields = COMMON_FIELDS + ('text',)

如您所见,实际视图继承自CreateItemView。我希望为CreateItemView定义尽可能多的功能,这样我就不必为所有项目类别单独执行。这在大多数情况下都是有效的,但是当我尝试使用数据处理表单时,它会变得有点棘手。

def post(self, request, *args, **kwargs):
    form = TextForm(request.POST) # line 2
    form = getattr(TextForm, '__init__')(data=request.POST) # line 3

    if form.is_valid():           
        # Add owner information.
        item = form.save(commit=False)
        item.owner = request.user
        item.save()

        return HttpResponseRedirect(reverse('collector:index'))

    return render(request, self.template_name, {'form': form})

在第2行中,如果只有一种类型的表单,您可以看到我将如何处理表单。第3行是我正在尝试做的事情。我希望能够使用context['item_type']动态选择正确的表单并使用给定的数据对其进行实例化。

现在问题在于__init__ - 我从未在任何地方定义的方法。当我只将POST.request传递给__init__时,它会抱怨没有自己。当我传递额外的self时,它会抱怨CreateTextView没有_meta属性等等。我只是找不到正确的论证组合来满足__init__ - 方法。我也无法查找它的定义,因为我没有定义它。然后我按照django框架中父类的定义,这导致我看到了几个看起来像工厂的复杂函数。这对我没有帮助......

现在我知道如何使用TextForm() - 启动。有没有办法用getattr()动态获取此方法?这样可以省去__init__的麻烦。如果没有,我如何为__init__提供正确的自我实例?


如下所述,我已经改变了我的课程。我不再使用上下文来存储item_type,而是使用类变量来轻松访问视图中的item_type。我的post方法在母类CreateItemView中定义,现在看起来像这样:

def post(self, request, *args, **kwargs):
    try:
        form_cls = ITEM_TYPE_MAP[self.item_type]
    except KeyError:
        # invalid item_type. raise a meaningful error here
        raise Exception('Invalid item type.')
    form = form_cls(request.POST)

    if form.is_valid():           
        # Add owner information.
        item = form.save(commit=False)
        item.owner = request.user
        item.save()

        return HttpResponseRedirect(reverse('collector:index'))

    return render(request, self.template_name, {'form': form})

1 个答案:

答案 0 :(得分:0)

一个干净而且非常简单的解决方案是使用字典将item_type值映射到实际的表单类:

ITEM_TYPE_MAP = {
    "foo": TextForm,
    "bar": SomeOtherForm,
}

你将这个字典放在某个全球的地方,并在控制器中使用它,如下所示:

item_type = context['item_type']
try:
    form_cls = ITEM_TYPE_MAP[item_type]
except KeyError:
    # invalid item_type. raise a meaningful error here
    raise
form = form_cls(request.POST)

你通常不能直接调用__init__,因为实现对象的不仅仅是那个。 Python也会在对象的上调用__new__,所以唯一可以确定的方法是通过调用该类型的实际构造函数。

这是上面发生的事情,首先将类型提取到form_cls然后调用类型(即构造函数)。