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.

Et si vous obteniez un diplƓme OpenClassrooms ?
  • Formations jusqu’à 100 % financĆ©es
  • Date de dĆ©but flexible
  • Projets professionnalisants
  • Mentorat individuel
Trouvez la formation et le financement faits pour vous