Django表单违反了MVC吗?


16

我刚开始使用来自Spring MVC多年的Django,并且表单实现有点疯狂。如果您不熟悉,Django表单会以定义您的字段的表单模型类开始。Spring同样从表单支持对象开始。但是,在Spring提供用于将表单元素绑定到JSP中的后备对象的taglib的地方,Django却具有直接绑定到模型的表单小部件。有默认的小部件,您可以在其中向您的字段添加样式属性以应用CSS或将完全自定义的小部件定义为新类。所有这些都在您的python代码中。对我来说这似乎很疯狂。首先,您将有关视图的信息直接放入模型中,其次将模型绑定到特定视图。我想念什么吗?

编辑:一些示例代码按要求。

Django:

# Class defines the data associated with this form
class CommentForm(forms.Form):
    # name is CharField and the argument tells Django to use a <input type="text">
    # and add the CSS class "special" as an attribute. The kind of thing that should
    # go in a template
    name = forms.CharField(
                widget=forms.TextInput(attrs={'class':'special'}))
    url = forms.URLField()
    # Again, comment is <input type="text" size="40" /> even though input box size
    # is a visual design constraint and not tied to the data model
    comment = forms.CharField(
               widget=forms.TextInput(attrs={'size':'40'}))

春季MVC:

public class User {
    // Form class in this case is a POJO, passed to the template in the controller
    private String firstName;
    private String lastName;
    get/setWhatever() {}
}

<!-- JSP code references an instance of type User with custom tags -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!-- "user" is the name assigned to a User instance -->
<form:form commandName="user">
      <table>
          <tr>
              <td>First Name:</td>
              <!-- "path" attribute sets the name field and binds to object on backend -->
              <td><form:input path="firstName" class="special" /></td>
          </tr>
          <tr>
              <td>Last Name:</td>
              <td><form:input path="lastName" size="40" /></td>
          </tr>
          <tr>
              <td colspan="2">
                  <input type="submit" value="Save Changes" />
              </td>
          </tr>
      </table>
  </form:form>

“直接在模型中提供有关视图的信息”?请具体说明。“将模型绑定到特定视图”?请具体说明。请提供具体的具体示例。像这样的一般挥舞着的抱怨很难理解,反而很难回应。
S.Lott

我还没有建立任何东西,只是阅读文档。您可以直接在Python代码中将HTML小部件和CSS类绑定到Form类。这就是我要说的。
jiggy

您还想在哪里进行此绑定?请提供您反对的特定示例,链接或报价。假设的论点很难理解。
S.Lott

是的 看一下Spring MVC是如何做到的。您将表单支持对象(如Django Form类)注入到视图中。然后,使用taglibs编写HTML,以便可以正常设计HTML,只需添加一个path属性即可将其绑定到表单支持对象的属性。
jiggy

更新问题以使其完全清楚您所反对的内容。这个问题很难理解。它没有示例代码可以使您的观点完全清楚。
S.Lott

Answers:


21

是的,从MVC角度来看,Django表单是一团糟,假设您正在开发大型MMO超级英雄游戏,并且正在创建Hero模型:

class Hero(models.Model):
    can_fly = models.BooleanField(default=False)
    has_laser = models.BooleanField(default=False)
    has_shark_repellent = models.BooleanField(default=False)

现在,要求您为其创建一个表单,以便MMO玩家可以输入其英雄超级能力:

class HeroForm(forms.ModelForm):
    class Meta:
        model = Hero

由于“驱蚊剂”是一种非常强大的武器,您的老板要求您限制它。如果英雄拥有鲨鱼驱避剂,那么他将无法飞行。大多数人所做的只是以干净的形式添加此业务规则,然后将其命名为“一天”:

class HeroForm(forms.ModelForm):
    class Meta:
        model = Hero

    def clean(self):
        cleaned_data = super(HeroForm, self).clean()
        if cleaned_data['has_shark_repellent'] and cleaned_data['can_fly']:
            raise ValidationError("You cannot fly and repel sharks!")

这种模式看起来很酷,并且可能适用于小型项目,但是根据我的经验,在具有多个开发人员的大型项目中很难维持这种模式。问题在于该表单是MVC 视图的一部分。因此,您每次都必须记住该业务规则:

  • 编写另一个处理Hero模型的表格。
  • 编写一个脚本,从另一个游戏中导入英雄。
  • 在游戏机制中手动更改模型实例。
  • 等等

我的意思是,forms.py与表单布局和表示有关,除非您喜欢弄乱意大利面条代码,否则切勿在该文件中添加业务逻辑。

处理英雄问题的最佳方法是使用模型清除方法以及自定义信号。模型清理的工作方式类似于表单清理,但是它存储在模型本身中,每当清理HeroForm时,它都会自动调用Hero清理方法。这是一个好习惯,因为如果其他开发人员为Hero编写其他表格,他将免费获得驱蚊/飞行验证。

clean的问题在于仅当Model被表单修改时才调用它。当您手动保存()时,它不会被调用,并且您最终可能在数据库中遇到一个无效的英雄。为了解决这个问题,您可以将此侦听器添加到您的项目中:

from django.db.models.signals import pre_save

def call_clean(sender, instance, **kwargs):
    instance.clean()
pre_save.connect(call_clean, dispatch_uid='whata')

这将在所有模型的每个save()调用中调用clean方法。


这是一个非常有用的答案。但是,我的许多表格和相应的验证都涉及多个模型上的多个字段。我认为这是一个非常重要的考虑因素。您将如何对模型的一种干净方法执行这种验证?
Bobort 2016年

8

您正在混合整个堆栈,涉及多个层:

  • Django模型定义了数据结构。

  • Django表单是定义HTML表单,字段验证和Python / HTML值转换的快捷方式。并非严格需要它,但通常很方便。

  • Django ModelForm是另一个快捷方式,简而言之是Form子类,该子类从Model定义获取其字段。对于使用表单将数据输入数据库的常见情况,这只是一种便捷的方法。

最后:

  • Django架构师并不完全遵守MVC结构。有时他们称其为MTV(模型模板视图);因为没有像控制器这样的东西,并且模板(仅表示形式,没有逻辑)和视图(仅面向用户的逻辑,没有HTML)之间的分离与模型的隔离同样重要。

有些人认为这是异端。但是请务必记住,MVC最初是为GUI应用程序定义的,并且它对于Web应用程序而言相当笨拙。


但是小部件是表示形式,它们直接连接到您的表单中。当然,我不能使用它们,但是您失去了绑定和验证的好处。我的观点是,Spring为您提供了绑定和验证,以及模型和视图的完全分离。我认为Django可以轻松实现类似的功能。我将url配置视为一种内置的前端控制器,这是Spring MVC的一种非常流行的模式。
jiggy

最短的代码获胜。
凯文·克莱恩

1
@jiggy:表单是演示的一部分,绑定和验证仅适用于用户输入的数据。这些模型具有自己的绑定和验证,与表单分开且独立。该的ModelForm只是当他们是1的快捷方式:1(或接近)
哈维尔

只是一点点的注意,是的,MVC在Web应用程序中并没有真正意义……直到AJAX重新将其放回去。
AlexanderJohannesen

表单显示为视图。表单验证是控制者。表单数据是模型。海事组织,至少。Django将它们全部拼凑在一起。除了Pedantry之外,这意味着如果您雇用专门的客户端开发人员(如我的公司那样),那么整个事情就没有用了。
jiggy

4

我正在回答这个旧问题,因为其他答案似乎避免了所提到的特定问题。

Django表单使您可以轻松编写少量代码,并使用合理的默认值创建表单。大量的自定义会很快导致“更多的代码”和“更多的工作”,并且在某种程度上抵消了表单系统的主要好处

模板库(如django-widget-tweaks)使自定义表单变得更加容易。希望通过香草Django安装最终可以轻松实现此类自定义。

您的django-widget-tweaks示例:

{% load widget_tweaks %}
<form>
  <table>
      <tr>
          <td>Name:</td>
          <td>{% render_field form.name class="special" %}</td>
      </tr>
      <tr>
          <td>Comment:</td>
          <td>{% render_field form.comment size="40" %}</td>
      </tr>
      <tr>
          <td colspan="2">
              <input type="submit" value="Save Changes" />
          </td>
      </tr>
  </table>


1

(我使用斜体来表示MVC概念,以使其更加清晰。)

不,我认为它们不会破坏MVC。在使用Django模型/表单时,可以将其视为将整个MVC堆栈用作模型

  1. django.db.models.Model是基本模型(保存数据和业务逻辑)。
  2. django.forms.ModelForm提供与进行交互的控制器django.db.models.Model
  3. django.forms.Form(通过继承提供django.forms.ModelForm)是您与之交互的视图
    • 由于确实ModelFormForm,这会使事情变得模糊,因此这两层是紧密耦合的。我认为,这样做是为了简化我们的代码,并在Django开发人员的代码中重新使用代码。

这样,django.forms.ModelForm(凭借其数据和业务逻辑)就成为了模型本身。您可以将其称为(MVC)VC,这是OOP中非常常见的实现。

以Django的django.db.models.Model类为例。当我们查看django.db.models.Model对象时,即使模型已经是MVC的完整实现,也可以看到模型。假设MySQL是后端数据库:

  • MySQLdb模型(数据存储层,以及有关如何与数据交互/验证数据的业务逻辑)。
  • django.db.models.queryController(处理来自View的输入并将其转换为Model)。
  • django.db.models.Model视图(用户与之交互)。
    • 在这种情况下,开发人员(您和我)是“用户”。

使用yourproject.forms.YourForm(继承自django.forms.ModelForm)对象时,此交互方式与您的“客户端开发人员”相同:

  • 因为我们需要知道如何与交互django.db.models.Model,所以他们需要知道如何与yourproject.forms.YourForm(与其模型)交互。
  • 就像我们不需要知道的那样MySQLdb,您的“客户端开发人员”不需要了解任何信息yourproject.models.YourModel
  • 在这两种情况下,我们都很少需要担心Controller的实际实现方式。

1
而不是讨论语义,我只想将HTML和CSS保留在模板中,而不必将其放在.py文件中。抛开哲学,这只是我要实现的实际目标,因为它更有效并且可以更好地分工。
吉吉

1
那仍然是完全可能的。您可以在模板中手动编写字段,在视图中手动编写验证,然后手动更新模型。但是Django表单的设计并没有破坏MVC。
Jack M.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.