Verletzen Django-Formulare die MVC?

16

Ich habe gerade angefangen, mit Django zu arbeiten, der aus den Jahren von Spring MVC stammt, und die Implementierung der Formulare scheint etwas verrückt zu sein. Wenn Sie nicht vertraut sind, beginnt Django Forms mit einer Formularmodellklasse, die Ihre Felder definiert. Der Frühling beginnt in ähnlicher Weise mit einem Formträger. Wo Spring jedoch eine Taglib zum Binden von Formularelementen an das Hintergrundobjekt in Ihrer JSP bereitstellt , sind in Django Formular-Widgets direkt mit dem Modell verknüpft. Es gibt Standardwidgets, mit denen Sie Ihren Feldern Stilattribute hinzufügen können, um CSS anzuwenden, oder vollständig benutzerdefinierte Widgets als neue Klassen definieren können. Es geht alles in Ihrem Python-Code. Das kommt mir verrückt vor. Erstens fügen Sie Informationen zu Ihrer Ansicht direkt in Ihr Modell ein und zweitens binden Sie Ihr Modell an eine bestimmte Ansicht. Vermisse ich etwas?

EDIT: Ein Beispielcode wie gewünscht.

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

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>
jiggy
quelle
"Informationen zu Ihrer Ansicht direkt in Ihrem Modell"? Bitte erläutern. "Ihr Modell an eine bestimmte Ansicht binden"? Bitte erläutern. Bitte nennen Sie konkrete Beispiele. Eine allgemeine, von Hand wehende Beschwerde wie diese ist schwer zu verstehen, geschweige denn zu beantworten.
S.Lott
Ich habe noch nichts gebaut, nur die Dokumente gelesen. Sie binden ein HTML-Widget zusammen mit CSS-Klassen direkt in Python-Code an Ihre Form-Klasse. Das rufe ich heraus.
jiggy
wo sonst willst du diese bindung machen? Bitte geben Sie ein Beispiel oder einen Link oder ein Angebot an die spezifischen Sache Sie widersprechen . Das hypothetische Argument ist schwer zu folgen.
S.Lott
Ich tat. Schau dir an, wie Spring MVC das macht. Sie fügen das Form-Backing-Objekt (wie eine Django-Form-Klasse) in Ihre Ansicht ein. Anschließend schreiben Sie Ihren HTML-Code mit Hilfe von Taglibs, sodass Sie Ihren HTML-Code wie gewohnt gestalten und einfach ein Pfadattribut hinzufügen können, das ihn an die Eigenschaften Ihres Formular-Backing-Objekts bindet.
jiggy
Bitte aktualisieren Sie die Frage, um deutlich zu machen, gegen was Sie Einwände erheben. Die Frage ist schwer zu beantworten. Es hat keinen Beispielcode auf Ihren Punkt machen vollkommen klar.
S.Lott

Antworten:

21

Ja, die Django-Formulare sind aus MVC-Sicht ein Durcheinander. Angenommen, Sie arbeiten in einem großen MMO-Superheld-Spiel und erstellen das Hero-Modell:

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

Nun werden Sie gebeten, ein Formular dafür zu erstellen, damit die MMO-Spieler ihre Helden-Superkräfte eingeben können:

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

Da der Shark Repellent eine sehr mächtige Waffe ist, hat Ihr Chef Sie gebeten, ihn einzuschränken. Wenn ein Held das Shark Repellent hat, kann er nicht fliegen. Die meisten Leute fügen diese Geschäftsregel einfach in das Formular clean ein und nennen es einen Tag:

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!")

Dieses Muster sieht cool aus und funktioniert möglicherweise bei kleinen Projekten, aber meiner Erfahrung nach ist es sehr schwierig, es bei großen Projekten mit mehreren Entwicklern beizubehalten. Das Problem ist, dass das Formular Teil der Ansicht der MVC ist. Sie müssen sich also jedes Mal an diese Geschäftsregel erinnern, wenn Sie:

  • Schreiben Sie ein anderes Formular, das sich mit dem Hero-Modell befasst.
  • Schreiben Sie ein Skript, das Helden aus einem anderen Spiel importiert.
  • Ändern Sie die Modellinstanz manuell während der Spielmechanik.
  • etc.

Mein Punkt hier ist, dass die forms.py alles über das Formularlayout und die Präsentation ist. Sie sollten niemals Geschäftslogik in diese Datei einfügen, es sei denn, Sie genießen es, mit Spaghetti-Code herumzuspielen.

Der beste Weg, um das Heldenproblem zu lösen, ist die Verwendung der Model-Clean-Methode plus eines benutzerdefinierten Signals. Die Modellbereinigung funktioniert wie die Formularbereinigung, ist jedoch im Modell selbst gespeichert, und ruft bei jeder Bereinigung von HeroForm automatisch die Hero-Bereinigungsmethode auf. Dies ist eine gute Vorgehensweise, da ein anderer Entwickler, der ein anderes Formular für den Hero schreibt, die Repellent / Fly-Validierung kostenlos erhält.

Das Problem bei der Bereinigung ist, dass sie nur aufgerufen wird, wenn ein Modell von einem Formular geändert wird. Es wird nicht aufgerufen, wenn Sie es manuell speichern () und Sie können mit einem ungültigen Helden in Ihrer Datenbank enden. Um diesem Problem entgegenzuwirken, können Sie Ihrem Projekt diesen Listener hinzufügen:

from django.db.models.signals import pre_save

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

Dadurch wird die Clean-Methode bei jedem Aufruf von save () für alle Ihre Modelle aufgerufen.

Cesar Canassa
quelle
Dies ist eine sehr hilfreiche Antwort. Viele meiner Formulare und die entsprechende Validierung betreffen jedoch mehrere Felder in mehreren Modellen. Ich denke, das ist ein sehr wichtiges Szenario. Wie würden Sie eine solche Validierung für eine der bereinigten Methoden des Modells durchführen?
Bobort
8

Sie mischen den gesamten Stapel, es sind mehrere Ebenen beteiligt:

  • Ein Django-Modell definiert die Datenstruktur.

  • Ein Django-Formular ist eine Verknüpfung zum Definieren von HTML-Formularen, Feldvalidierungen und Python / HTML-Wert-Übersetzungen. Es ist nicht unbedingt erforderlich, aber oft praktisch.

  • Eine Django ModelForm ist eine weitere Verknüpfung, kurz eine Form-Unterklasse, die ihre Felder aus einer Modelldefinition bezieht. Nur ein praktischer Weg für den allgemeinen Fall, dass ein Formular zum Eingeben von Daten in die Datenbank verwendet wird.

und schlussendlich:

  • Django-Architekten halten sich nicht genau an die MVC-Struktur. Manchmal nennen sie es MTV (Model Template View); weil es keinen Controller gibt und die Aufteilung zwischen Vorlage (nur Präsentation, keine Logik) und Ansicht (nur Benutzerlogik, kein HTML) genauso wichtig ist wie die Isolation des Modells.

Einige Leute sehen das als Ketzerei an; Es ist jedoch wichtig, sich daran zu erinnern, dass MVC ursprünglich für GUI-Anwendungen definiert wurde und für Webanwendungen eher umständlich ist.

Javier
quelle
Widgets sind jedoch Präsentationen und werden direkt in Ihr Formular eingebunden. Sicher, ich kann sie nicht verwenden, aber dann verlieren Sie die Vorteile der Bindung und Validierung. Mein Punkt ist, dass Ihnen Spring die Bindung und Validierung sowie die vollständige Trennung von Modell und Ansicht ermöglicht. Ich würde denken, Django hätte so etwas leicht umsetzen können. Und ich betrachte die URL-Konfiguration als eine Art eingebauten Front-Controller, der ein ziemlich beliebtes Muster für Spring MVC ist.
jiggy
Kürzester Code gewinnt.
Kevin Cline
1
@jiggy: Formulare sind Teil der Präsentation, die Bindung und Validierung erfolgt nur für vom Benutzer eingegebene Daten. Die Modelle haben ihre eigene Bindung und Validierung, getrennt und unabhängig von den Formularen. Die Modellform ist nur eine Abkürzung, wenn sie 1: 1 (oder fast) sind
Javier
Nur eine kleine Bemerkung, dass MVC in Web-Apps nicht wirklich Sinn machte ... bis AJAX es wieder richtig einsetzte.
AlexanderJohannesen
Formularanzeige ist Ansicht. Die Formularvalidierung ist der Controller. Formulardaten sind Modell. IMO zumindest. Django mischt sie alle zusammen. Pedanterie beiseite bedeutet, dass, wenn Sie engagierte clientseitige Entwickler beschäftigen (wie mein Unternehmen), diese ganze Sache irgendwie nutzlos ist.
jiggy
4

Ich beantworte diese alte Frage, weil die anderen Antworten das erwähnte spezifische Problem zu vermeiden scheinen.

Mit Django-Formularen können Sie problemlos kleinen Code schreiben und ein Formular mit vernünftigen Standardeinstellungen erstellen. Jede Menge Anpassungen führen sehr schnell zu "mehr Code" und "mehr Arbeit" und machen den Hauptnutzen des Formularsystems etwas zunichte

Template-Bibliotheken wie Django-Widget-Tweaks erleichtern das Anpassen von Formularen erheblich. Hoffentlich sind solche Anpassungen vor Ort mit einer Vanille-Django-Installation ganz einfach.

Ihr Beispiel mit 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>

Trey Hunner
quelle
1

(Ich habe Kursivschrift verwendet , um die MVC-Konzepte zu kennzeichnen und die Lesbarkeit zu verbessern.)

Nein, meiner Meinung nach brechen sie MVC nicht. Wenn Sie mit Django Models / Forms arbeiten, stellen Sie sich vor, dass Sie einen gesamten MVC-Stapel als Modell verwenden :

  1. django.db.models.Modelist das Basismodell (die Daten und die Geschäftslogik hält).
  2. django.forms.ModelFormBietet einen Controller für die Interaktion mit django.db.models.Model.
  3. django.forms.Form(wie durch die Vererbung von bereitgestellt django.forms.ModelForm) ist die Ansicht , mit der Sie interagieren.
    • Dies macht die Dinge verschwommen, da ModelFormes sich um eine handelt Form, so dass die beiden Schichten eng miteinander verbunden sind. Meiner Meinung nach wurde dies aus Gründen der Kürze in unserem Code und zur Wiederverwendung im Code der Django-Entwickler durchgeführt.

Auf diese Weise wird django.forms.ModelForm(mit seinen Daten und seiner Geschäftslogik) selbst ein Modell . Sie könnten es als (MVC) VC bezeichnen, was in OOP eine ziemlich häufige Implementierung ist.

Nehmen Sie zum Beispiel Djangos django.db.models.ModelKlasse. Wenn wir uns die django.db.models.ModelObjekte ansehen , sehen wir Model , obwohl es bereits eine vollständige Implementierung von MVC ist. Angenommen, MySQL ist die Back-End-Datenbank:

  • MySQLdbist das Modell (Datenspeicherschicht und Geschäftslogik in Bezug auf die Interaktion / Validierung der Daten).
  • django.db.models.queryist der Controller (verarbeitet Eingaben aus der Ansicht und übersetzt sie für das Modell ).
  • django.db.models.Modelist die Ansicht (mit der der Benutzer interagiert).
    • In diesem Fall sind die Entwickler (Sie und ich) der "Benutzer".

Diese Interaktion ist dieselbe wie bei Ihren "clientseitigen Entwicklern", wenn Sie mit Objekten arbeiten yourproject.forms.YourForm(von django.forms.ModelFormObjekten erben ):

  • Da wir wissen müssen, wie man mit django.db.models.Modelihnen umgeht, müssen sie wissen, wie man mit yourproject.forms.YourFormihnen umgeht (ihr Modell ).
  • Da wir nichts darüber wissen müssen MySQLdb, müssen Ihre "clientseitigen Entwickler" nichts darüber wissen yourproject.models.YourModel.
  • In beiden Fällen müssen wir uns sehr selten darum kümmern, wie der Controller tatsächlich implementiert ist.
Jack M.
quelle
1
Anstatt über Semantik zu debattieren, möchte ich nur mein HTML und CSS in meinen Vorlagen behalten und muss nichts davon in .py-Dateien ablegen. Abgesehen von der Philosophie ist dies nur ein praktisches Ziel, das ich erreichen möchte, weil es effizienter ist und eine bessere Arbeitsteilung ermöglicht.
jiggy
1
Das ist immer noch perfekt möglich. Sie können Ihre Felder manuell in die Vorlagen schreiben, Ihre Validierung manuell in Ihre Ansichten schreiben und dann Ihre Modelle manuell aktualisieren. Aber das Design von Django's Forms macht MVC nicht kaputt.
Jack M.