DjangoフォームはMVCに違反していますか?


16

私は、長年のSpring MVCから来たDjangoと仕事を始めたばかりで、フォームの実装は少しおかしいと感じています。慣れていない場合、Djangoフォームは、フィールドを定義するフォームモデルクラスから始まります。同様に、Springはフォームをバッキングするオブジェクトから始まります。ただし、Springがフォーム要素をJSP内のバッキングオブジェクトにバインドするためのtaglibを提供している場合、Djangoにはモデルに直接関連付けられたフォームウィジェットがあります。CSSを適用したり、完全にカスタムウィジェットを新しいクラスとして定義するためにフィールドにスタイル属性を追加できるデフォルトのウィジェットがあります。それはすべてあなたのpythonコードに含まれています。それは私には馬鹿げているようです。最初に、ビューに関する情報をモデルに直接入力し、次にモデルを特定のビューにバインドします。何か不足していますか?

編集:要求されたいくつかのサンプルコード。

ジャンゴ:

# 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'}))

Spring 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

まだドキュメントを読んでいないので、まだ何も作成していません。HTMLコードとCSSクラスを、Pythonコードで直接Formクラスにバインドします。それが私が言っていることです。
ジギー

他にどこでこのバインディングをしたいですか?あなたが反対している特定の事柄への例、リンク、または引用を提供してください。仮説的な議論を理解するのは困難です。
-S.ロット

やった。Spring MVCの仕組みをご覧ください。フォームにバッキングオブジェクト(Django Formクラスなど)をビューに挿入します。次に、taglibを使用してHTMLを記述し、通常どおりHTMLを設計し、フォームバッキングオブジェクトのプロパティにバインドするパス属性を追加します。
ジギー

してください更新、それは完全にあなたがに反対しているものを明確にする質問を。質問を追うのは難しいです。ポイントを完全に明確にするサンプルコードはありません。
-S.ロット

回答:


21

はい、DjangoフォームはMVCの観点から見ると混乱しています。大きなMMOスーパーヒーローゲームで作業していて、ヒーローモデルを作成しているとします。

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

サメ忌避剤は非常に強力な武器であるため、上司から制限を求められました。ヒーローがサメ忌避剤を持っている場合、彼は飛ぶことができません。ほとんどの人が行うことは、このビジネスルールをフォームに単に追加して、1日と呼ぶだけです。

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 cleanメソッドを呼び出します。これは、別の開発者がヒーローのために別のフォームを作成した場合、無料で忌避剤/ハエの検証を取得するため、これは良い習慣です。

クリーンの問題は、モデルがフォームによって変更されたときにのみ呼び出されることです。手動でsave()した場合は呼び出されず、データベース内の無効なヒーローで終わる可能性があります。この問題に対処するには、このリスナーをプロジェクトに追加します。

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メソッドが呼び出されます。


これは非常に役立つ答えです。ただし、フォームとそれに対応する検証の多くには、いくつかのモデルのいくつかのフィールドが含まれます。これは非常に重要な考慮すべきシナリオだと思います。モデルのクリーンメソッドの1つでこのような検証をどのように実行しますか?
ボボート

8

スタック全体を混合しているため、いくつかのレイヤーが関係しています。

  • Djangoモデルはデータ構造を定義します。

  • Djangoフォームは、HTMLフォーム、フィールド検証、Python / HTML値の翻訳を定義するためのショートカットです。厳密には必要ではありませんが、しばしば便利です。

  • Django ModelFormはもう1つのショートカットです。要するに、Model定義からフィールドを取得するFormサブクラスです。フォームを使用してデータベースにデータを入力する一般的な場合に便利な方法です。

そして最後に:

  • Djangoのアーキテクトは、MVC構造に厳密に従っていません。MTV(Model Template View)と呼ばれることもあります。コントローラーなどは存在せず、テンプレート(プレゼンテーションのみ、ロジックなし)とビュー(ユーザー向けロジック、HTMLなし)の分割は、モデルの分離と同じくらい重要だからです。

一部の人々はそれを異端だと見ています。しかし、MVCはもともとGUIアプリケーション用に定義されたものであり、Webアプリにはやや不適当であることを覚えておくことが重要です。


ただし、ウィジェットはプレゼンテーションであり、フォームに直接配線されます。確かに、私はそれらを使用することはできませんが、バインドと検証の利点を失います。私のポイントは、Springがモデルとビューのバインディングと検証、および完全な分離を提供することです。Djangoは似たようなものを簡単に実装できたと思います。また、URL構成は、Spring MVCで非常に人気のあるパターンである、組み込みのフロントコントローラーの一種と考えています。
ジギー

最短のコードが優先されます。
ケビンクライン

1
@jiggy:フォームはプレゼンテーションの一部であり、バインディングと検証はユーザーが入力したデータ専用です。モデルには、フォームとは別に独立した独自のバインディングと検証があります。モデルフォームは、1:1(またはほぼ)の場合のショートカットに過ぎません
ハビエル

はい、MVCはWebアプリでは本当に意味がありませんでした... AJAXが再び元に戻るまで、ちょっとだけ注意してください。
アレクサンダー

フォーム表示はビューです。フォーム検証はコントローラーです。フォームデータはモデルです。少なくともIMO。Djangoはそれらすべてをまとめてつぶします。余談ですが、(私の会社がそうであるように)専用のクライアント側開発者を雇うと、このすべてがまったく役に立たなくなることを意味します。
ジギー

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 Models / Formsを使用する場合、MVCスタック全体をModelとして使用すると考えてください。

  1. django.db.models.Model基本モデルです(データとビジネスロジックを保持します)。
  2. django.forms.ModelFormとやり取りするためのControllerを提供しますdjango.db.models.Model
  3. django.forms.Form(による継承によって提供されるdjango.forms.ModelForm)は、対話するビューです。
    • ので、これは、メイクのものがぼやけていModelFormているForm二つの層が密結合されているので、。私の意見では、これはコードを簡潔にするためと、Django開発者のコ​​ード内でコードを再利用するために行われました。

このようにして、django.forms.ModelForm(データとビジネスロジックと共に)モデル自体になります。これを(MVC)VCとして参照できます。これは、OOPでかなり一般的な実装です。

たとえば、Djangoのdjango.db.models.Modelクラスを取り上げます。django.db.models.Modelオブジェクトを見ると、既にMVCの完全な実装であるにもかかわらず、Modelが表示されます。MySQLがバックエンドデータベースであると仮定します。

  • MySQLdbあるモデル(と/検証データのやり取りする方法については、データ記憶層、およびビジネスロジックが)。
  • django.db.models.queryあるコントローラー(ハンドルからの入力を表示し、それを翻訳モデル)。
  • django.db.models.Modelあるビュー(ユーザーが対話する何が)。
    • この場合、開発者(あなたと私)は「ユーザー」です。

この操作は、オブジェクトを操作yourproject.forms.YourForm(継承django.forms.ModelForm)する場合の「クライアント側開発者」と同じです。

  • 相互作用する方法を知る必要があるのでdjango.db.models.Model、彼らは相互作用する方法yourproject.forms.YourForm(彼らのModel)を知る必要があります。
  • について知る必要はないのでMySQLdb、「クライアント側の開発者」はについて何も知る必要はありませんyourproject.models.YourModel
  • どちらの場合も、Controllerが実際にどのように実装されるかを心配する必要はほとんどありません。

1
セマンティクスを議論するのではなく、テンプレートにHTMLとCSSを保持したいだけで、それらを.pyファイルに入れる必要はありません。哲学は別として、それはより効率的で、より良い分業を可能にするので、それは私が達成したいただの実際的な目的です。
ジギー

1
それはまだ完全に可能です。テンプレートにフィールドを手動で記述し、ビューに検証を手動で記述してから、モデルを手動で更新できます。しかし、Djangoのフォームの設計はMVCを破壊しません。
ジャック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.