¿Las formas de Django están violando MVC?

16

Acabo de comenzar a trabajar con Django después de años de Spring MVC y la implementación de formularios parece un poco loca. Si no está familiarizado, los formularios de Django comienzan con una clase de modelo de formulario que define sus campos. Spring comienza de manera similar con un objeto de respaldo de formulario. Pero donde Spring proporciona un taglib para vincular elementos de formulario al objeto de respaldo dentro de su JSP, Django tiene widgets de formulario vinculados directamente al modelo. Hay widgets predeterminados donde puede agregar atributos de estilo a sus campos para aplicar CSS o definir widgets completamente personalizados como nuevas clases. Todo va en tu código de Python. Eso me parece una locura. Primero, está poniendo información sobre su vista directamente en su modelo y, en segundo lugar, está vinculando su modelo a una vista específica. ¿Me estoy perdiendo de algo?

EDITAR: Algunos códigos de ejemplo según lo solicitado.

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
fuente
¿"información sobre su vista directamente en su modelo"? Por favor sea especifico. ¿"vincular su modelo a una vista específica"? Por favor sea especifico. Proporcione ejemplos concretos y específicos. Una queja general como esta es difícil de entender y mucho menos responder.
S.Lott
Todavía no he construido nada, solo estoy leyendo los documentos. Vincula un widget HTML junto con clases CSS a su clase Form directamente en código Python. Eso es lo que estoy llamando.
jiggy
¿Dónde más quieres hacer este enlace? Proporcione un ejemplo o un enlace o una cita a lo específico que está objetando. El argumento hipotético es difícil de seguir.
S.Lott
Yo hice. Mira cómo Spring MVC lo hace. Inyecta el objeto de respaldo de formulario (como una clase de formulario de Django) en su vista. Luego, escribe su HTML usando taglibs para que pueda diseñar su HTML de manera normal y simplemente agregue un atributo de ruta que lo vinculará a las propiedades de su objeto de respaldo de formulario.
jiggy
Por favor, actualice la pregunta para que sea perfectamente claro lo que está objetando. La pregunta es difícil de seguir. No tiene un código de ejemplo para dejar su punto perfectamente claro.
S.Lott

Respuestas:

21

Sí, los formularios de Django son un desastre desde la perspectiva MVC, supongamos que estás trabajando en un gran juego de superhéroes MMO y estás creando el modelo Hero:

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

Ahora se le pide que cree un formulario para que los jugadores de MMO puedan ingresar sus superpoderes de héroe:

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

Como el repelente de tiburones es un arma muy poderosa, tu jefe te pidió que lo limitaras. Si un héroe tiene el Repelente de tiburones, entonces no puede volar. Lo que la mayoría de la gente hace es simplemente agregar esta regla de negocios en el formulario limpio y llamarlo un día:

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

Este patrón se ve genial y podría funcionar en proyectos pequeños, pero en mi experiencia esto es muy difícil de mantener en proyectos grandes con múltiples desarrolladores. El problema es que el formulario es parte de la vista del MVC. Por lo tanto, deberá recordar esa regla comercial cada vez que:

  • Escribe otro formulario que trate con el modelo Hero.
  • Escribe un guión que importe héroes de otro juego.
  • Cambia manualmente la instancia del modelo durante la mecánica del juego.
  • etc.

Mi punto aquí es que formularios.py tiene que ver con el diseño y la presentación del formulario, nunca debe agregar lógica de negocios en ese archivo a menos que disfrute de jugar con el código de espagueti.

La mejor manera de manejar el problema del héroe es usar el método de limpieza del modelo más una señal personalizada. La limpieza del modelo funciona como la limpieza de la forma, pero se almacena en el modelo mismo, cada vez que se limpia el HeroForm, se llama automáticamente al método de limpieza del héroe. Esta es una buena práctica porque si otro desarrollador escribe otro formulario para el Héroe, obtendrá la validación repelente / mosca de forma gratuita.

El problema con la limpieza es que solo se llama cuando un modelo es modificado por un formulario. No se llama cuando lo guarda () manualmente y puede terminar con un héroe no válido en su base de datos. Para contrarrestar este problema, puede agregar este oyente a su proyecto:

from django.db.models.signals import pre_save

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

Esto llamará al método clean en cada llamada save () para todos sus modelos.

Cesar Canassa
fuente
Esta es una respuesta muy útil. Sin embargo, gran parte de mis formularios y la validación correspondiente implican varios campos en varios modelos. Creo que es un escenario muy importante a considerar. ¿Cómo realizaría tal validación en uno de los métodos limpios del modelo?
Bobort
8

Estás mezclando toda la pila, hay varias capas involucradas:

  • Un modelo de Django define la estructura de datos.

  • un formulario Django es un acceso directo para definir formularios HTML, validaciones de campo y traducciones de valores Python / HTML. No es estrictamente necesario, pero a menudo es útil.

  • un Django ModelForm es otro acceso directo, en resumen, una subclase de Formulario que obtiene sus campos de una definición de Modelo. Solo una manera práctica para el caso común en el que se usa un formulario para ingresar datos en la base de datos.

y finalmente:

  • Los arquitectos de Django no se adhieren exactamente a la estructura MVC. A veces lo llaman MTV (Vista de plantilla de modelo); porque no existe un controlador, y la división entre plantilla (solo presentación, sin lógica) y Vista (solo lógica orientada al usuario, sin HTML) es tan importante como el aislamiento del Modelo.

Algunas personas ven eso como herejía; pero es importante recordar que MVC se definió originalmente para aplicaciones GUI, y es un ajuste bastante incómodo para aplicaciones web.

Javier
fuente
Pero los widgets son presentaciones y están conectados directamente a su formulario. Claro, no puedo usarlos, pero luego pierdes los beneficios de la unión y la validación. Mi punto es que Spring le ofrece enlace y validación y una separación completa del modelo y la vista. Creo que Django podría haber implementado fácilmente algo similar. Y miro la configuración de url como una especie de controlador frontal integrado que es un patrón bastante popular para Spring MVC.
jiggy
El código más corto gana.
Kevin Cline
1
@jiggy: los formularios son parte de la presentación, el enlace y la validación son solo para datos ingresados ​​por el usuario. los modelos tienen su propio enlace y validación, separados e independientes de los formularios. la forma del modelo es solo un atajo para cuando son 1: 1 (o casi)
Javier
Solo una pequeña nota de que sí, MVC realmente no tenía sentido en las aplicaciones web ... hasta que AJAX lo volvió a poner de nuevo.
AlexanderJohannesen
La visualización del formulario es vista. La validación del formulario es el controlador. Los datos del formulario son modelo. OMI, al menos. Django los junta a todos. Dejando a un lado la pedantería, significa que si empleas desarrolladores dedicados del lado del cliente (como lo hace mi empresa) todo esto es un poco inútil.
jiggy
4

Estoy respondiendo esta vieja pregunta porque las otras respuestas parecen evitar el problema específico mencionado.

Los formularios de Django le permiten escribir fácilmente código pequeño y crear un formulario con valores predeterminados razonables. Cualquier cantidad de personalización muy rápidamente conduce a "más código" y "más trabajo" y anula de alguna manera el beneficio principal del sistema de formularios

Las bibliotecas de plantillas como django-widget-tweaks facilitan mucho más la personalización de formularios. Con suerte, las personalizaciones de campo como esta eventualmente serán fáciles con una instalación vainilla de Django.

Su ejemplo con 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
fuente
1

(He usado cursiva para indicar los conceptos de MVC para hacer esto más legible).

No, en mi opinión, no rompen MVC. Cuando trabaje con modelos / formularios de Django, piense que utiliza una pila MVC completa como modelo :

  1. django.db.models.Modeles el modelo base (contiene los datos y la lógica de negocios).
  2. django.forms.ModelFormproporciona un controlador para interactuar con django.db.models.Model.
  3. django.forms.Form(según lo dispuesto por herencia django.forms.ModelForm) es la Vista con la que interactúa.
    • Esto hace que las cosas se vuelvan borrosas, ya que ModelFormes un Form, por lo que las dos capas están estrechamente acopladas. En mi opinión, esto se hizo por brevedad en nuestro código, y para la reutilización del código dentro del código de los desarrolladores de Django.

De esta manera, django.forms.ModelForm(con sus datos y lógica de negocios) se convierte en un Modelo en sí mismo. Puede hacer referencia a él como (MVC) VC, que es una implementación bastante común en OOP.

Tomemos, por ejemplo, la django.db.models.Modelclase de Django . Cuando miramos los django.db.models.Modelobjetos, vemos el Modelo a pesar de que ya es una implementación completa de MVC. Suponiendo que MySQL es la base de datos de fondo:

  • MySQLdbes el Modelo (capa de almacenamiento de datos y lógica empresarial sobre cómo interactuar con / validar los datos).
  • django.db.models.queryes el controlador (maneja la entrada de la vista y la traduce para el modelo ).
  • django.db.models.Modeles la Vista (con lo que interactúa el usuario).
    • En este caso, los desarrolladores (usted y yo) somos el 'usuario'.

Esta interacción es la misma que la de sus "desarrolladores del lado del cliente" cuando trabaja con yourproject.forms.YourForm(heredando django.forms.ModelForm) objetos:

  • Como necesitamos saber cómo interactuar django.db.models.Model, ellos tendrían que saber cómo interactuar con yourproject.forms.YourForm(su Modelo ).
  • Como no necesitamos saber sobre esto MySQLdb, sus "desarrolladores del lado del cliente" no necesitan saber nada al respecto yourproject.models.YourModel.
  • En ambos casos, rara vez tenemos que preocuparnos de cómo se implementa realmente el controlador .
Jack M.
fuente
1
En lugar de debatir la semántica, solo quiero mantener mi HTML y CSS en mis plantillas y no tener que poner nada de eso en archivos .py. Dejando a un lado la filosofía, ese es solo un fin práctico que quiero lograr porque es más eficiente y permite una mejor división del trabajo.
jiggy
1
Eso sigue siendo perfectamente posible. Puede escribir manualmente sus campos en las plantillas, escribir manualmente su validación en sus vistas y luego actualizar manualmente sus modelos. Pero el diseño de los formularios de Django no rompe MVC.
Jack M.