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>
asked Jul 7, 2011 at 23:40
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. Commented 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. Commented 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. Commented 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. Commented 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. Commented 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.

answered Jul 9, 2011 at 9:38
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? Commented 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.

answered Jul 8, 2011 at 1:28
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. Commented Jul 8, 2011 at 1:52
  • Shortest code wins. Commented 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) Commented 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. Commented 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. Commented 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>
answered May 23, 2012 at 22:59
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.
answered Jul 8, 2011 at 16:28
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. Commented 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. Commented 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. Commented Jun 9, 2022 at 9:58

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.