Формы Джанго нарушают MVC?

16

Я только начал работать с Django, пришедшим из лет Spring MVC, и реализация форм выглядит немного сумасшедшей. Если вы не знакомы, формы Django начинаются с класса модели формы, который определяет ваши поля. Spring также начинается с объекта поддержки формы. Но там, где Spring предоставляет taglib для привязки элементов формы к базовому объекту в вашем JSP, Django имеет виджеты форм, привязанные непосредственно к модели. Существуют виджеты по умолчанию, в которых вы можете добавить атрибуты стиля к своим полям, чтобы применять CSS или определять полностью настраиваемые виджеты как новые классы. Все это идет в вашем коде Python. Это кажется мне чокнутым. Во-первых, вы помещаете информацию о своем представлении непосредственно в вашу модель, а во-вторых, вы связываете свою модель с конкретным представлением. Я что-то пропустил?

РЕДАКТИРОВАТЬ: Пример кода в соответствии с просьбой.

Джанго:

# 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
источник
"информация о вашем взгляде прямо в вашей модели"? Пожалуйста, будьте конкретны. "привязка вашей модели к конкретному виду"? Пожалуйста, будьте конкретны. Пожалуйста, предоставьте конкретные, конкретные примеры. Такую жалобу, сложенную вручную, трудно понять, а тем более ответить на нее.
С.Лотт
Я еще ничего не построил, просто читал документы. Вы связываете виджет HTML вместе с CSS-классами с вашим классом Form непосредственно в коде Python. Это то, что я зову.
Джигги
где еще вы хотите сделать это связывание? Пожалуйста, предоставьте пример или ссылку или цитату на конкретную вещь, на которую вы возражаете. Гипотетический аргумент трудно следовать.
С.Лотт
Я сделал. Посмотрите, как Spring MVC делает это. Вы внедряете объект поддержки формы (например, класс Django Form) в ваше представление. Затем вы пишете свой HTML, используя taglibs, так что вы можете создать свой HTML как обычный и просто добавить атрибут пути, который свяжет его со свойствами вашего объекта поддержки формы.
Джигги
Пожалуйста, обновите вопрос, чтобы было совершенно ясно, на что вы возражаете. Вопрос трудно понять. У него нет примера кода, чтобы сделать вашу точку зрения совершенно ясной.
С.Лотт

Ответы:

21

Да, формы Django - беспорядок с точки зрения MVC, предположим, вы работаете в большой игре супергероев MMO и создаете модель Hero:

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

Теперь вас попросят создать для него форму, чтобы игроки ММО могли вводить суперсилы своего героя:

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

Поскольку 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. Таким образом, вам придется помнить это бизнес-правило каждый раз, когда вы:

  • Напишите другую форму, которая касается модели героя.
  • Напишите скрипт, который импортирует героев из другой игры.
  • Вручную измените экземпляр модели во время игровой механики.
  • и т.п.

Суть в том, что файл forms.py полностью посвящен макету формы и представлению, вам никогда не следует добавлять бизнес-логику в этот файл, если вам не нравится возиться со спагетти-кодом.

Лучший способ справиться с проблемой героя - использовать метод очистки модели и специальный сигнал. Очистка модели работает так же, как очистка формы, но она сохраняется в самой модели: при очистке HeroForm автоматически вызывается метод очистки Hero. Это хорошая практика, потому что если другой разработчик напишет другую форму для Героя, он получит бесплатную проверку репеллента / мухи.

Проблема с чистым заключается в том, что он вызывается только тогда, когда модель изменяется формой. Он не вызывается, когда вы сохраняете его вручную (), и вы можете получить недопустимого героя в вашей базе данных. Чтобы противостоять этой проблеме, вы можете добавить этот слушатель в свой проект:

from django.db.models.signals import pre_save

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

Это вызовет метод clean при каждом вызове save () для всех ваших моделей.

Сезар Канасса
источник
Это очень полезный ответ. Тем не менее, большая часть моих форм и соответствующей проверки включает в себя несколько полей в нескольких моделях. Я думаю, что это очень важный сценарий для рассмотрения. Как бы вы провели такую ​​проверку на одном из чистых методов модели?
Боборт
8

Вы смешиваете весь стек, есть несколько слоев:

  • Модель Джанго определяет структуру данных.

  • Форма Django - это ярлык для определения форм HTML, проверки полей и перевода значений Python / HTML. Это не строго необходимо, но часто удобно.

  • Django ModelForm - это еще один ярлык, короче говоря, подкласс Form, который получает свои поля из определения модели. Просто удобный способ для общего случая, когда форма используется для ввода данных в базу данных.

и наконец:

  • Архитекторы Django точно не придерживаются структуры MVC. Иногда они называют это MTV (представление шаблона модели); потому что нет такого понятия, как контроллер, а разделение между шаблоном (только представление, без логики) и представлением (просто логика, ориентированная на пользователя, без HTML) так же важно, как и изоляция модели.

Некоторые люди считают это ересью; но важно помнить, что MVC изначально был определен для приложений с графическим интерфейсом, и это довольно неудобно для веб-приложений.

Хавьер
источник
Но виджеты являются презентацией, и они подключены прямо к вашей форме. Конечно, я не могу их использовать, но тогда вы потеряете преимущества привязки и проверки. Моя точка зрения заключается в том, что Spring дает вам привязку, проверку и полное разделение модели и представления. Я думаю, что Django мог бы легко реализовать нечто подобное. И я рассматриваю настройку URL как своего рода встроенный фронт-контроллер, который является довольно популярным шаблоном для Spring MVC.
Джигги
Самый короткий код выигрывает.
Кевин Клайн
1
@jiggy: формы являются частью презентации, привязка и проверка только для введенных пользователем данных. модели имеют свои собственные привязки и проверки, отдельные и независимые от форм. форма модели - это просто сокращение, когда они равны 1: 1 (или почти)
Хавьер
Просто небольшое замечание, что да, MVC действительно не имеет смысла в веб-приложениях ... пока AJAX не вернул его снова.
Александр
Отображение формы является просмотром. Проверка формы контролером. Данные формы - это модель. ИМО, по крайней мере. Джанго наполняет их всех вместе. Помимо педантизма, это означает, что если вы нанимаете преданных разработчиков на стороне клиента (как это делает моя компания), то все это бесполезно.
Джигги
4

Я отвечаю на этот старый вопрос, потому что другие ответы, кажется, избегают конкретной упомянутой проблемы.

Формы Django позволяют легко написать небольшой код и создать форму со стандартными настройками. Любая настройка очень быстро приводит к «большему количеству кода» и «большему количеству работы» и несколько сводит на нет основное преимущество системы форм.

Библиотеки шаблонов, такие как django-widget-tweaks значительно упрощают настройку форм. Надеемся, что подобная настройка на месте, в конце концов, будет легкой с простой установкой 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

(Я использовал курсив для обозначения концепций MVC, чтобы сделать это более разборчивым.)

Нет, на мой взгляд, они не ломают MVC. Работая с Django Models / Forms, думайте об этом как об использовании всего стека MVC в качестве Модели :

  1. django.db.models.Modelявляется базовой моделью (содержит данные и бизнес-логику).
  2. django.forms.ModelFormобеспечивает контроллер для взаимодействия с django.db.models.Model.
  3. django.forms.Form(как обеспечивается через наследование django.forms.ModelForm) - это представление, с которым вы взаимодействуете.
    • Это делает вещи размыто, так как ModelFormэто Form, так что эти два слоя тесно связаны между собой . По моему мнению, это было сделано для краткости в нашем коде и для повторного использования кода в коде разработчиков Django.

Таким образом django.forms.ModelForm(со своими данными и бизнес-логикой) становится сама Модель . Вы можете ссылаться на него как (MVC) VC, который является довольно распространенной реализацией в ООП.

Взять, к примеру, django.db.models.Modelкласс Джанго . Когда мы смотрим на django.db.models.Modelобъекты, мы видим Model, хотя это уже полная реализация MVC. Предполагая, что MySQL является внутренней базой данных:

  • MySQLdbявляется модель (слой хранения данных и бизнес - логики относительно того, как взаимодействовать с / проверки достоверности данных).
  • 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. Помимо философии, это просто практическая цель, которой я хочу достичь, потому что она более эффективна и обеспечивает лучшее разделение труда.
Джигги
1
Это все еще вполне возможно. Вы можете вручную написать свои поля в шаблонах, вручную написать подтверждение в ваших представлениях, а затем вручную обновить ваши модели. Но дизайн форм Джанго не нарушает MVC.
Джек М.