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>
5
  • "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 8, 2011 at 3: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 8, 2011 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 8, 2011 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 8, 2011 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 8, 2011 at 14:50

4 Answers 4

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.

1
  • 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, 2016 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.

6
  • 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 8, 2011 at 1:52
  • Shortest code wins. Jul 8, 2011 at 4: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 8, 2011 at 4:41
  • Just a slight note that, yes, MVC didn't really make sense in web apps ... until AJAX put it right back in again. Jul 8, 2011 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 8, 2011 at 14:32
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>

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.
3
  • 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 8, 2011 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 8, 2011 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. Jun 9, 2022 at 9:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.