Les formulaires Django violent-ils MVC?

16

Je viens de commencer à travailler avec Django après des années de Spring MVC et l'implémentation des formulaires semble être un peu folle. Si vous n'êtes pas familier, les formulaires Django commencent par une classe de modèle de formulaire qui définit vos champs. Spring commence également avec un objet de support de formulaire. Mais lorsque Spring fournit un taglib pour lier des éléments de formulaire à l'objet de support dans votre JSP, Django a des widgets de formulaire directement liés au modèle. Il existe des widgets par défaut où vous pouvez ajouter des attributs de style à vos champs pour appliquer du CSS ou définir des widgets entièrement personnalisés en tant que nouvelles classes. Tout va dans votre code python. Cela me semble dingue. Premièrement, vous mettez des informations sur votre vue directement dans votre modèle et deuxièmement, vous liez votre modèle à une vue spécifique. Suis-je en train de manquer quelque chose?

EDIT: Un exemple de code tel que demandé.

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
la source
"des informations sur votre vue directement dans votre modèle"? Soyez précis s'il vous plait. "lier votre modèle à une vue spécifique"? Soyez précis s'il vous plait. Veuillez fournir des exemples concrets et spécifiques. Une plainte générale de la main comme celle-ci est difficile à comprendre et encore moins à répondre.
S.Lott
Je n'ai encore rien construit, je lis juste les documents. Vous liez un widget HTML avec des classes CSS à votre classe Form directement dans le code Python. Voilà ce que j'appelle.
jiggy
où voulez-vous faire cette reliure? Veuillez fournir un exemple ou un lien ou un devis à la chose spécifique à laquelle vous vous opposez. L'argument hypothétique est difficile à suivre.
S.Lott
J'ai fait. Regardez comment Spring MVC le fait. Vous injectez l'objet de support de formulaire (comme une classe Django Form) dans votre vue. Ensuite, vous écrivez votre code HTML à l'aide de balises tag afin que vous puissiez concevoir votre code HTML comme normal et ajoutez simplement un attribut de chemin qui le liera aux propriétés de votre objet de support de formulaire.
jiggy
Veuillez mettre à jour la question pour indiquer clairement à quoi vous vous opposez. La question est difficile à suivre. Il n'a pas d'exemple de code pour rendre votre point parfaitement clair.
S.Lott

Réponses:

21

Oui, les formulaires Django sont un gâchis du point de vue MVC, supposons que vous travaillez dans un grand jeu de super-héros MMO et que vous créez le modèle Hero:

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

Maintenant, vous êtes invité à créer un formulaire pour cela, afin que les joueurs MMO puissent entrer leurs super pouvoirs de héros:

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

Le Shark Repellent étant une arme très puissante, votre patron vous a demandé de la limiter. Si un héros possède le Shark Repellent, il ne peut pas voler. Ce que la plupart des gens font, c'est simplement d'ajouter cette règle commerciale sous la forme propre et de l'appeler un jour:

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

Ce modèle a l'air cool et pourrait fonctionner sur de petits projets, mais d'après mon expérience, il est très difficile à maintenir dans les grands projets avec plusieurs développeurs. Le problème est que le formulaire fait partie de la vue du MVC. Vous devrez donc vous souvenir de cette règle commerciale chaque fois que vous:

  • Écrivez un autre formulaire qui traite du modèle Hero.
  • Écrivez un script qui importe des héros d'un autre jeu.
  • Modifiez manuellement l'instance du modèle pendant la mécanique du jeu.
  • etc.

Mon point ici est que le forms.py concerne la mise en page et la présentation du formulaire, vous ne devez jamais ajouter de logique métier dans ce fichier, sauf si vous aimez jouer avec du code spaghetti.

La meilleure façon de gérer le problème des héros est d'utiliser la méthode de nettoyage de modèle plus un signal personnalisé. Le modèle propre fonctionne comme le formulaire propre mais il est stocké dans le modèle lui-même, chaque fois que HeroForm est nettoyé, il appelle automatiquement la méthode Hero propre. C'est une bonne pratique car si un autre développeur écrit un autre formulaire pour le héros, il obtiendra gratuitement la validation répulsif / anti-mouches.

Le problème avec le nettoyage est qu'il n'est appelé que lorsqu'un modèle est modifié par un formulaire. Il n'est pas appelé lorsque vous l'enregistrez manuellement () et vous pouvez vous retrouver avec un héros invalide dans votre base de données. Pour contrer ce problème, vous pouvez ajouter cet écouteur à votre projet:

from django.db.models.signals import pre_save

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

Cela appellera la méthode propre à chaque appel save () pour tous vos modèles.

Cesar Canassa
la source
Ceci est une réponse très utile. Cependant, une grande partie de mes formulaires et de la validation correspondante implique plusieurs champs sur plusieurs modèles. Je pense que c'est un scénario très important à considérer. Comment effectueriez-vous une telle validation sur l'une des méthodes propres du modèle?
Bobort
8

Vous mélangez toute la pile, plusieurs couches sont impliquées:

  • un modèle Django définit la structure des données.

  • un formulaire Django est un raccourci pour définir des formulaires HTML, des validations de champs et des traductions de valeurs Python / HTML. Ce n'est pas strictement nécessaire, mais souvent pratique.

  • un Django ModelForm est un autre raccourci, en bref une sous-classe Form qui obtient ses champs à partir d'une définition de modèle. Juste un moyen pratique pour le cas commun où un formulaire est utilisé pour entrer des données dans la base de données.

et enfin:

  • Les architectes Django n'adhèrent pas exactement à la structure MVC. Parfois, ils l'appellent MTV (Model Template View); car il n'y a pas de contrôleur, et la séparation entre le modèle (juste la présentation, pas de logique) et la vue (juste la logique utilisateur, pas de HTML) est tout aussi importante que l'isolement du modèle.

Certaines personnes voient cela comme une hérésie; mais il est important de se rappeler que MVC a été défini à l'origine pour les applications GUI, et qu'il convient plutôt aux applications Web.

Javier
la source
Mais les widgets sont une présentation et ils sont câblés directement dans votre formulaire. Bien sûr, je ne peux pas les utiliser, mais alors vous perdez les avantages de la liaison et de la validation. Mon point est que Spring vous donne la liaison et la validation et la séparation complète du modèle et de la vue. Je pense que Django aurait pu facilement implémenter quelque chose de similaire. Et je regarde la configuration des URL comme une sorte de contrôleur frontal intégré qui est un modèle assez populaire pour Spring MVC.
jiggy
Le code le plus court gagne.
kevin cline
1
@jiggy: les formulaires font partie de la présentation, la liaison et la validation ne concernent que les données saisies par l'utilisateur. les modèles ont leur propre liaison et validation, séparés et indépendants des formulaires. la forme du modèle n'est qu'un raccourci pour quand ils sont 1: 1 (ou presque)
Javier
Juste une petite note que, oui, MVC n'avait pas vraiment de sens dans les applications Web ... jusqu'à ce que AJAX le remette à nouveau en place.
AlexanderJohannesen
L'affichage du formulaire est une vue. La validation du formulaire est le contrôleur. Les données de formulaire sont des modèles. OMI, au moins. Django les rassemble tous ensemble. Mis à part la pédanterie, cela signifie que si vous employez des développeurs dédiés côté client (comme le fait ma société), tout cela est un peu inutile.
jiggy
4

Je réponds à cette vieille question parce que les autres réponses semblent éviter le problème spécifique mentionné.

Les formulaires Django vous permettent d'écrire facilement peu de code et de créer un formulaire avec des valeurs par défaut sensées. Toute personnalisation entraîne très rapidement «plus de code» et «plus de travail» et annule quelque peu le principal avantage du système de formulaire

Les bibliothèques de modèles comme django-widget-tweaks facilitent la personnalisation des formulaires. Espérons que les personnalisations sur le terrain comme celle-ci seront finalement faciles avec une installation Django vanille.

Votre exemple avec 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
la source
1

(J'ai utilisé l' italique pour signifier les concepts MVC pour le rendre plus lisible.)

Non, à mon avis, ils ne cassent pas MVC. Lorsque vous travaillez avec des modèles / formulaires Django, considérez-le comme utilisant une pile MVC entière comme modèle :

  1. django.db.models.Modelest le modèle de base (contient les données et la logique métier).
  2. django.forms.ModelFormfournit un contrôleur pour interagir avec django.db.models.Model.
  3. django.forms.Form(tel que fourni par héritage par django.forms.ModelForm) est la vue avec laquelle vous interagissez.
    • Cela rend les choses floues, car ModelFormc'est un Form, donc les deux couches sont étroitement couplées. À mon avis, cela a été fait pour la brièveté de notre code et pour la réutilisation du code dans le code des développeurs Django.

De cette façon, django.forms.ModelForm(avec ses données et sa logique métier) devient un modèle lui-même. Vous pouvez le référencer en tant que (MVC) VC, qui est une implémentation assez courante dans la POO.

Prenez, par exemple, la django.db.models.Modelclasse de Django . Lorsque nous regardons les django.db.models.Modelobjets, nous voyons le modèle même s'il s'agit déjà d'une implémentation complète de MVC. En supposant que MySQL est la base de données principale:

  • MySQLdbest le modèle (couche de stockage de données et logique métier concernant la manière d'interagir avec / valider les données).
  • django.db.models.queryest le contrôleur (gère les entrées de la vue et les traduit pour le modèle ).
  • django.db.models.Modelest la vue (avec laquelle l'utilisateur interagit).
    • Dans ce cas, les développeurs (vous et moi) sont les «utilisateurs».

Cette interaction est la même que pour vos "développeurs côté client" lorsque vous travaillez avec yourproject.forms.YourForm(héritant de django.forms.ModelForm) des objets:

  • Comme nous devons savoir comment interagir avec django.db.models.Model, ils devraient savoir comment interagir avec yourproject.forms.YourForm(leur modèle ).
  • Comme nous n'avons pas besoin de le savoir MySQLdb, vos "développeurs côté client" n'ont besoin de rien savoir yourproject.models.YourModel.
  • Dans les deux cas, nous avons très rarement besoin de nous soucier de la façon dont le contrôleur est réellement implémenté.
Jack M.
la source
1
Plutôt que de débattre de la sémantique, je veux juste garder mon HTML et CSS dans mes modèles et ne pas avoir à en mettre dans des fichiers .py. Hormis la philosophie, c'est juste une fin pratique que je veux atteindre car elle est plus efficace et permet une meilleure division du travail.
jiggy
1
C'est encore parfaitement possible. Vous pouvez écrire manuellement vos champs dans les modèles, écrire manuellement votre validation dans vos vues, puis mettre à jour manuellement vos modèles. Mais la conception des formulaires de Django ne casse pas MVC.
Jack M.