I moduli Django stanno violando MVC?

16

Ho appena iniziato a lavorare con Django proveniente da anni di Spring MVC e l'implementazione di moduli è leggermente pazzesca. Se non ti è familiare, i moduli di Django iniziano con una classe del modello di modulo che definisce i tuoi campi. La primavera inizia analogamente con un oggetto che sostiene la forma. Ma dove Spring fornisce un taglib per gli elementi del modulo vincolanti per l'oggetto di supporto all'interno del tuo JSP, Django ha widget di forma legati 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. Tutto va nel tuo codice Python. Mi sembra pazzesco. Innanzitutto, stai mettendo le informazioni sulla tua vista direttamente nel tuo modello e in secondo luogo leghi il tuo modello a una vista specifica. Mi manca qualcosa?

EDIT: codice di esempio 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>
    
posta jiggy 08.07.2011 - 01:40
fonte

4 risposte

20

Sì, le forme di Django sono un disastro dal punto di vista di MVC, supponi di lavorare in un grande gioco di supereroi MMO e stai creando il modello di 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 questo, in modo che i giocatori di MMO possano inserire i loro eroi superpoteri:

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

Dato che lo Shark Repellent è un'arma molto potente, il tuo capo ti ha chiesto di limitarlo. Se un eroe ha il repellente per squali, allora non può volare. Ciò 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 questo è molto difficile da mantenere in progetti di grandi dimensioni con più sviluppatori. Il problema è che il modulo fa parte della vista del MVC. Quindi dovrai ricordare quella regola aziendale ogni volta che:

  • Scrivi un altro modulo che si occupa del modello di Hero.
  • Scrivi uno script che importi eroi da un altro gioco.
  • Modifica manualmente l'istanza del modello durante le meccaniche di gioco.
  • ecc.

Il mio punto qui è che il forms.py è tutto basato sul layout del modulo e sulla presentazione, non dovresti mai aggiungere la logica di business in quel file a meno che non ti piaccia scherzare con il codice spaghetti.

Il modo migliore per gestire il problema dell'eroe è utilizzare metodo clean model oltre a un segnale personalizzato. Il modello clean funziona come il modulo clean ma è memorizzato nel modello stesso, ogni volta che HeroForm viene ripulito chiama automaticamente il metodo Clean. Questa è una buona pratica, perché se un altro sviluppatore scrive un altro modulo per l'Hero, riceverà gratuitamente la convalida repellente / volatile.

Il problema con clean è che viene chiamato solo quando un modello viene modificato da un modulo. Non viene chiamato quando lo si salva manualmente () e si può finire con un eroe non valido nel proprio 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.

    
risposta data 09.07.2011 - 11:38
fonte
8

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

  • un modello Django definisce la struttura dati.

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

  • a Django ModelForm è un'altra scorciatoia, in breve una sottoclasse di 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 Model View); perché non esiste un controller, e la suddivisione tra template (solo presentazione, nessuna logica) e View (solo logica user-facing, no HTML) è importante tanto quanto l'isolamento del Model.

Alcuni lo vedono come eresia; ma è importante ricordare che MVC è stato originariamente definito per le applicazioni GUI ed è un adattamento piuttosto scomodo per le app Web.

    
risposta data 08.07.2011 - 03:28
fonte
4

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

I moduli Django ti consentono di scrivere facilmente un piccolo codice e creare un modulo con impostazioni predefinite sane. Qualsiasi quantità di personalizzazione porta molto rapidamente 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 semplificano notevolmente la personalizzazione dei moduli. Speriamo che le personalizzazioni del campo come questa saranno alla fine facili con un'installazione di Django vanigliata.

Il tuo esempio con tweaks di widget di Django:

{% 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>

    
risposta data 24.05.2012 - 00:59
fonte
1

(Ho usato Corsivo per significare i concetti MVC per renderlo più leggibile.)

No, secondo me, non infrangono MVC. Quando lavori con Django Models / Forms, pensa che utilizzi un intero stack MVC come Model :

  1. django.db.models.Model è il Modello di base (contiene i dati e la logica aziendale).
  2. django.forms.ModelForm fornisce un Controller per l'interazione con django.db.models.Model .
  3. django.forms.Form (come fornito dall'ereditarietà di django.forms.ModelForm ) è il Visualizza con cui interagisci.
    • Questo rende le cose sfocate, poiché ModelForm è un Form , quindi i due livelli sono strettamente accoppiati. A mio parere, questo è stato fatto per brevità nel nostro codice e per il riutilizzo del codice all'interno del codice degli sviluppatori di Django.

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

Prendi, ad esempio, la classe django.db.models.Model di Django. Quando guardiamo gli oggetti django.db.models.Model , vediamo Modello 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 su come interagire con / convalidare i dati).
  • django.db.models.query è il Controller (gestisce l'input da Visualizza e lo traduce per il modello ).
  • django.db.models.Model è la Vista (con cui interagisce l'utente).
    • In questo caso, gli sviluppatori (tu ed io) sono gli "utenti".

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

  • Poiché abbiamo bisogno di sapere come interagire con django.db.models.Model , dovrebbero sapere come interagire con yourproject.forms.YourForm (il loro modello ).
  • Poiché non è necessario conoscere MySQLdb , i tuoi "sviluppatori lato client" non devono sapere nulla su yourproject.models.YourModel .
  • In entrambi i casi, raramente ci dobbiamo preoccupare di come il Controller sia effettivamente implementato.
risposta data 08.07.2011 - 18:28
fonte

Leggi altre domande sui tag