【问题标题】:Associating a given django model with it's corresponding create/edit form将给定的 django 模型与其相应的创建/编辑表单相关联
【发布时间】:2012-07-26 20:47:52
【问题描述】:

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


我正在开发 django 任务/项目管理站点,该站点将存储许多产品组的任务。导航至mysite/<ProductGroup>/create_task.html 应将用户引导至用于在该产品组内创建任务/项目的表单。默认情况下,这将允许使用简单的ModelForm 编辑基本Task 模型实例。但是,对于特定的产品组,我希望选择将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 属性(例如 SalesTask.form = 'SalesTaskForm'),轻松扩展给定产品组的任务模型,然后设置ProductGroup 的销售实例的 task_type 外键。

生成的create_task 视图函数可以智能地为给定产品组呈现适当的表单:

<views.py>
...
import mysite.forms as taskforms
...
def create_task(request, name):
    try:
        product_group = ProductGroup.object.get(product_group_iexact=name)
    except ProductGroup.DoesNotExist:
        raise Http404
if request.method == 'POST':
    task_model = product_group.item_type.model_class()
    try:
        form = taskforms.__getattribute__(task_model.form)
    except AttributeError:
        raise Http404

    if form.is_valid():
        # Process form
    ...

这似乎可行,我对解决方案并不不满,但将表单与给定模型相关联似乎是一种常见的需求——对于 django 来说相对较新——我想知道是否有内置的-in 或更雄辩的方法来处理这个问题?

提前致谢。

【问题讨论】:

  • 为什么要把表单弄得这么复杂:form = taskforms.__getattribute__(task_model.form)?据我了解,您在这一行构造了一个具有某种类型的表单。
  • @sergzach 不确定我是否遵循(尽管如果我缺少更简单的方法,我肯定很感兴趣)。为了确保在同一页面上:task_model.form 是存储在Task 模型(或其子类)中的字符串,让我知道Task 的给定子类使用什么形式。为了使 Task 模型的所有子类的视图通用,我需要这一行来加载适当的表单类型。
  • 传统的方式是为每个模型创建一个单独的表单。这不是 DRY 的问题,从概念上讲,它是模型的输入扩展。当您创建一个新表单时,您将其分配给一个模型。模型不应包含对其形式的任何引用,这违反了 Django 的封装。模型不知道他们的形式。
  • @sergzach 我明白你的意思。实际上,Task 模型的所有子类都有自己的形式(以显示它们的附加字段)。对于大多数产品组,基本型号 TaskTaskForm 就足够了。但我想避免对我的视图函数进行特殊封装,以便使用Task 的子类为所有产品组显示适当的形式。我想我想要做的可能是非标准的,所以可能没有任何内置方法。就我个人而言,我觉得这个实现对我来说更容易维护,但也许我正在为未来的意外问题做好准备?
  • 是的,如果您的代码变得更加困难,就会出现意料之外的问题。如果你走这条路,你应该准备好用非标准方法解决简单的标准任务。我认为是时候提醒一条基本的 Python 规则了:“特殊情况并不足以打破规则。”。为什么不考虑将视图实现为类并应用类继承以避免重复行?你可以想出一些非常紧凑但又不违反规则的东西。

标签: django django-forms


【解决方案1】:

基于 sergzach 的 cmets,我意识到我应该放弃当前的解决方案并改用基于类的通用视图。我找不到太多关于基于内置类的视图的文档(除了简单的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 将自动使用model_form_factory 生成一个通用的ModelForm。因此,如果我不想要任何自定义,我什至不必创建相应的模型表单。因此,如果说销售组想要包含一个额外的字段budget,我可以简单地将基础Task 模型子类化为SalesTask,将budget 字段添加到这个子类中,然后设置task_type 外键指向SalesTask 而不是Task。非常漂亮!

【讨论】:

  • 您可以不使用 Django 的视图类来创建自定义类。在某些情况下,它是更方便的解决方案。使用装饰器 @classmethod 实现 as_view 函数。在 as_view 方法中,您可以构建您的类,然后将其分配给 self:def as_view(cls):\self = cls()\return self.view。然后就可以在urls.py中使用as_view函数了: url( r'^$', MyView.as_view() ),
  • 没有多余的基类的完整例子:class View(object):\@classmethod\def as_view(cls):\self = cls()\return self.pre_view\def pre_view(self , request, *args, **kwargs ): \ return self.view( request, *args, **kwargs ) \ def view( self, request ): \ raise EmptyViewException() \ def _redirect( self, page ): \返回重定向(页面)\ def raise404(自我):\ raise Http404 \
猜你喜欢
  • 2023-03-17
  • 2013-12-10
  • 1970-01-01
  • 1970-01-01
  • 2014-12-14
  • 2018-11-30
  • 1970-01-01
  • 2014-09-17
  • 2010-11-09
相关资源
最近更新 更多