Czy formularze Django naruszają MVC?

16

Właśnie zacząłem pracować z Django pochodzącym z lat Spring MVC, a implementacja formularzy wydaje się nieco szalona. Jeśli nie jesteś zaznajomiony, formularze Django zaczynają się od klasy modelu formularza, która definiuje twoje pola. Wiosna podobnie zaczyna się od obiektu opartego na formularzu. Ale tam, gdzie Spring udostępnia taglib do wiązania elementów formularza do obiektu podkładu w JSP, Django ma widżety formularzy powiązane bezpośrednio z modelem. Istnieją domyślne widżety, w których można dodać atrybuty stylu do pól w celu zastosowania CSS lub zdefiniować całkowicie niestandardowe widżety jako nowe klasy. Wszystko idzie w kodzie Pythona. To wydaje mi się szalone. Po pierwsze, umieszczasz informacje o swoim widoku bezpośrednio w swoim modelu, a po drugie wiążesz swój model z określonym widokiem. Czy coś brakuje?

EDYCJA: Niektóre przykładowy kod na żądanie.

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'}))

Wiosna 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>
nerwowy
źródło
„informacje o twoim widoku bezpośrednio w twoim modelu”? Proszę, bądź konkretny. „powiązanie twojego modelu z konkretnym widokiem”? Proszę, bądź konkretny. Proszę podać konkretne, konkretne przykłady. Taki ogólny powód machania ręką jest trudny do zrozumienia, a tym bardziej nie można na nie odpowiedzieć.
S.Lott,
Jeszcze niczego nie zbudowałem, po prostu czytam dokumenty. Wiążesz widżet HTML wraz z klasami CSS z klasą Form bezpośrednio w kodzie Python. Właśnie to wołam.
jiggy
gdzie jeszcze chcesz zrobić to wiązanie? Podaj przykład lub link lub cytat dotyczący konkretnej rzeczy, której jesteś przeciwny. Hipotetyczny argument jest trudny do naśladowania.
S.Lott,
Zrobiłem. Zobacz, jak robi to Spring MVC. Wstrzykujesz obiekt wspierający formularz (taki jak klasa Form Django) do swojego widoku. Następnie piszesz HTML przy użyciu taglibs, abyś mógł zaprojektować HTML w normalny sposób i po prostu dodać atrybut ścieżki, który będzie go wiązał z właściwościami obiektu opartego na formularzu.
jiggy
Proszę zaktualizować pytanie, aby to całkowicie jasne, co masz do sprzeciwu. Trudno odpowiedzieć na pytanie. Nie ma żadnego przykładowego kodu, który idealnie wyjaśni twój punkt .
S.Lott,

Odpowiedzi:

21

Tak, formy Django to bałagan z perspektywy MVC, załóżmy, że pracujesz w wielkiej grze MMO superbohater i tworzysz model Bohatera:

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

Teraz musisz utworzyć formularz, aby gracze MMO mogli wprowadzić swoje super moce bohatera:

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

Ponieważ Odstraszacz Rekinów jest bardzo potężną bronią, twój szef poprosił cię o jej ograniczenie. Jeśli bohater ma Odstraszacz Rekinów, nie może latać. To, co większość ludzi robi, to po prostu dodać tę regułę biznesową w postaci czystej i nazwać to dniem:

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

Ten wzór wygląda fajnie i może działać na małych projektach, ale z mojego doświadczenia jest bardzo trudny do utrzymania w dużych projektach z wieloma programistami. Problem polega na tym, że formularz jest częścią widoku MVC. Musisz więc pamiętać tę regułę biznesową za każdym razem, gdy:

  • Napisz inną formę, która dotyczy modelu Bohatera.
  • Napisz skrypt, który importuje bohaterów z innej gry.
  • Ręcznie zmieniaj instancję modelu podczas mechaniki gry.
  • itp.

Chodzi mi o to, że forms.py dotyczy układu i prezentacji formularza, nigdy nie należy dodawać logiki biznesowej do tego pliku, chyba że lubisz bawić się kodem spaghetti.

Najlepszym sposobem na rozwiązanie problemu z bohaterem jest użycie metody czyszczenia modelu oraz niestandardowego sygnału. Model clean działa podobnie do formularza clean, ale jest przechowywany w samym modelu, za każdym razem, gdy HeroForm jest czyszczony, automatycznie wywołuje metodę Hero clean. Jest to dobra praktyka, ponieważ jeśli inny programista napisze kolejną formę dla Bohatera, otrzyma bezpłatną weryfikację odstraszania / muchy.

Problem z czyszczeniem polega na tym, że jest wywoływany tylko wtedy, gdy model jest modyfikowany przez formularz. Nie jest wywoływany, gdy ręcznie go zapiszesz () i możesz skończyć z niepoprawnym bohaterem w bazie danych. Aby przeciwdziałać temu problemowi, możesz dodać tego detektora do swojego projektu:

from django.db.models.signals import pre_save

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

Spowoduje to wywołanie metody clean przy każdym wywołaniu funkcji save () dla wszystkich modeli.

Cesar Canassa
źródło
To bardzo pomocna odpowiedź. Jednak większość moich formularzy i odpowiadająca im walidacja obejmuje kilka pól na kilku modelach. Myślę, że jest to bardzo ważny scenariusz do rozważenia. Jak przeprowadziłbyś taką walidację na jednej z czystych metod modelu?
Bobort
8

Mieszasz cały stos, w grę wchodzi kilka warstw:

  • Model Django definiuje strukturę danych.

  • Formularz Django to skrót do definiowania formularzy HTML, sprawdzania poprawności pól i tłumaczenia wartości w języku Python / HTML. Nie jest to absolutnie potrzebne, ale często przydatne.

  • Django ModelForm to kolejny skrót, w skrócie podklasa Form, która pobiera swoje pola z definicji Modelu. Po prostu wygodny sposób na częsty przypadek, gdy formularz służy do wprowadzania danych do bazy danych.

i w końcu:

  • Architekci Django nie przestrzegają dokładnie struktury MVC. Czasami nazywają to MTV (widok szablonu modelu); ponieważ nie ma czegoś takiego jak kontroler, a podział na szablon (tylko prezentacja, bez logiki) i widok (tylko logika zorientowana na użytkownika, bez HTML) jest równie ważny jak izolacja modelu.

Niektórzy uważają to za herezję; ale ważne jest, aby pamiętać, że MVC zostało pierwotnie zdefiniowane dla aplikacji GUI i jest raczej niezręczne dla aplikacji internetowych.

Javier
źródło
Ale widżety są prezentacją i są podłączone bezpośrednio do formularza. Jasne, nie mogę ich używać, ale wtedy tracisz korzyści z wiązania i sprawdzania poprawności. Chodzi mi o to, że Spring daje ci wiążące i sprawdzanie poprawności oraz całkowite rozdzielenie modelu i widoku. Wydaje mi się, że Django mógł łatwo wdrożyć coś podobnego. I patrzę na konfigurację adresu URL jako rodzaj wbudowanego kontrolera przedniego, który jest dość popularnym wzorcem dla Spring MVC.
jiggy
Najkrótszy kod wygrywa.
kevin cline
1
@ jiggy: formularze są częścią prezentacji, wiązanie i sprawdzanie poprawności dotyczy tylko danych wprowadzonych przez użytkownika. modele mają swoje wiążące i walidacyjne, oddzielne i niezależne od formularzy. modelform jest tylko skrótem, gdy mają 1: 1 (lub prawie)
Javier
Wystarczy niewielka uwaga, że ​​tak, MVC tak naprawdę nie miało sensu w aplikacjach internetowych ... dopóki AJAX nie przywróci go ponownie.
AlexanderJohannesen
Wyświetlanie formularza to widok. Sprawdzanie poprawności formularza jest kontrolerem. Dane formularza to model. Przynajmniej IMO. Django łączy je wszystkie razem. Pomijając pedanterię, oznacza to, że jeśli zatrudnisz dedykowanych programistów po stronie klienta (podobnie jak moja firma), to wszystko jest trochę bezużyteczne.
jiggy
4

Odpowiadam na to stare pytanie, ponieważ wydaje się, że w innych odpowiedziach nie ma wspomnianego konkretnego problemu.

Formularze Django umożliwiają łatwe pisanie małego kodu i tworzenie formularza z rozsądnymi ustawieniami domyślnymi. Każda modyfikacja bardzo szybko prowadzi do „więcej kodu” i „więcej pracy” i nieco niweluje podstawową korzyść systemu formularzy

Biblioteki szablonów, takie jak ulepszenia widgetów django, znacznie ułatwiają dostosowywanie formularzy. Mamy nadzieję, że takie modyfikacje w terenie będą łatwe dzięki instalacji waniliowej Django.

Twój przykład z 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
źródło
1

(Użyłem kursywy do oznaczenia koncepcji MVC, aby uczynić to bardziej czytelnym.)

Nie, moim zdaniem, nie łamią MVC. Pracując z modelami / formularzami Django, pomyśl o tym jako o użyciu całego stosu MVC jako modelu :

  1. django.db.models.Modeljest modelem podstawowym (zawiera dane i logikę biznesową).
  2. django.forms.ModelFormzapewnia kontroler do interakcji django.db.models.Model.
  3. django.forms.Form(pod warunkiem dziedziczenia przez django.forms.ModelForm) to widok, z którym wchodzisz w interakcje.
    • Powoduje to rozmycie, ponieważ ModelFormjest to Form, więc dwie warstwy są ściśle ze sobą powiązane. Moim zdaniem zostało to zrobione ze względu na zwięzłość naszego kodu i ponowne użycie kodu w kodzie programisty Django.

W ten sposób django.forms.ModelForm(wraz z logiką danych i biznesu) staje się samym modelem . Można go nazwać VC (MVC), co jest dość powszechną implementacją w OOP.

Weźmy na przykład django.db.models.Modelklasę Django . Kiedy patrzymy na django.db.models.Modelobiekty, widzimy Model, mimo że jest to już pełna implementacja MVC. Zakładając, że MySQL jest bazą danych zaplecza:

  • MySQLdbto Model (warstwa przechowywania danych i logika biznesowa dotycząca interakcji z danymi / sprawdzania ich poprawności).
  • django.db.models.queryjest kontrolerem (obsługuje dane wejściowe z widoku i tłumaczy je dla modelu ).
  • django.db.models.Modeljest Widok (z czym użytkownik wchodzi w interakcje).
    • W tym przypadku programiści (ty i ja) są „użytkownikami”.

Ta interakcja jest taka sama, jak twoi „programiści po stronie klienta” podczas pracy z yourproject.forms.YourForm(dziedziczeniem po django.forms.ModelForm) obiektami:

  • Ponieważ musimy wiedzieć, jak wchodzić w interakcje django.db.models.Model, musieliby wiedzieć, jak wchodzić w interakcje yourproject.forms.YourForm(ich Model ).
  • Ponieważ nie musimy wiedzieć o tym MySQLdb, twoi „programiści po stronie klienta” nie muszą nic wiedzieć yourproject.models.YourModel.
  • W obu przypadkach bardzo rzadko musimy martwić się o to, jak kontroler jest faktycznie implementowany.
Jack M.
źródło
1
Zamiast semantyki debaty chcę po prostu zachować HTML i CSS w moich szablonach i nie muszę umieszczać żadnego z nich w plikach .py. Pomijając filozofię, to tylko praktyczny cel, który chcę osiągnąć, ponieważ jest bardziej wydajny i pozwala na lepszy podział pracy.
jiggy
1
To wciąż jest całkowicie możliwe. Możesz ręcznie pisać pola w szablonach, ręcznie pisać swoje sprawdzenie poprawności w swoich widokach, a następnie ręcznie aktualizować modele. Ale projekt formularzy Django nie łamie MVC.
Jack M.,