Apakah formulir Django melanggar MVC?

16

Saya baru saja mulai bekerja dengan Django yang berasal dari Spring MVC selama bertahun-tahun dan implementasi formulir terasa sedikit gila. Jika Anda tidak terbiasa, formulir Django dimulai dengan kelas model formulir yang mendefinisikan bidang Anda. Musim semi juga dimulai dengan objek dukungan bentuk. Tetapi di mana Spring menyediakan taglib untuk mengikat elemen formulir ke objek dukungan dalam JSP Anda, Django memiliki widget formulir yang diikat langsung ke model. Ada widget default tempat Anda dapat menambahkan atribut style ke bidang Anda untuk menerapkan CSS atau mendefinisikan widget khusus sepenuhnya sebagai kelas baru. Semuanya ada dalam kode python Anda. Sepertinya itu gila bagi saya. Pertama, Anda menempatkan informasi tentang tampilan Anda langsung di model Anda dan kedua Anda mengikat model Anda ke tampilan tertentu. Apakah saya melewatkan sesuatu?

EDIT: Beberapa kode contoh seperti yang diminta.

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
sumber
"informasi tentang pandangan Anda langsung di model Anda"? Harap spesifik. "mengikat model Anda ke tampilan tertentu"? Harap spesifik. Harap berikan contoh konkret dan spesifik. Keluhan umum yang melambaikan tangan seperti ini sulit dipahami, apalagi menanggapinya.
S.Lott
Saya belum membangun apa pun, hanya membaca dokumen. Anda mengikat widget HTML bersama dengan kelas CSS ke kelas Formulir Anda langsung dalam kode Python. Itulah yang saya panggil.
jiggy
di mana lagi Anda ingin melakukan pengikatan ini? Harap berikan contoh atau tautan atau kutipan ke hal spesifik yang Anda tolak. Argumen hipotetis sulit untuk diikuti.
S.Lott
Aku melakukannya. Lihatlah bagaimana Spring MVC melakukannya. Anda menyuntikkan objek dukungan formulir (seperti kelas Formulir Django) ke tampilan Anda. Kemudian Anda menulis HTML menggunakan taglibs sehingga Anda bisa mendesain HTML seperti biasa dan hanya menambahkan atribut jalur yang akan mengikatnya ke properti objek dukungan-bentuk Anda.
jiggy
Harap perbarui pertanyaan untuk memperjelas dengan jelas apa yang Anda tolak. Pertanyaan itu sulit diikuti. Tidak memiliki kode contoh untuk membuat poin Anda sangat jelas.
S.Lott

Jawaban:

21

Ya, bentuk Django berantakan dari perspektif MVC, misalkan Anda bekerja di game pahlawan super MMO besar dan Anda membuat model Pahlawan:

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

Sekarang Anda diminta untuk membuat formulir untuk itu, sehingga para pemain MMO dapat memasukkan kekuatan super pahlawan mereka:

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

Karena Penolak Hiu adalah senjata yang sangat kuat, bos Anda meminta Anda untuk membatasi itu. Jika seorang pahlawan memiliki Penolak Hiu maka dia tidak bisa terbang. Apa yang dilakukan kebanyakan orang hanyalah menambahkan aturan bisnis ini dalam formulir bersih dan menyebutnya sehari:

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

Pola ini terlihat keren dan mungkin bekerja pada proyek-proyek kecil, tetapi dalam pengalaman saya ini sangat sulit untuk dipertahankan dalam proyek-proyek besar dengan banyak pengembang. Masalahnya adalah bahwa formulir adalah bagian dari pandangan MVC. Jadi, Anda harus ingat aturan bisnis itu setiap kali Anda:

  • Tulis formulir lain yang berhubungan dengan model Pahlawan.
  • Tulis skrip yang mengimpor pahlawan dari gim lain.
  • Ubah instance model secara manual selama mekanik permainan.
  • dll.

Maksud saya di sini adalah bahwa forms.py adalah semua tentang tata letak formulir dan presentasi, Anda tidak boleh menambahkan logika bisnis dalam file itu kecuali Anda menikmati bermain-main dengan kode spaghetti.

Cara terbaik untuk menangani masalah pahlawan adalah dengan menggunakan metode model clean plus sinyal khusus. Model clean berfungsi seperti form clean tetapi disimpan dalam model itu sendiri, setiap kali HeroForm dibersihkan secara otomatis memanggil metode Hero clean. Ini adalah praktik yang baik karena jika pengembang lain menulis formulir lain untuk Pahlawan, ia akan mendapatkan validasi penolak / lalat secara gratis.

Masalah dengan bersih adalah bahwa itu dipanggil hanya ketika Model dimodifikasi oleh formulir. Itu tidak dipanggil ketika Anda secara manual menyimpan () dan Anda dapat berakhir dengan pahlawan yang tidak valid di database Anda. Untuk mengatasi masalah ini, Anda dapat menambahkan pendengar ini ke proyek Anda:

from django.db.models.signals import pre_save

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

Ini akan memanggil metode bersih pada setiap panggilan save () untuk semua model Anda.

Cesar Canassa
sumber
Ini jawaban yang sangat membantu. Namun, sebagian besar formulir saya dan validasi yang sesuai melibatkan beberapa bidang pada beberapa model. Saya pikir itu adalah skenario yang sangat penting untuk dipertimbangkan. Bagaimana Anda melakukan validasi seperti itu pada salah satu metode bersih model?
Bobort
8

Anda sedang mencampur seluruh tumpukan, ada beberapa lapisan yang terlibat:

  • Model Django mendefinisikan struktur data.

  • Formulir Django adalah jalan pintas untuk menentukan formulir HTML, validasi bidang, dan terjemahan nilai Python / HTML. Ini tidak sepenuhnya dibutuhkan, tetapi sering kali berguna.

  • Django ModelForm adalah jalan pintas lain, singkatnya subclass Formulir yang mendapatkan bidangnya dari definisi Model. Hanya cara praktis untuk kasus umum di mana formulir digunakan untuk memasukkan data ke dalam basis data.

dan akhirnya:

  • Arsitek Django tidak mematuhi persis struktur MVC. Kadang-kadang mereka menyebutnya MTV (Model Template View); karena tidak ada yang namanya controller, dan pemisahan antara template (hanya presentasi, tidak ada logika) dan View (hanya logika yang menghadap pengguna, tidak ada HTML) sama pentingnya dengan isolasi Model.

Beberapa orang melihatnya sebagai bid'ah; tetapi penting untuk diingat bahwa MVC pada awalnya ditentukan untuk aplikasi GUI, dan itu agak canggung untuk aplikasi web.

Javier
sumber
Tetapi widget adalah presentasi dan mereka terhubung langsung ke Formulir Anda. Tentu, saya tidak bisa menggunakannya, tetapi kemudian Anda kehilangan manfaat dari pengikatan dan validasi. Maksud saya adalah Spring memberi Anda pengikatan dan validasi serta pemisahan lengkap antara model dan tampilan. Saya akan berpikir Django bisa dengan mudah mengimplementasikan sesuatu yang serupa. Dan saya melihat konfigurasi url sebagai semacam built in front controller yang merupakan pola yang cukup populer untuk Spring MVC.
jiggy
Kode terpendek menang.
kevin cline
1
@ jiggy: formulir adalah bagian dari presentasi, pengikatan dan validasi hanya untuk data yang dimasukkan pengguna. model memiliki ikatan dan validasi mereka sendiri, terpisah dan independen dari formulir. modelform hanyalah jalan pintas ketika mereka 1: 1 (atau hampir)
Javier
Hanya sedikit catatan bahwa, ya, MVC tidak benar-benar masuk akal di aplikasi web ... sampai AJAX memasangnya kembali.
AlexanderJohannesen
Tampilan formulir adalah tampilan. Validasi form adalah pengontrol. Formulir data adalah model. IMO, setidaknya. Django munges mereka semua bersama. Selain kesedihan, itu berarti bahwa jika Anda mempekerjakan pengembang sisi klien yang berdedikasi (seperti perusahaan saya) semua ini agak tidak berguna.
jiggy
4

Saya menjawab pertanyaan lama ini karena jawaban lain tampaknya menghindari masalah spesifik yang disebutkan.

Formulir Django memungkinkan Anda untuk dengan mudah menulis kode kecil dan membuat formulir dengan standar waras. Setiap jumlah penyesuaian sangat cepat mengarah ke "lebih banyak kode" dan "lebih banyak pekerjaan" dan agak membatalkan manfaat utama dari sistem formulir

Perpustakaan templat seperti django-widget-tweaks membuat menyesuaikan formulir lebih mudah. Semoga kustomisasi bidang seperti ini pada akhirnya akan mudah dengan instalasi vanilla Django.

Contoh Anda dengan 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
sumber
1

(Saya telah menggunakan Miring untuk menandakan konsep MVC untuk membuat ini lebih mudah dibaca.)

Tidak, menurut saya, mereka tidak merusak MVC. Ketika bekerja dengan Model / Formulir Django, anggap itu menggunakan seluruh tumpukan MVC sebagai Model :

  1. django.db.models.Modeladalah Model dasar (memegang data dan logika bisnis).
  2. django.forms.ModelFormmenyediakan Controller untuk berinteraksi dengan django.db.models.Model.
  3. django.forms.Form(seperti yang disediakan melalui pewarisan oleh django.forms.ModelForm) adalah View yang berinteraksi dengan Anda.
    • Ini memang membuat hal-hal buram, karena ModelForma Form, sehingga dua lapisan yang erat. Menurut pendapat saya, ini dilakukan untuk singkatnya kode kami, dan untuk penggunaan kembali kode dalam kode pengembang Django.

Dengan cara ini, django.forms.ModelForm(dengan data dan logika bisnisnya) menjadi Model itu sendiri. Anda bisa merujuknya sebagai (MVC) VC, yang merupakan implementasi yang cukup umum di OOP.

Ambil contoh, django.db.models.Modelkelas Django . Ketika kita melihat django.db.models.Modelobjek, kita melihat Model meskipun itu sudah merupakan implementasi penuh dari MVC. Dengan asumsi MySQL adalah database back end:

  • MySQLdbadalah Model (lapisan penyimpanan data, dan logika bisnis mengenai cara berinteraksi dengan / memvalidasi data).
  • django.db.models.queryadalah Controller (menangani input dari View dan menerjemahkannya untuk Model ).
  • django.db.models.Modeladalah View (apa yang berinteraksi dengan pengguna).
    • Dalam hal ini, pengembang (Anda dan saya) adalah 'pengguna'.

Interaksi ini sama dengan "pengembang sisi klien" Anda ketika bekerja dengan yourproject.forms.YourForm(mewarisi dari django.forms.ModelForm) objek:

  • Karena kita perlu tahu cara berinteraksi django.db.models.Model, mereka perlu tahu cara berinteraksi yourproject.forms.YourForm( Model mereka ).
  • Karena kami tidak perlu tahu tentang MySQLdb, "pengembang sisi klien" Anda tidak perlu tahu apa-apa tentang yourproject.models.YourModel.
  • Dalam kedua kasus tersebut, kita sangat jarang perlu khawatir tentang bagaimana Controller sebenarnya diimplementasikan.
Jack M.
sumber
1
Daripada berdebat semantik, saya hanya ingin menyimpan HTML dan CSS saya di template saya dan tidak harus memasukkannya ke file .py. Selain filosofi, itu hanya tujuan praktis yang ingin saya capai karena lebih efisien dan memungkinkan pembagian kerja yang lebih baik.
jiggy
1
Itu masih sangat mungkin. Anda dapat secara manual menulis bidang Anda di templat, menulis validasi Anda secara manual dalam tampilan Anda, dan kemudian memperbarui model Anda secara manual. Namun desain Formulir Django tidak merusak MVC.
Jack M.