18

I just started working with Django coming from years of Spring MVC and the forms implementation strikes as being slightly crazy. If you're not familiar, Django forms starts with a form model class that defines your fields. Spring similarly starts with a form-backing object. But where Spring provides a taglib for binding form elements to the backing object within your JSP, Django has form widgets tied directly to the model. There are default widgets where you can add style attributes to your fields to apply CSS or define completely custom widgets as new classes. It all goes in your python code. That seems nuts to me. First, you are putting information about your view directly in your model and secondly you are binding your model to a specific view. Am I missing something?

EDIT: Some example code as requested.

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
  • 1,590
  • 11
  • 15
  • "information about your view directly in your model"? Please be specific. "binding your model to a specific view"? Please be specific. Please provide concrete, specific examples. A general, hand-waving complaint like this is difficult to understand, much less respond to. – S.Lott Jul 08 '11 at 03:08
  • I haven't built anything yet, just reading the docs. You bind an HTML widget along with CSS classes to your Form class directly in Python code. That's what I'm calling out. – jiggy Jul 08 '11 at 14:26
  • where else do you want to do this binding? Please provide an example or a link or a quote to the **specific** thing you're objecting to. The hypothetical argument is hard to follow. – S.Lott Jul 08 '11 at 14:26
  • I did. Look at how Spring MVC does it. You inject the form-backing object (like a Django Form class) into your view. Then you write your HTML using taglibs so you can design your HTML as normal and just add a path attribute that will bind it to properties of your form-backing object. – jiggy Jul 08 '11 at 14:37
  • Please **update** the question to make it perfectly clear what you're objecting to. The question is hard to follow. It has no example code to make your point **perfectly** clear. – S.Lott Jul 08 '11 at 14:50

4 Answers4

23

Yes, the Django forms is a mess from the MVC perspective, suppose you are working in a big MMO super-hero game and you are creating the Hero model:

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

Now you are asked to create a form for it, so that the MMO players can input their hero super powers:

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

Since the Shark Repellent is a very powerful weapon, your boss asked you to limit it. If a hero has the Shark Repellent then he cannot fly. What most people do is simply add this business rule in the form clean and call it a day:

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

This pattern looks cool and might work on small projects, but in my experience this is very hard to maintain in large projects with multiple developers. The problem is that the form is part of the view of the MVC. So you will have to remember that business rule every time you:

  • Write another form that deals with the Hero model.
  • Write a script that import heroes from another game.
  • Manually change the model instance during the game mechanics.
  • etc.

My point here is that the forms.py is all about the form layout and presentation, you should never add business logic in that file unless you enjoy messing with spaghetti code.

The best way to handle the hero problem is to use model clean method plus a custom signal. The model clean works like the form clean but its stored in the model itself, whenever the HeroForm is cleaned it automatically calls the Hero clean method. This is a good practice because if another developer writes a another form for the Hero he will get the repellent/fly validation for free.

The problem with the clean is that it's called only when a Model is modified by a form. It's not called when you manually save() it and you can end-up with a invalid hero in your database. To counter this problem, you can add this listener to your project:

from django.db.models.signals import pre_save

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

This will call the clean method on each save() call for all your models.

Cesar Canassa
  • 1,390
  • 11
  • 13
  • 2
    This is a very helpful answer. However, much of my forms and corresponding validation involves several fields on several models. I think that is a very important scenario to consider. How would you perform such validation on one of the model's clean methods? – Bobort Dec 16 '16 at 15:08
8

You're mixing the whole stack, there are several layers involved:

  • a Django Model defines the data structure.

  • a Django Form is a shortcut to define HTML forms, field validations and Python/HTML value translations. It's not strictly needed, but often handy.

  • a Django ModelForm is another shortcut, in short a Form subclass that gets its fields from a Model definition. Just a handy way for the common case where a form is used to enter data into the database.

and finally:

  • Django architects don't adhere exactly to MVC structure. Sometimes they call it MTV (Model Template View); because there's no such thing as a controller, and the split between template (just presentation, no logic) and View (just user-facing logic, no HTML) is just as important as the isolation of the Model.

Some people see that as heresy; but it's important to remember that MVC was originally defined for GUI applications, and it's a rather awkward fit for web apps.

Javier
  • 9,888
  • 1
  • 26
  • 35
  • But widgets are presentation and they are wired straight into your Form. Sure, I can not use them, but then you lose the benefits of binding and validation. My point is that Spring gives you binding and validation and complete separation of model and view. I would think Django could have easily implemented something similar. And I look at url configuration as a sort of built in front controller which is a pretty popular pattern for Spring MVC. – jiggy Jul 08 '11 at 01:52
  • Shortest code wins. – kevin cline Jul 08 '11 at 04:19
  • 1
    @jiggy: forms are part of the presentation, the binding and validation is only for user-entered data. the models have their own binding and validation, separate and independent from the forms. the modelform is just a shortcut for when they're 1:1 (or nearly) – Javier Jul 08 '11 at 04:41
  • Just a slight note that, yes, MVC didn't really make sense in web apps ... until AJAX put it right back in again. – AlexanderJohannesen Jul 08 '11 at 11:17
  • Form display is view. Form validation is controller. Form data is model. IMO, at least. Django munges them all together. Pedantry aside, it means that if you employ dedicated client-side developers (as my company does) this whole thing is kinda useless. – jiggy Jul 08 '11 at 14:32
  • It's still an awkward fit even with Ajax. On the front end, you have a different set of concerns being separated and there's still that HTTP wall to chuck signals over. The principles are worth nothing but there are lots of ways to distribute concerns effectively that don't necessarily follow MVC to the letter. – Erik Reppen May 23 '12 at 23:46
4

I'm answering this old question because the other answers seem to avoid the specific issue mentioned.

Django forms allow you to easily write little code and create a form with sane defaults. Any amount of customization very quickly leads to "more code" and "more work" and somewhat nullifies the primary benefit of the form system

Template libraries like django-widget-tweaks make customizing forms much easier. Hopefully field customizations like this will eventually be easy with a vanilla Django installation.

Your example with 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
  • 141
  • 3
1

(I've used Italics to signify the MVC concepts to make this more legible.)

No, in my opinion, they do not break MVC. When working with Django Models / Forms, think of it as using an entire MVC stack as a Model:

  1. django.db.models.Model is the base Model (holds the data and the business logic).
  2. django.forms.ModelForm provides a Controller for interacting with django.db.models.Model.
  3. django.forms.Form (as provided through inheritance by django.forms.ModelForm) is the View you interact with.
    • This does make things blurry, since the ModelForm is a Form, so the two layers are tightly coupled. In my opinion, this was done for brevity in our code, and for code re-use within the Django developers' code.

In this way, django.forms.ModelForm (with its data and business logic) becomes a Model itself. You could reference it as (MVC)VC, which is a pretty common implementation in OOP.

Take, for example, Django's django.db.models.Model class. When we look at the django.db.models.Model objects, we see Model even though it is already a full implementation of MVC. Assuming MySQL is the back end database:

  • MySQLdb is the Model (data storage layer, and business logic regarding how to interact with/validate the data).
  • django.db.models.query is the Controller (handles input from the View and translates it for the Model).
  • django.db.models.Model is the View (what the user interacts with).
    • In this case, the developers (you and I) are the 'user'.

This interaction is the same as your "client-side developers" when working with yourproject.forms.YourForm (inheriting from django.forms.ModelForm) objects:

  • As we need to know how to interact with django.db.models.Model, they would need to know how to interact with yourproject.forms.YourForm (their Model).
  • As we do not need to know about MySQLdb, your "client-side developers" do not need to know anything about yourproject.models.YourModel.
  • In both cases, we very seldom need to worry about how the Controller is actually implemented.
Jack M.
  • 492
  • 2
  • 7
  • 1
    Rather than debate semantics, I just want to keep my HTML and CSS in my templates and not have to put any of it in .py files. Philosophy aside, that's a just a practical end that I want to achieve because it's more efficient and allows for better division of labor. – jiggy Jul 08 '11 at 20:17
  • 1
    That is still perfectly possible. You can manually write your fields in the templates, manually write your validation in your views, and then manually update your models. But the design of Django's Forms does not break MVC. – Jack M. Jul 08 '11 at 20:21
  • Except you can have Multiple forms for the same models. Now the controller is fragmented, and not unified. Django is more of a MTV (Model Template View) pattern. – run_the_race Jun 09 '22 at 09:58