Le forme Django violano MVC?


16

Ho appena iniziato a lavorare con Django proveniente da anni di Spring MVC e l'implementazione dei moduli sembra leggermente pazza. Se non si ha familiarità, i moduli Django iniziano con una classe del modello di modulo che definisce i campi. La primavera inizia allo stesso modo con un oggetto che supporta il form. Ma dove Spring fornisce un taglio per legare gli elementi del modulo all'oggetto di supporto all'interno del tuo JSP, Django ha dei widget di forma collegati direttamente al modello. Esistono widget predefiniti in cui è possibile aggiungere attributi di stile ai campi per applicare CSS o definire widget completamente personalizzati come nuove classi. Va tutto nel tuo codice Python. Mi sembra da impazzire. Innanzitutto, stai inserendo le informazioni sulla tua vista direttamente nel tuo modello e in secondo luogo stai vincolando il tuo modello a una vista specifica. Mi sto perdendo qualcosa?

EDIT: alcuni esempi di codice come richiesto.

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>

"informazioni sulla tua vista direttamente nel tuo modello"? Si prega di essere specifici. "associazione del modello a una vista specifica"? Si prega di essere specifici. Fornire esempi concreti e specifici. Una lamentela generale, agitando la mano come questa, è difficile da capire, e tanto meno risponde.
S.Lott

Non ho ancora costruito nulla, sto solo leggendo i documenti. Associ un widget HTML con le classi CSS alla tua classe Form direttamente nel codice Python. Questo è quello che sto chiamando.
jiggy,

dove altro vuoi fare questo legame? Fornisci un esempio o un link o un preventivo per la cosa specifica a cui stai obiettando. L'argomentazione ipotetica è difficile da seguire.
S.Lott

L'ho fatto. Guarda come lo fa Spring MVC. Inietti l'oggetto form-backing (come una classe Django Form) nella tua vista. Quindi scrivi il tuo HTML usando taglibs in modo da poter progettare il tuo HTML come di consueto e aggiungere semplicemente un attributo path che lo legherà alle proprietà dell'oggetto che supporta il form.
jiggy,

Si prega di aggiornare la questione di rendere perfettamente chiaro quello che stai contestare. La domanda è difficile da seguire. Non ha un codice di esempio per chiarire perfettamente il punto .
S.Lott

Risposte:


21

Sì, le forme Django sono un disastro dal punto di vista MVC, supponiamo che tu stia lavorando in un grande gioco MMO di supereroi e stai creando il modello Hero:

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

Ora ti viene chiesto di creare un modulo per farlo, in modo che i giocatori MMO possano inserire i loro super poteri dei loro eroi:

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

Poiché lo Shark Repellent è un'arma molto potente, il tuo capo ti ha chiesto di limitarlo. Se un eroe ha il repellente per squali, non può volare. Quello che la maggior parte delle persone fa è semplicemente aggiungere questa regola aziendale nel modulo pulito e chiamarla un giorno:

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

Questo modello sembra interessante e potrebbe funzionare su piccoli progetti, ma nella mia esperienza è molto difficile mantenerlo in grandi progetti con più sviluppatori. Il problema è che il modulo fa parte della vista di MVC. Quindi dovrai ricordare quella regola aziendale ogni volta che:

  • Scrivi un altro modulo relativo al modello Hero.
  • Scrivi una sceneggiatura che importi gli eroi da un altro gioco.
  • Modifica manualmente l'istanza del modello durante le meccaniche di gioco.
  • eccetera.

Il mio punto qui è che il form.py è tutto circa il layout e la presentazione del modulo, non dovresti mai aggiungere la logica aziendale in quel file a meno che non ti diverta a scherzare con il codice spaghetti.

Il modo migliore per gestire il problema dell'eroe è usare il metodo di pulizia del modello più un segnale personalizzato. Il modello clean funziona come il modulo clean ma è memorizzato nel modello stesso, ogni volta che HeroForm viene pulito chiama automaticamente il metodo clean di Hero. Questa è una buona pratica perché se un altro sviluppatore scrive un altro modulo per l'Eroe otterrà la convalida repellente / volo gratis.

Il problema con il clean è che viene chiamato solo quando un modello viene modificato da un modulo. Non viene chiamato quando lo salvi manualmente () e puoi ritrovarti con un eroe non valido nel tuo database. Per contrastare questo problema, puoi aggiungere questo listener al tuo progetto:

from django.db.models.signals import pre_save

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

Questo chiamerà il metodo clean su ogni chiamata save () per tutti i tuoi modelli.


Questa è una risposta molto utile Tuttavia, gran parte dei miei moduli e la relativa convalida coinvolgono diversi campi su diversi modelli. Penso che sia uno scenario molto importante da considerare. Come eseguiresti tale convalida su uno dei metodi di pulizia del modello?
Bobort,

8

Stai mescolando l'intero stack, ci sono diversi livelli coinvolti:

  • un modello Django definisce la struttura dei dati.

  • un Django Form è una scorciatoia per definire moduli HTML, validazioni di campi e traduzioni di valori Python / HTML. Non è strettamente necessario, ma spesso utile.

  • un Django ModelForm è un'altra scorciatoia, in breve una sottoclasse Form che ottiene i suoi campi da una definizione del modello. Solo un modo pratico per il caso comune in cui un modulo viene utilizzato per inserire dati nel database.

e infine:

  • Gli architetti Django non aderiscono esattamente alla struttura MVC. A volte lo chiamano MTV (Model Template View); perché non esiste un controller e la suddivisione tra template (solo presentazione, nessuna logica) e View (solo logica rivolta all'utente, senza HTML) è importante tanto quanto l'isolamento del modello.

Alcune persone lo vedono come un'eresia; ma è importante ricordare che MVC è stato originariamente definito per le applicazioni della GUI, ed è una soluzione piuttosto scomoda per le app Web.


Ma i widget sono presentazioni e sono collegati direttamente al tuo modulo. Certo, non posso usarli, ma poi perdi i benefici dell'associazione e della convalida. Il mio punto è che Spring ti offre un legame e una validazione e una completa separazione di modello e vista. Penso che Django avrebbe potuto implementare facilmente qualcosa di simile. E guardo alla configurazione dell'URL come una sorta di controller frontale incorporato che è un modello abbastanza popolare per Spring MVC.
jiggy,

Il codice più corto vince.
Kevin Cline,

1
@jiggy: i moduli fanno parte della presentazione, l'associazione e la convalida sono solo per i dati inseriti dall'utente. i modelli hanno una propria associazione e validazione, separati e indipendenti dalle forme. la modelform è solo una scorciatoia per quando sono 1: 1 (o quasi)
Javier

Solo una piccola nota che, sì, MVC non aveva davvero senso nelle app Web ... fino a quando AJAX non lo ha reinserito.
AlexanderJohannesen l'

La visualizzazione del modulo è vista. La convalida del modulo è controller. I dati del modulo sono modello. IMO, almeno. Django li mescola tutti insieme. Pedanteria a parte, significa che se impieghi sviluppatori dedicati lato client (come fa la mia azienda), tutto questo è un po 'inutile.
jiggy,

4

Sto rispondendo a questa vecchia domanda perché le altre risposte sembrano evitare il problema specifico menzionato.

I moduli Django ti consentono di scrivere facilmente un piccolo codice e creare un modulo con impostazioni predefinite corrette. Qualsiasi quantità di personalizzazione molto rapidamente porta a "più codice" e "più lavoro" e in qualche modo annulla il vantaggio principale del sistema di moduli

Le librerie di modelli come django-widget-tweaks rendono molto più semplice la personalizzazione dei moduli. Speriamo che personalizzazioni sul campo come questa alla fine saranno facili con un'installazione Django vaniglia.

Il tuo esempio 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>


1

(Ho usato il corsivo per indicare i concetti MVC per renderlo più leggibile.)

No, a mio avviso, non rompono MVC. Quando lavori con Django Models / Forms, pensa a come usare un intero stack MVC come Modello :

  1. django.db.models.Modelè il modello di base (contiene i dati e la logica aziendale).
  2. django.forms.ModelFormfornisce un controller per interagire con django.db.models.Model.
  3. django.forms.Form(come previsto dall'eredità da django.forms.ModelForm) è la vista con cui interagisci.
    • Questo rende le cose sfocate, dato che ModelFormè a Form, quindi i due strati sono strettamente accoppiati. A mio avviso, ciò è stato fatto per brevità nel nostro codice e per il riutilizzo del codice nel codice degli sviluppatori di Django.

In questo modo, django.forms.ModelForm(con i suoi dati e la sua logica aziendale) diventa un Modello stesso. Potresti fare riferimento a come (MVC) VC, che è un'implementazione abbastanza comune in OOP.

Prendi, ad esempio, la django.db.models.Modellezione di Django . Quando osserviamo gli django.db.models.Modeloggetti, vediamo Model anche se è già un'implementazione completa di MVC. Supponendo che MySQL sia il database back-end:

  • MySQLdbè il Modello (livello di archiviazione dei dati e logica aziendale relativa a come interagire / convalidare i dati).
  • django.db.models.queryè il controller (gestisce l'input dalla vista e lo traduce per il modello ).
  • django.db.models.Modelè la vista (con cui l'utente interagisce).
    • In questo caso, gli sviluppatori (io e te) siamo "l'utente".

Questa interazione è la stessa degli "sviluppatori lato client" quando si lavora con yourproject.forms.YourForm(ereditando da django.forms.ModelForm) oggetti:

  • Dato che dobbiamo sapere come interagire django.db.models.Model, dovrebbero sapere come interagire yourproject.forms.YourForm(il loro modello ).
  • Dato che non abbiamo bisogno di sapere MySQLdb, i tuoi "sviluppatori lato client" non devono sapere nulla yourproject.models.YourModel.
  • In entrambi i casi, raramente dobbiamo preoccuparci di come il controller è effettivamente implementato.

1
Piuttosto che discutere di semantica, voglio solo mantenere il mio HTML e CSS nei miei modelli e non devo inserirli nei file .py. Filosofia a parte, questo è solo un fine pratico che voglio raggiungere perché è più efficiente e consente una migliore divisione del lavoro.
jiggy,

1
Questo è ancora perfettamente possibile. È possibile scrivere manualmente i campi nei modelli, scrivere manualmente la convalida nelle viste e quindi aggiornare manualmente i modelli. Ma il design di Django's Forms non rompe MVC.
Jack M.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.