แบบฟอร์ม Django ละเมิด MVC หรือไม่


16

ฉันเพิ่งเริ่มทำงานกับ Django ที่มาจาก Spring MVC มาหลายปีและรูปแบบการติดตั้งใช้งานเหมือนเป็นบ้าเล็กน้อย หากคุณไม่คุ้นเคยรูปแบบ Django จะเริ่มต้นด้วยคลาสของโมเดลโมเดลที่กำหนดฟิลด์ของคุณ ฤดูใบไม้ผลิเริ่มต้นคล้ายกับวัตถุการสนับสนุนแบบฟอร์ม แต่ที่ Spring จัดเตรียมtaglibสำหรับการรวมองค์ประกอบของฟอร์มเข้ากับวัตถุสำรองภายใน JSP ของคุณ Django มีวิดเจ็ตของฟอร์มที่เชื่อมโยงโดยตรงกับโมเดล มีวิดเจ็ตเริ่มต้นที่คุณสามารถเพิ่มแอตทริบิวต์สไตล์ให้กับฟิลด์ของคุณเพื่อใช้ CSS หรือกำหนดวิดเจ็ตที่กำหนดเองอย่างสมบูรณ์เป็นคลาสใหม่ ทุกอย่างจะไปในรหัสหลามของคุณ ที่ดูเหมือนว่าถั่วกับฉัน ก่อนอื่นคุณจะใส่ข้อมูลเกี่ยวกับมุมมองของคุณโดยตรงในแบบจำลองของคุณและประการที่สองคุณจะผูกพันรูปแบบของคุณกับมุมมองที่เฉพาะเจาะจง ฉันพลาดอะไรไปรึเปล่า?

แก้ไข: โค้ดตัวอย่างบางส่วนตามที่ร้องขอ

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

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>

"ข้อมูลเกี่ยวกับมุมมองของคุณโดยตรงในแบบจำลองของคุณ"? กรุณาระบุ "ผูกโมเดลของคุณเข้ากับมุมมองเฉพาะ" หรือไม่ กรุณาระบุ โปรดระบุตัวอย่างที่ชัดเจนและเป็นรูปธรรม การร้องเรียนทั่วไปการโบกมือด้วยมือเช่นนี้เป็นการยากที่จะเข้าใจ
S.Lott

ฉันยังไม่ได้สร้างอะไรเลยแค่อ่านเอกสาร คุณผูกวิดเจ็ต HTML พร้อมกับคลาส CSS กับคลาสฟอร์มของคุณโดยตรงในรหัส Python นั่นคือสิ่งที่ฉันเรียก
jiggy

คุณต้องการผูกที่อื่นที่ไหน โปรดระบุตัวอย่างหรือลิงก์หรือข้อความอ้างอิงถึงสิ่งเฉพาะที่คุณคัดค้าน การโต้แย้งตามอำเภอใจยากที่จะติดตาม
S.Lott

ฉันทำ. ดูว่า Spring MVC ทำอย่างไร คุณฉีดวัตถุสำรองข้อมูล (เช่นคลาส Django ฟอร์ม) ลงในมุมมองของคุณ จากนั้นคุณเขียน HTML ของคุณโดยใช้ taglibs เพื่อให้คุณสามารถออกแบบ HTML ของคุณตามปกติและเพียงเพิ่มแอตทริบิวต์ path ที่จะผูกไว้กับคุณสมบัติของออบเจ็กต์การสำรองข้อมูลแบบฟอร์มของคุณ
jiggy

โปรดอัปเดตคำถามเพื่อให้ชัดเจนในสิ่งที่คุณคัดค้าน คำถามยากที่จะติดตาม ไม่มีรหัสตัวอย่างเพื่อทำให้จุดของคุณชัดเจนอย่างสมบูรณ์
S.Lott

คำตอบ:


21

ใช่รูปแบบ Django ยุ่งเหยิงจากมุมมอง MVC สมมติว่าคุณกำลังทำงานในเกมซุปเปอร์ฮีโร่ MMO ขนาดใหญ่และคุณกำลังสร้างโมเดลฮีโร่:

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

ตอนนี้คุณถูกขอให้สร้างแบบฟอร์มเพื่อให้ผู้เล่น MMO สามารถป้อนพลังวิเศษของฮีโร่ได้:

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

เนื่องจาก Shark Repellent เป็นอาวุธที่ทรงพลังมากหัวหน้าของคุณจึงขอให้คุณ จำกัด หากฮีโร่มี Shark Repellent เขาก็ไม่สามารถบินได้ สิ่งที่คนส่วนใหญ่ทำคือเพียงเพิ่มกฎธุรกิจนี้ในรูปแบบที่สะอาดและเรียกมันว่าวัน:

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

รูปแบบนี้ดูเท่ห์และอาจใช้งานกับโครงการขนาดเล็กได้ แต่จากประสบการณ์ของฉันมันยากมากที่จะดูแลในโครงการขนาดใหญ่ที่มีผู้พัฒนาหลายคน ปัญหาคือว่ารูปแบบเป็นส่วนหนึ่งของมุมมองของ MVC ดังนั้นคุณจะต้องจำไว้ว่ากฎเกณฑ์ทางธุรกิจทุกครั้งที่คุณ:

  • เขียนรูปแบบอื่นที่เกี่ยวข้องกับโมเดล Hero
  • เขียนสคริปต์ที่นำเข้าฮีโร่จากเกมอื่น
  • เปลี่ยนอินสแตนซ์โมเดลด้วยตนเองระหว่างกลไกของเกม
  • เป็นต้น

จุดของฉันที่นี่คือ forms.py ทั้งหมดเกี่ยวกับเค้าโครงแบบฟอร์มและการนำเสนอคุณไม่ควรเพิ่มตรรกะทางธุรกิจในไฟล์นั้นจนกว่าคุณจะสนุกกับการล้อเล่นด้วยรหัสสปาเก็ตตี้

วิธีที่ดีที่สุดในการจัดการกับปัญหาฮีโร่คือใช้วิธีการล้างข้อมูลบนแบบจำลองพร้อมกับสัญญาณที่กำหนดเอง คลีนโมเดลใช้งานได้เหมือนฟอร์มคลีน แต่ถูกเก็บในโมเดลเองทุกครั้งที่มีการล้าง HeroForm มันจะเรียกเมธอด Hero clean โดยอัตโนมัติ นี่เป็นแนวปฏิบัติที่ดีเพราะหากนักพัฒนารายอื่นเขียนแบบฟอร์มอีกฉบับสำหรับฮีโร่เขาจะได้รับการตรวจสอบความน่าเชื่อถือ / การบินได้ฟรี

ปัญหาเกี่ยวกับการล้างข้อมูลคือมันถูกเรียกใช้เฉพาะเมื่อโมเดลถูกแก้ไขโดยฟอร์ม จะไม่ถูกเรียกเมื่อคุณบันทึก () ด้วยตนเองและคุณสามารถจบลงด้วยฮีโร่ที่ไม่ถูกต้องในฐานข้อมูลของคุณ ในการแก้ปัญหานี้คุณสามารถเพิ่มผู้ฟังนี้ในโครงการของคุณ:

from django.db.models.signals import pre_save

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

วิธีนี้จะเรียกวิธีการที่สะอาดในการโทรผ่าน save () ทุกรุ่นของคุณ


นี่เป็นคำตอบที่มีประโยชน์มาก อย่างไรก็ตามแบบฟอร์มของฉันและการตรวจสอบที่สอดคล้องกันส่วนใหญ่เกี่ยวข้องกับหลายเขตข้อมูลในหลายรุ่น ฉันคิดว่านั่นเป็นสถานการณ์ที่สำคัญมากที่ต้องพิจารณา คุณจะตรวจสอบความถูกต้องดังกล่าวด้วยวิธีการทำความสะอาดแบบใดแบบหนึ่งได้อย่างไร
Bobort

8

คุณกำลังผสมสแต็กทั้งหมดมีหลายเลเยอร์ที่เกี่ยวข้อง:

  • Django Model กำหนดโครงสร้างข้อมูล

  • แบบฟอร์ม Django เป็นทางลัดในการกำหนดรูปแบบ HTML การตรวจสอบความถูกต้องของข้อมูลและการแปลค่า Python / HTML มันไม่จำเป็นอย่างเคร่งครัด แต่มักจะมีประโยชน์

  • Django ModelForm เป็นอีกหนึ่งทางลัดโดยย่อ subclass ของ Form ที่รับฟิลด์จากนิยามของ Model เป็นวิธีที่สะดวกสำหรับกรณีทั่วไปที่ใช้ฟอร์มเพื่อป้อนข้อมูลลงในฐานข้อมูล

และในที่สุดก็:

  • สถาปนิก Django ไม่ยึดติดกับโครงสร้าง MVC บางครั้งพวกเขาเรียกมันว่า MTV (มุมมองเทมเพลตโมเดล) เนื่องจากไม่มีสิ่งใดเป็นตัวควบคุมและการแยกระหว่างเทมเพลต (เพียงแค่การนำเสนอไม่มีตรรกะ) และมุมมอง (เพียงตรรกะที่ผู้ใช้พบไม่มี HTML) จึงสำคัญเท่ากับการแยกโมเดล

บางคนเห็นว่าเป็นบาป แต่สิ่งสำคัญคือต้องจำไว้ว่า MVC นั้นถูกกำหนดไว้สำหรับแอปพลิเคชัน GUI แต่แรกและมันค่อนข้างเหมาะสำหรับเว็บแอป


แต่วิดเจ็ตเป็นการนำเสนอและจะถูกเชื่อมต่อเข้ากับแบบฟอร์มของคุณ แน่นอนว่าฉันไม่สามารถใช้งานได้ แต่คุณจะสูญเสียผลประโยชน์จากการเชื่อมโยงและการตรวจสอบความถูกต้อง ประเด็นของฉันคือสปริงให้การเชื่อมโยงและการตรวจสอบความถูกต้องกับคุณและการแยกโมเดลและมุมมองอย่างสมบูรณ์ ฉันคิดว่า Django สามารถนำสิ่งที่คล้ายกันไปใช้ได้อย่างง่ายดาย และฉันดูการกำหนดค่า url เป็นตัวควบคุมด้านหน้าที่สร้างขึ้นซึ่งเป็นรูปแบบที่ได้รับความนิยมอย่างมากสำหรับ Spring MVC
jiggy

รหัสที่สั้นที่สุดชนะ
วินไคลน์

1
@jiggy: แบบฟอร์มเป็นส่วนหนึ่งของงานนำเสนอการเชื่อมโยงและการตรวจสอบความถูกต้องสำหรับข้อมูลที่ผู้ใช้ป้อนเท่านั้น โมเดลมีผลผูกพันและการตรวจสอบของตัวเองแยกและเป็นอิสระจากแบบฟอร์ม modelform เป็นเพียงช็อตคัตเมื่อพวกเขาเป็น 1: 1 (หรือเกือบ)
Javier

ทราบเพียงเล็กน้อยว่าใช่ MVC ไม่สมเหตุสมผลในเว็บแอป ... จนกระทั่ง AJAX นำกลับมาใช้อีกครั้ง
AlexanderJohannesen

แสดงแบบฟอร์มคือมุมมอง การตรวจสอบแบบฟอร์มเป็นตัวควบคุม ข้อมูลในแบบฟอร์มเป็นแบบจำลอง อย่างน้อย IMO Django munges พวกเขาทั้งหมดเข้าด้วยกัน มันหมายความว่าถ้าคุณจ้างนักพัฒนาด้านลูกค้าโดยเฉพาะ (อย่างที่ บริษัท ของฉันทำ) สิ่งทั้งหมดนี้ไร้ประโยชน์
jiggy

4

ฉันตอบคำถามเก่านี้เพราะคำตอบอื่น ๆ ดูเหมือนจะหลีกเลี่ยงปัญหาเฉพาะที่กล่าวถึง

แบบฟอร์ม Django ช่วยให้คุณสามารถเขียนโค้ดเล็ก ๆ น้อย ๆ และสร้างฟอร์มด้วยค่าเริ่มต้นที่มีสติ จำนวนของการปรับแต่งใด ๆ อย่างรวดเร็วนำไปสู่ ​​"รหัสเพิ่มเติม" และ "ทำงานมากขึ้น" และค่อนข้างโมฆะประโยชน์หลักของระบบแบบฟอร์ม

เทมเพลตไลบรารีเช่นdjango-widget-tweaksทำให้การกำหนดฟอร์มเองง่ายขึ้นมาก หวังว่าการปรับแต่งฟิลด์เช่นนี้จะเป็นเรื่องง่ายในที่สุดด้วยการติดตั้ง vanilla Django

ตัวอย่างของคุณกับ 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

(ฉันใช้Italicsเพื่อแสดงถึงแนวคิด MVC เพื่อทำให้อ่านง่ายขึ้น)

ไม่ในความคิดของฉันพวกเขาไม่ทำลาย MVC เมื่อทำงานกับ Django Models / Forms ให้คิดว่าเป็นการใช้ MVC stack ทั้งหมดเป็นแบบจำลอง :

  1. django.db.models.Modelคือโมเดลพื้นฐาน(เก็บข้อมูลและตรรกะทางธุรกิจ)
  2. django.forms.ModelFormให้ควบคุมdjango.db.models.Modelสำหรับการโต้ตอบกับ
  3. django.forms.Form(ตามที่ระบุไว้ผ่านการสืบทอดโดยdjango.forms.ModelForm) คือมุมมองที่คุณโต้ตอบด้วย
    • สิ่งนี้ทำให้สิ่งต่าง ๆ ไม่ชัดเจนเนื่องจากModelFormเป็น a Formดังนั้นทั้งสองชั้นจึงเชื่อมติดกันอย่างแน่นหนา ในความคิดของฉันสิ่งนี้ทำเพื่อความกะทัดรัดในรหัสของเราและสำหรับการใช้รหัสซ้ำภายในรหัสนักพัฒนาของ Django

ด้วยวิธีนี้django.forms.ModelForm(ด้วยข้อมูลและตรรกะทางธุรกิจ) จะกลายเป็นรูปแบบของตัวเอง คุณสามารถอ้างอิงมันเป็น (MVC) VC ซึ่งเป็นการนำไปใช้ที่ค่อนข้างทั่วไปใน OOP

ยกตัวอย่างเช่นdjango.db.models.Modelคลาสของ Django เมื่อเราดูdjango.db.models.Modelวัตถุเราจะเห็นแบบจำลองแม้ว่าจะมีการนำ MVC มาใช้อย่างสมบูรณ์แล้ว สมมติว่า MySQL เป็นฐานข้อมูลส่วนหลัง:

  • MySQLdbคือModel (เลเยอร์การจัดเก็บข้อมูลและตรรกะทางธุรกิจเกี่ยวกับวิธีการโต้ตอบ / ตรวจสอบความถูกต้องของข้อมูล)
  • django.db.models.queryคือคอนโทรลเลอร์ (จัดการอินพุตจากมุมมองและแปลเป็นโมเดล )
  • django.db.models.Modelคือมุมมอง (สิ่งที่ผู้ใช้โต้ตอบกับ)
    • ในกรณีนี้นักพัฒนาซอฟต์แวร์ (คุณและฉัน) คือ 'ผู้ใช้'

การโต้ตอบนี้เหมือนกับของคุณ "นักพัฒนาฝั่งไคลเอ็นต์" เมื่อทำงานกับวัตถุyourproject.forms.YourForm(สืบทอดมาจากdjango.forms.ModelForm):

  • เมื่อเราต้องการทราบวิธีการโต้ตอบกับdjango.db.models.Modelพวกเขาจะต้องรู้วิธีการโต้ตอบกับyourproject.forms.YourForm( แบบจำลอง )
  • เนื่องจากเราไม่จำเป็นต้องรู้MySQLdb"นักพัฒนาฝั่งไคลเอ็นต์" ของคุณไม่จำเป็นต้องรู้อะไรyourproject.models.YourModelเลย
  • ในทั้งสองกรณีเราแทบจะไม่ต้องกังวลเกี่ยวกับการใช้งานคอนโทรลเลอร์จริง ๆ

1
แทนที่จะใช้ซีแมนทิกส์อภิปรายฉันต้องการเก็บ HTML และ CSS ไว้ในแม่แบบของฉันและไม่ต้องใส่มันลงในไฟล์. py นอกเหนือจากหลักปรัชญานั่นเป็นเพียงจุดสิ้นสุดที่ใช้งานได้จริงที่ฉันต้องการบรรลุเพราะมีประสิทธิภาพมากขึ้นและช่วยให้สามารถแบ่งงานได้ดีขึ้น
jiggy

1
ที่ยังคงเป็นไปได้อย่างสมบูรณ์แบบ คุณสามารถเขียนฟิลด์ของคุณด้วยตนเองในเทมเพลตเขียนการตรวจสอบด้วยตนเองในมุมมองของคุณแล้วอัปเดตโมเดลของคุณด้วยตนเอง แต่การออกแบบฟอร์มของ Django นั้นไม่ได้ทำลาย MVC
Jack M.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.