Os formulários do Django estão violando o MVC?

16

Comecei a trabalhar com o Django vindo de anos do Spring MVC e a implementação de formulários parece um pouco louca. Se você não estiver familiarizado, os formulários do Django começam com uma classe de modelo de formulário que define seus campos. O Spring começa da mesma forma com um objeto de suporte de formulário. Mas onde o Spring fornece um taglib para vincular elementos de formulário ao objeto de backup em seu JSP, o Django possui widgets de formulário vinculados diretamente ao modelo. Existem widgets padrão nos quais você pode adicionar atributos de estilo aos seus campos para aplicar CSS ou definir widgets completamente personalizados como novas classes. Tudo acontece no seu código python. Isso parece loucura para mim. Primeiro, você está colocando informações sobre sua visão diretamente em seu modelo e, em segundo lugar, está vinculando seu modelo a uma visão específica. Estou esquecendo de algo?

EDIT: Alguns exemplos de código conforme 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
fonte
"informações sobre a sua visão diretamente no seu modelo"? Por favor, seja específico. "vinculando seu modelo a uma visão específica"? Por favor, seja específico. Forneça exemplos concretos e específicos. É difícil entender uma queixa geral como essa, que é muito difícil de responder e muito menos responder.
S.Lott 8/07
Ainda não construí nada, apenas lendo os documentos. Você vincula um widget HTML, juntamente com classes CSS, à sua classe Form diretamente no código Python. É o que estou chamando.
11117 jiggy
onde mais você deseja fazer essa ligação? Forneça um exemplo ou um link ou uma cotação para o item específico ao qual você se opõe. O argumento hipotético é difícil de seguir.
8898 S.Lott
Eu fiz. Veja como o Spring MVC faz isso. Você injeta o objeto de apoio ao formulário (como uma classe Django Form) na sua exibição. Em seguida, você escreve seu HTML usando taglibs para poder projetá-lo normalmente e apenas adicionar um atributo de caminho que o vincule às propriedades do seu objeto de backup de formulário.
jiggy
Por favor, atualize a questão de deixar bem claro o que você está contestando. A pergunta é difícil de seguir. Não possui código de exemplo para deixar seu argumento perfeitamente claro.
8898 S.Lott

Respostas:

21

Sim, as formas do Django são uma confusão da perspectiva do MVC, suponha que você esteja trabalhando em um grande jogo de super-heróis MMO e criando o 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)

Agora, você deve criar um formulário para ele, para que os jogadores do MMO possam inserir seus super poderes de herói:

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

Como o repelente de tubarões é uma arma muito poderosa, seu chefe pediu que você o limite. Se um herói tem o repelente de tubarões, ele não pode voar. O que a maioria das pessoas faz é simplesmente adicionar essa regra de negócios no formato clean e chamar isso de dia:

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

Esse padrão parece legal e pode funcionar em pequenos projetos, mas, na minha experiência, isso é muito difícil de manter em grandes projetos com vários desenvolvedores. O problema é que o formulário faz parte da exibição do MVC. Portanto, você deve se lembrar dessa regra de negócios toda vez que:

  • Escreva outra forma que lide com o modelo Hero.
  • Escreva um script que importe heróis de outro jogo.
  • Altere manualmente a instância do modelo durante a mecânica do jogo.
  • etc.

Meu argumento aqui é que o forms.py tem tudo a ver com o layout e a apresentação do formulário, você nunca deve adicionar lógica de negócios nesse arquivo, a menos que goste de mexer com o código espaguete.

A melhor maneira de lidar com o problema do herói é usar o método de limpeza de modelo mais um sinal personalizado. A limpeza do modelo funciona como a forma limpa, mas é armazenada no próprio modelo, sempre que o HeroForm é limpo, ele chama automaticamente o método de limpeza Hero. Essa é uma boa prática, porque se outro desenvolvedor escrever uma outra forma para o Herói, ele receberá a validação repelente / grátis.

O problema com a limpeza é que ele é chamado apenas quando um modelo é modificado por um formulário. Não é chamado quando você o salva manualmente () e pode acabar com um herói inválido no seu banco de dados. Para combater esse problema, você pode adicionar este ouvinte ao seu projeto:

from django.db.models.signals import pre_save

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

Isso chamará o método clean em cada chamada save () para todos os seus modelos.

Cesar Canassa
fonte
Esta é uma resposta muito útil. No entanto, muitos dos meus formulários e a validação correspondente envolvem vários campos em vários modelos. Eu acho que é um cenário muito importante a considerar. Como você executaria essa validação em um dos métodos limpos do modelo?
Bobort
8

Você está misturando a pilha inteira, há várias camadas envolvidas:

  • um modelo do Django define a estrutura de dados.

  • um Django Form é um atalho para definir formulários HTML, validações de campo e traduções de valores Python / HTML. Não é estritamente necessário, mas muitas vezes útil.

  • um Django ModelForm é outro atalho, em suma uma subclasse de Form que obtém seus campos a partir de uma definição de modelo. Apenas uma maneira prática para o caso comum em que um formulário é usado para inserir dados no banco de dados.

e finalmente:

  • Os arquitetos do Django não aderem exatamente à estrutura do MVC. Às vezes, eles chamam de MTV (Model Template View); porque não existe um controlador e a divisão entre modelo (apenas apresentação, sem lógica) e View (apenas lógica voltada para o usuário, sem HTML) é tão importante quanto o isolamento do Modelo.

Algumas pessoas vêem isso como heresia; mas é importante lembrar que o MVC foi originalmente definido para aplicativos da GUI e é um ajuste bastante estranho para aplicativos da Web.

Javier
fonte
Mas os widgets são de apresentação e são conectados diretamente ao seu formulário. Claro, não posso usá-los, mas você perde os benefícios da ligação e validação. Meu argumento é que o Spring fornece vinculação e validação e separação completa de modelo e visualização. Eu acho que o Django poderia facilmente implementar algo semelhante. E eu vejo a configuração do URL como uma espécie de controlador frontal incorporado, que é um padrão bastante popular para o Spring MVC.
Jiggy
O menor código vence.
Kevin cline
1
@ jiggy: os formulários fazem parte da apresentação, a ligação e a validação são apenas para dados inseridos pelo usuário. os modelos têm sua própria ligação e validação, separados e independentes dos formulários. o modelform é apenas um atalho para quando eles são 1: 1 (ou quase)
Javier
Apenas uma pequena nota de que, sim, o MVC realmente não fazia sentido nos aplicativos da web ... até que o AJAX o recuperou novamente.
AlexanderJohannesen
A exibição do formulário é vista. A validação do formulário é controladora. Os dados do formulário são modelo. IMO, pelo menos. Django junta todos eles. Pedantismo à parte, significa que se você empregar desenvolvedores dedicados do lado do cliente (como minha empresa), tudo isso é meio inútil.
jiggy
4

Estou respondendo a essa pergunta antiga porque as outras respostas parecem evitar o problema específico mencionado.

Os formulários do Django permitem que você escreva facilmente pouco código e crie um formulário com padrões saudáveis. Qualquer quantidade de personalização leva muito rapidamente a "mais código" e "mais trabalho" e anula um pouco o principal benefício do sistema de formulários

Bibliotecas de modelos como django-widget-tweaks facilitam a personalização de formulários. Esperamos que personalizações de campo como essa acabem sendo fáceis com uma instalação baunilha do Django.

Seu exemplo com 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
fonte
1

(Eu usei itálico para significar os conceitos do MVC para tornar isso mais legível.

Não, na minha opinião, eles não quebram o MVC. Ao trabalhar com Django Models / Forms, pense nisso como usar uma pilha MVC inteira como um Model :

  1. django.db.models.Modelé o modelo base (mantém os dados e a lógica de negócios).
  2. django.forms.ModelFormfornece um controlador para interagir comdjango.db.models.Model .
  3. django.forms.Form(conforme fornecido por herança django.forms.ModelForm) é a Visualização com a qual você interage.
    • Isso torna as coisas embaçadas, já que o ModelFormé a Form, então as duas camadas são fortemente acopladas. Na minha opinião, isso foi feito com brevidade em nosso código e para reutilização de código no código dos desenvolvedores do Django.

Dessa forma, django.forms.ModelForm(com seus dados e lógica de negócios) se torna um Modelo si. Você pode referenciá-lo como (MVC) VC, que é uma implementação bastante comum no OOP.

Veja, por exemplo, a django.db.models.Modelclasse do Django . Quando olhamos para os django.db.models.Modelobjetos, vemos o Model , apesar de já ser uma implementação completa do MVC. Supondo que o MySQL seja o banco de dados back-end:

  • MySQLdbé o modelo (camada de armazenamento de dados e lógica de negócios sobre como interagir com / validar os dados).
  • django.db.models.queryé o controlador (manipula a entrada da visualização e a traduz para o modelo ).
  • django.db.models.Modelé a visualização (com a qual o usuário interage).
    • Nesse caso, os desenvolvedores (você e eu) somos o 'usuário'.

Essa interação é igual aos seus "desenvolvedores do lado do cliente" ao trabalhar com yourproject.forms.YourForm(herdar de django.forms.ModelForm) objetos:

  • Como precisamos saber como interagir django.db.models.Model, eles precisam saber como interagir yourproject.forms.YourForm(seu Modelo ).
  • Como não precisamos saber MySQLdb, seus "desenvolvedores do lado do cliente" não precisam saber nada sobre yourproject.models.YourModel.
  • Nos dois casos, raramente precisamos nos preocupar com como o Controlador é realmente implementado.
Jack M.
fonte
1
Em vez de debater semântica, eu apenas quero manter meu HTML e CSS em meus modelos e não precisar inseri-lo em arquivos .py. Filosofia à parte, esse é apenas um fim prático que eu quero alcançar, porque é mais eficiente e permite uma melhor divisão do trabalho.
jiggy
1
Isso ainda é perfeitamente possível. Você pode escrever manualmente seus campos nos modelos, escrever manualmente sua validação em suas visualizações e, em seguida, atualizar manualmente seus modelos. Mas o design do Forms do Django não quebra o MVC.
87711 Jack M.