将给定的django模型与其相应的创建/编辑表单相关联

时间:2012-07-26 20:47:52

标签: django django-forms

tl; dr :我正在寻找一种简单的方法来将模型的形式与给定的模型相关联,这样只知道我可以渲染适当的创建/编辑形式的模型。我已经通过在Model类中将相应的ModelForm的名称存储为字符串属性来开发了一个解决方案,但是对于django来说是新手,我很好奇这是否是首选解决方案。


我正在研究django任务/项目管理网站,该网站将存储许多产品组的任务。导航到mysite/<ProductGroup>/create_task.html应该将用户定向到用于在该产品组中创建任务/项目的表单。默认情况下,这将允许使用简单的Task编辑基本ModelForm模型实例。但是,对于特定的产品组,我希望可以选择子类化Task模型(示例SalesTask)并显示特定于该子类的ModeForm(例如SalesTaskForm )。

我目前的解决方案是将任务对象类型存储为ProductGroup模型中的内容类型,例如:

class ProductGroup(models.Model):
    task_type = models.ForeignKey(ContentType)
    ...
    <define other fields here>

然后使用特殊字符串属性定义基本任务模型,在渲染时使用相应的ModelForm,例如:

<models.py>
class Task(models.Model):
    product_group = models.ForeignKey(ProductGroup)
    ...
    <define task fields common to all Task subclasses>
    ...
    # Associate model with a form (regular python class attribute, 
    # not a django field)
    form = 'TaskForm'

<forms.py>
class TaskForm(ModelForm):
    class Meta:
        model = Task

*Note that it would be slightly more convenient if I could set Task.form equal 
to the actual TaskForm(ModelForm) class rather than a string, but I couldn't 
get around the circular imports when trying this route (models.py `Task` 
would need to import `Taskorm` from forms.py, which itself needs to import 
`Task`).*

此设置允许我通过简单地继承TaskTaskForm来轻松扩展给定产品组的任务模型,从而覆盖模型子类定义上的Task.form属性(例如{{ 1}})然后为SalesTask.form = 'SalesTaskForm'的销售实例设置task_type外键。

生成的ProductGroup视图函数可以智能地呈现给定产品组的相应表单:

create_task

这似乎有效,我对这个解决方案并不满意,但似乎需要将一个表格与一个给定的模型联系起来 - 并且对django来说相对较新 - 我想知道是否有一个内置的 - 或者更有说服力的处理方法?

提前致谢。

1 个答案:

答案 0 :(得分:0)

根据sergzach的评论,我意识到我应该放弃现有的解决方案,转而使用基于类的通用视图。我找不到很多关于内置的基于类的视图的文档(除了简单的TemplateView示例),但是挖掘django.views.gereic.edit的来源会显示CreateView视图类,它提供了非常有说服力的解决方案。

如果您想在url.conf中指定template_namemodel作为参数,则可以直接导入并使用CreateView。对于我的情况,我仍然想从url的正则表达式中获取product_group,然后使用给定产品组上的ContentType字段来获取相应的模型。所以我的ProductGroup类定义与上面的相同,我的url.py变为:

from mysite.views import CreateTask
urlpatterns = patterns('',
    ...
    url(r'^product_groups/(?P<product_group>[\w-]+)/new_task$', 
        CreateTask.as_view(),
        name='create_task'),
    ...

然后在views.py中,我只是将CreateView子类化并覆盖它的get_form_class方法,以从捕获的产品组中获取相应的模型:

class CreateTask(CreateView):
    template_name = "item_form.html"
    def get_form_class(self):
        """
        Returns the form class to use in this view
        """
        if self.form_class:
        # If we pass form_class as an arg, use that
                return self.form_class
        if self.kwargs['product_group']:
        # Otherwise, get the product_group from the url regex, get its associated
        # task model subclass and have the form_facory generate a form_class
            try:
                product_group = ProductGroup.objects.get(product_group__iexact=
                        self.kwargs['product_group'])
            except ProductGroup.DoesNotExist:
                raise Http404
            model = product_group.task_type.model_class()
        # The remainder is straight from CreateView's definition:
        else:
                if self.model is not None:
                    # If a model has been explicitly provided, use it
                    model = self.model
                elif hasattr(self, 'object') and self.object is not None:
                    # If this view is operating on a single object, use
                    # the class of that object
                    model = self.object.__class__
                else:
                    # Try to get a queryset and extract the model class
                    # from that
                    model = self.get_queryset().model
        return model_forms.modelform_factory(model)

CreateView类(或从ModelFormMixin继承的任何其他类)的一个非常灵活的特性是,如果模型的相应ModelForm尚未创建或作为参数提供, ModelFormMixin将使用ModelForm自动生成通用model_form_factory。因此,如果我不想进行任何自定义,我甚至不必创建相应的模型表单。因此,如果说销售组想要包含其他字段budget,我可以简单地将基础Task模型子类化为SalesTask,将budget字段添加到此子类中,然后将task_type外键设置为指向SalesTask而不是Task。很光滑!