• 12 hours
  • Medium

Free online content available in this course.

course.header.alt.is_video

course.header.alt.is_certifying

Got it!

Last updated on 7/20/22

Create Model Objects With a ModelForm

Add a URL Pattern, View, and Template for Creating a Band

Time to create our bands! We'll represent them using model objects.

If we’re going to have a new form, we need somewhere to put it. Let’s think about what the URL path to our new form could be.

So far our  Band  related paths are:

  • ‘bands/’ for the list of bands.

  • ‘bands/1/’ for band 1, ‘bands/2/’ for band 2, etc. 

We know that in the rest of this part, we’ll be adding views for creating, updating, and deleting bands. It would be nice if we could somehow fit them all under the ‘bands/’ path.

To create a  Band  , how about we use the path:  ’bands/add/’? Let’s add it underneath the rest of our band-related URL patterns: 

# merchex/urls.py

urlpatterns = [
  ...
  path('bands/', views.band_list, name='band-list'),
  path('bands/<int:id>/', views.band_detail, name='band-detail'),
  path('bands/add/', views.band_create, name='band-create'),
...

You know by now that giving our URL Patterns a  name  is useful for generating links in templates, so include  name=’band-create’  .

 Why ‘bands/add/’? Why not ‘bands/create/’? 

The truth is, you can use any verb you like, as long as it makes sense to your users! I'm using a different verb partly to demonstrate that you’re free to build your URLs however is appropriate for your application.

I’ll let you go ahead and create the  band_create  view, and the  band_create.html  template, on your own. The template can just contain a simple  <h1>  for now. We’ll add the form in the next section.

Let Your Model Define the Form

Now that you know how to create forms in Django, the next step is to create a form that will allow users to create a new  Band  object. 

Now, you might think that we would start by defining another form class, and for each field in our model, defining a corresponding form field, like this:

class BandForm(forms.Form):
   name = forms.CharField(max_length=100)
   biography = forms.CharField(max_length=1000)
   year_formed = forms.IntegerField(min_value=1900, max_value=2021)
   official_homepage = forms.URLField(required=False)

You could do that! But compare it to the model itself (trimmed down for comparison):

class Band(models.Model):
…
   name = models.fields.CharField(max_length=100)
...
   biography = models.fields.CharField(max_length=1000)
   year_formed = models.fields.IntegerField(
    validators=[MinValueValidator(1900),
           MaxValueValidator(2021)]
)
…
official_homepage = models.fields.URLField(null=True, blank=True)

Even though we are building two separate things - a model and a form - there seems to be a lot of duplicated information between the two. We’ve already defined each field in the model, specifying the data types (string, integer), and the rules (e.g.,  max_length  ). Why should we have to define them in the form too? 

Wouldn’t it be great if we could let the model define the form? That’s exactly what  ModelForm s are for.

Generate a Form From a Model Automatically With a ModelForm

In listings/forms.py, we’ll define a new class that inherits from  django.forms.ModelForm  :

from django import forms

from listings.models import Band
...

class BandForm(forms.ModelForm):
   class Meta:
     model = Band
     fields = '__all__'

The new class contains a nested class,  Meta  , which specifies the model that this form will be for, and which fields from that model to include in this form (in this case, all of them). 

Now let’s use our  BandForm  in the  band_create  view, passing it to the template: 

# listings/views.py

from listings.forms import BandForm, ContactUsForm
...

def band_create(request):
   form = BandForm()
   return render(request,
            'listings/band_create.html',
            {'form': form})

And then render the form in the template:

# listings/templates/listings/band_create.html

{% extends 'listings/base.html' %}

{% block content %}

 <h1>Create new Band</h1>

<form action="" method="post" novalidate>
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Submit">
</form>

{% endblock %}

 Take a look at our new page in the browser at http://127.0.0.1:8000/bands/add/:

The page is titled
Our "Create new Band" page.

We just generated a form for our model without defining any form fields at all - we let the model do that for us! And here’s the best part: if we change the fields on the model, the form is automatically updated.

But we’re not done yet - we need to update our view to create a  Band  object from the values we submit in the form.

Update the View Logic to Create a New Object

We’re going to follow the same pattern that we did for the “Contact Us” form. Let’s recap on the lifecycle of this view pattern in “pseudocode” style:

  • The request is passed to the view function.

    • If this is a GET request:

      • This must be the first request to this page, and the user will expect to see a blank form ready for filling out. So, we create a new empty instance of the form and store it in a  form  variable.

    • If this is a POST request:

      • This is not the first request but must be a subsequent request to this page, together with some form values that the user will have submitted. So we create an instance of the form and populate it with the values sent in the POST request. This is stored in a  form  variable.

      • If the form validation passes successfully:

        • We perform the action - whether that’s sending an email, creating an object, or any other action.

        • We redirect the user to another view, perhaps a confirmation page, or some other page that indicates that the action was successful. We stop executing this view function at this point. However:

      • If the form validation did NOT pass successfully:

        • The validation process has now added error messages to the relevant fields.

        • We allow execution of this view to continue to the next step below. 

  • We now have a  form  that is either a) new and blank or b) populated with previously submitted values and relevant error messages. In both cases, we pass  form  to the template so it can be displayed in the browser. 

Here’s how that looks in Python, in our  band_create  view. Note that we handle the  ’POST’  case first in the  if  statement and then handle  ’GET’  in the  else  statement: 

# listings/views.py

def band_create(request):
    if request.method == 'POST':
        form = BandForm(request.POST)
        if form.is_valid():
            # create a new `Band` and save it to the db
            band = form.save()
            # redirect to the detail page of the band we just created
            # we can provide the url pattern arguments as arguments to redirect function
            return redirect('band-detail', band.id))

    else:
        form = BandForm()

    return render(request,
                'listings/band_create.html',
                {'form': form})

Note that the  form.save()  method not only saves the new object to the database - it also returns that object, which means we can use it in the next step, where we immediately redirect to the newly created band. 

Now we can fill out the form:

The fields on the form are filled in.
Our "Create new Band" form is ready.

Then we are redirected to the detail page of the band we just created:

The page is titled
The detail page for " A Tribe Called Quest."

Our new page works, but it’s no use unless our users can actually find it. We need to link to it from somewhere.

How about linking from the  Band  list view? 

# listings/templates/listings/band_list.html

...
   <h1>Bands</h1>

   <a href="{% url 'band-create' %}">Create new Band</a>
...

ModelForms are a powerful tool for creating models quickly and easily. If you need to check over any of the steps in the previous chapter, watch the screencast.

Exclude Fields From a ModelForm

It’s great that Django can generate a form for us, but what if the default form doesn’t quite meet our needs?

Well, you could build a form from scratch, like we did with the  ContactUsForm  , and use that. But it’s also possible to customize a ModelForm. 

Let’s say we want users to be able to create a new band, but we don’t want them to be able to edit the  active  and  official_homepage  fields - we only want administrators to be able to edit those fields (which they can do in the admin site). We can  exclude  them from the form, like this:

class BandForm(forms.ModelForm):
   class Meta:
      model = Band
      # fields = '__all__'  # delete this line
      exclude = ('active', 'official_homepage')  # add this line

And then those fields will not appear in the form:

The page contains 4 fields: Name, Genre, Biography, and Year formed.
The active and official_homepage fields excluded.

The form will still work, and we’d be able to create a new  Band  object without any errors, even though we’ve left these fields out - because  active  has a default of  True  , and  official_homepage  allows NULL values with  null=True  . 

Enable Client-Side Validation

For both of the forms we created so far, we added  novalidate  to the form HTML: 

<form action="" method="post" novalidate>

 This disabled client-side validation in your browser. Let’s re-enable it by removing  novalidate  from the band_create template: 

# listings/templates/listings/band_create.html

<form action="" method="post">

Now try submitting that form empty:

Below the Name field appears the message
Client-side validation.

Client-side validation provides an improved user experience. It validates the data in the form before it gets sent to the server. If there is any invalid data, the form will display an error message and won’t submit the data until it is valid. This saves the user from making a “round-trip” to the server, only to find out they submitted invalid data. 

So why did we disable client-side validation in the first place?

Well, if the form won’t submit until all the data is valid, then we can’t see what happens when we send invalid data to the server. Therefore, it’s not possible to demonstrate that the server-side validation is working.

But do we even need server-side validation if we have client-side validation?

Yes, absolutely. And this is an important rule to learn about web development:

“Never trust the client.”

Why not? We wrote the HTML for the client-side - can’t we trust it? 

The problem is that any user could edit the HTML in the front end, remove the validation, and post whatever they want to the server! In fact, you can do it right now. Open the inspector in your browser (right-click and “Inspect” if you’re in Chrome). You can easily increase the  maxlength  attribute of this input to 10,000 or even 100,000!

In the page's HTML, the element reading maxlength=100000 is circled.
Maxlength="100000".

If we didn’t have server-side validation, a 100,000 character biography could be saved straight to the database. This probably wouldn’t crash the site, and would be more annoying than anything else. But you can imagine that a hacker might try to come up with other ways to exploit this lack of server-side validation.

Web application security is an important subject that every developer should learn about, but we can’t cover it all in this course. However, Django has many security best-practices built right into it, so you’re off to a good start just by using the framework. 

For now though, remember:

  • You rely on server-side validation to ensure that you only accept valid data into your database.

  • Client-side validation improves the user experience, and while useful, should not be solely relied upon! 

While we’re on the subject of security, now is a good time to explain what the  {% csrf_token %}  is in all of our forms. CSRF stands for cross-site request forgery, and it’s a method for tricking users into performing operations by making up (or “forging”) requests. The  csrf_token  prevents this from happening by generating a random token for every request. If a subsequent POST request does not include this exact token, Django knows that the request is a forgery and raises an error to halt execution. 

Do we ever create a  Form  manually for a model, or do we always use a  ModelForm  ? 

You should very rarely have to manually create a  Form  for a model from scratch, although nothing is stopping you from doing that. But then you would have to keep both the  Model  and the  Form  in sync with each other. Using a  ModelForm  in the first instance makes sense because you keep things DRY, and there is less code to maintain. 

Now You Try! Add a ‘Create’ Page for the Listing Model

Here’s your list of tasks for this chapter:

  • Add a “Create new Listing” page (URL pattern, view, and template).

  • Add a  ListingForm  and use it in the new page to create  Listing  objects.

  • Link from the listings list to the listing create page.

  • Re-enable client-side validation across all your forms.

Let’s Recap!

  • When building a form for creating new objects from a model, use a  ModelForm  - it lets the model define the form fields automatically.

  • Use a  ModelForm  in the same way as a  Form  : create an instance of the form in your view and pass it to the template. Then handle both GET and POST requests in the view.

  • Customize a  ModelForm  by excluding some of the model’s fields from it, displaying only a subset of fields in the form. 

Now that we’ve created a form that can create model objects, let’s see how to reuse that form for the next write operation, the ‘U’ in ‘CRUD’ - Update.

Example of certificate of achievement
Example of certificate of achievement