The video mentions copying code from the screen or transcript. This is a reference to the old, paid version of the course. I have now collated all the code together in one place in the source, which you can download here.
Building a More Complex Form
The contact form we built in the last module is a common, but very simple use of a website form. Another common use for forms is to collect information from the user and save that information in the database.
Examples include entering your personal information for a membership application, your name and address details for a sales order and filling out a survey.
Using forms for collecting and saving data from users is so common that Django’s developers created a special model form class to make creating forms from Django models much easier.
With model forms, you create a Django model and then create a form that inherits from Django’s ModelForm class. As always, the devil is in the detail, so in this module we’re going to build a new Django app from scratch and add a new model and model form to demonstrate how it all works.
Meandco, being a web design company, needs a way for new and existing customers to submit a request for quotation. Once the quote request has been submitted, it needs to be saved to the database so the staff at Meandco can review the customer’s requirements and get back to the customer with a quote.
Remember that Django best-practice says that each Django app should do one thing only. To achieve the result we want, we must first go through the process of creating a new Django app.
The process is the same as we followed to create the pages app:
- Create the new quotes app;
- Create the Quote model and add it to the database; and
- Add the Quote model to the Django admin. This time, we’re also going to tweak the admin user interface to make managing the quote data easier.
Once the new app is set up and running, we can then create the model form, view and template so website users can submit a request for quotation.
Create the Quotes App
As I mentioned in the introduction, a Django app should only do one thing whenever possible. Collecting and managing quote requests from our site users is a very different application to displaying site pages, so we need to create a new Django app for quotes. Make sure your virtual environment is running and then change into the mfdw_root directory. Once in the root directory run python manage.py startapp quotes.
If you executed this command correctly, you will now have a new app (quotes) at the same level as the pages app in your project directory.
To store uploaded files in our project, we also need to add an \uploads folder at the same level as your quotes app. Once you have added the folder, you project structure should look like this.
Once the quotes app is created, we need to register it with our Django project. Pause the video and add the quotes app configuration to your INSTALLED_APPS list in the settings file
That’s it for setting up our new quotes app. In the next lesson, we’re going to create the quote model.
Create the Quote Model
When Django created the quotes app for you, it also created a new models.py file for the app. Open this new file and add the Quote model code to the file. Pause the video and enter the code into your editor or copy from the transcript:
This is a much bigger model than our Page model, but the fundamentals are the same, so don’t be daunted.
Let’s step through some of the more important parts:
We’re accessing the user database to link a user to the quote, so in line 2 we import the User class from django.contrib.auth.models. More on this later.
Lines 4 to 7 is a tuple I’ve named STATUS_CHOICES. STATUS_CHOICES consists of two-element tuples (two- tuples) which will translate into a drop-down list of options on the model form in both the admin and on the website. This could also be entered as a list of tuples, however, best-practice is to use tuples as they are immutable (can’t be changed). The first element in the tuple is the value that will be saved to the database, the second element is the human-readable name.
In Lines 9 to 13 PRIORITY_CHOICES is the same as STATUS_CHOICES; it will be translated to a list of options in forms and in the admin.
On line 24 you can see that the sitestatus field has an additional choices attribute.
When you set the choices attribute, Django will replace the standard TextInput widget with the Select widget. The Select widget displays a drop-down list populated with the contents of the choices attribute, which in this case is STATUS_CHOICES.
On Line 25 the priority field also sets the choices attribute. In this case, the drop-down list in the Select widget is provided by PRIORITY_CHOICES.
On Line 26 we’re using a FileField for the first time in a model. With a FileField, we need to provide the upload_to attribute so Django knows where to put uploaded files.
On line 27 we set the auto_now_add attribute for the submitted field to True. This will automatically save the current date and time in the submitted field.
On line 28 Remember that in Django a date field can’t blank, so to allow the quotedate field to be empty, we also need to set the null attribute to True to allow Django to save a Null entry when quotedate is empty.
And finally on line 30 we’ve added a foreign key link to the User model. The function of this link will become apparent in a later lesson.
Empty values are not allowed for foreign keys, so we set the null attribute to True. The on_delete attribute is required—if you don’t set it, Django will throw an error. We set on_delete to CASCADE which will delete all related entries in other tables.
Now that we have created the model, let’s make sure everything has been entered correctly with the check management command. Make sure your virtual environment is running, then in the terminal enter python manage.py check.
If the model was entered correctly, you should see something like this in your terminal.
Next, we need to create and run the migrations. While still in the terminal enter python manage.py makemigrations to create the migrations for the new Quote model.
Then enter python manage.py migrate to migrate the new model to the database.
And that’s it for the Quote model. In the next lesson we’re going to add it to the admin so we can manage incoming quote requests.
Add Quote Model to the Django Admin
To be able to manage incoming quotes, we need to add the Quote model to the admin. This is straight forward; let’s start with registering a simple admin class like we did in Module 5. Pause the video and enter the code into your editor or copy from the transcript.
I won’t go over this because it should be familiar by now. The purpose of this code is to make sure the admin is working OK with your model. Fire up the development server and navigate to http://127.0.0.1:8000/admin/ and select Add to add a new quote.
If all has worked to plan, you should see a blank form with your quote fields ready to fill out. The problem here is apparent—the form is huge! This form would be cumbersome to work with every day.
So how do we make this form more manageable? Let’s build upon what we learned in Module 5 and improve the management interface for the Quote model. Pause the video and enter the code into your editor or copy from the transcript.
Refresh the admin after these changes, and click the Add quote link. The form should now look like this.
As you can see in the video, each accordion has show and hide links that allow you to open and close the accordion.
We’ve added a few significant elements to the QuoteAdmin class, so we will work through them with some examples and a few screenshots to illustrate what’s going on.
Before we go on, enter a couple of test quotes—you only need to fill out the fields where the field name is in bold. Don’t forget to expand the Contact Information and Job
Information group to enter additional quote data. Don’t worry about the Quote Admin group at this stage. You can pause the video while you add the quotes.
Once you’ve added a couple of quotes, the quote listings should look like this.
Now we have some quotes in the database, let’s check out what additional formatting and model management tweaks our QuoteAdmin class has provided.
First, the quote listing. You can see that the list_display option governs what columns are displayed in the list.
And the list filter option tells the Django admin what fields to provide filters for.
Filtering becomes useful when the number of records in your database gets larger. For example, you could filter the quote list to show only the quotes that have been submitted this month.
The collapsible grouping of fields in the admin form is configured with the fieldsets option in our QuoteAdmin class. The fieldsets option controls the layout of the add and edit pages in the admin.
Fieldsets is a set of two-tuples one for each group of form fields on the form. The order of the two-tuples governs the order in which the field groups are displayed in each section on the admin page.
Each element of the tuple consists of a string name and a dictionary of field options.
Name is the title of the group. If name is set to None, then no group title will be shown.
The field options dictionary can include options for what fields to show in the group, additional CSS classes to apply to the group and a group description to show in forms.
Let’s see how that works in practice. Here is our first fieldset:
This fieldset shows name, email and description as the first three fields on the admin form, with no section title.
The following three fieldsets group the remaining fields into three sections:
- Contact Information;
- Job Information; and
- Quote Admin.
One last thing before we move on—when you open the the Job Information group you’ll notice the Submitted field isn’t editable.
When we set auto_add_now to True in the model, we turned the submitted field into a time-stamped field which means it can’t be edited in the admin.
If we tried to show it in the form, we would get an error. So we set the readonly_fields option on the admin model to include the submitted field so it can be shown in the form as read-only.
Now that our quotes app is up and running and the model admin sorted, in the remaining lessons in this module, we’re going to create the model form and associated view and template that will allow us to show the form on the website and collect request for quote submissions from Meandco customers.
Add Quote Form to Frontend
Now that we have the quote model and the model admin sorted, we need create all the elements that will allow users to submit quotes on the website.
There are four things we need to do:
- Create the quote form
- Add a view to manage the form
- Add a template for displaying the form
- And create the URL configuration files so we can link to the form from our site pages.
In this lesson we’re going to create the form and the view. In the next lesson we’ll and a template and create the URL configurations to show the form on our website
Create the Quote Form
This is where the power of Django’s ModelForm class really shines— creating a form for the model is an almost trivial task. Create a new forms.py file in your quotes app and enter the following code. Pause the video and enter the code into your editor or copy from the transcript.
That’s it—a few lines of code is all Django needs to create a form for your model that will show all the necessary HTML on the page, validate your form fields and pass form data to your view. There are some things to note, so let’s look at those now:
In line 2 we import the ModelForm class which will do all the heavy lifting for us.
In line 5 you can see our QuoteForm class inherits from ModelForm.
In line 6 we’ve added a handy ModelForm class option that adds a CSS class to our required fields. We will use this class to add an asterisk (*) to the required fields in the form template.
And on line 7 we’ve added an internal class Meta to the ModelForm class. This extra metadata provides information to the ModelForm class about the model on which to base our form and the model fields to render on the form.
Add the Quote View
The quote view also builds on what we have learned previously in the course. We are going to call the new view quote_req, so let’s go ahead and add the view code to the views.py file in our quotes app. Pause the video and enter the code into your editor or copy from the transcript:
This view is functionally identical to the view for our contact form, except we have removed the code for emailing the form data, and replaced it with the form.save() method to save the form data to our database.
One other important change to note—in line 11 we’ve added request.FILES to the arguments passed to the QuoteForm class. This is so we can retrieve file upload information from the response.
Create the Quote Form Template
Now it’s time to create the template for our form. We will inherit from the site’s base template, so the form will be very similar to the contact form template. There’s one major difference—the HTML form must be a multi-part form so that we are able to upload a file with the form.
First, create a new templates folder in your quotes app. Then add another folder inside that called \quotes. Your folder structure should look like this.
Now we need to create the quote template. Let’s call it quote.html. Pause the video and enter the code into your editor or copy from the transcript:
You should see immediately the similarity between this template and others we’ve created in earlier modules. The title and sidenav blocks are identical to the page template and the form code is almost the same as the contact form.
The only change from what you have seen before is line 20 where we set the encoding type of the form to multipart/form-data to handle the file upload.
While we are working on the form template, we need to add a little tweak to the main.css file so that our required field labels will have an asterisk appended to the label. Pause the video and enter the code into your editor or copy from the transcript:
Link the Quote Form
Now we have got the model, the admin and the form sorted, there is one last thing to do—add URL configurations and site links to our site so the quote form is accessible to users.
First, we need to add a new URL configuration to our site urls.py. Pause the video and enter the code into your editor or copy from the transcript:
Next, we create a urls.py file for our quotes app. This is a new file, so you need to create the file before adding the code. Pause the video and enter the code into your editor or copy from the transcript:
And finally, we add a link to the quote form in the base template. Pause the video and enter the code into your editor or copy from the transcript:
Phew! That was a lot to get through, but now if you navigate to http://127.0.0.1:8000/quote/ you should see a page just like this.
You will notice that a new menu item has also been added to the left menu and that you can navigate easily to the other site pages. Enter a few quotes now as we will be using them in the next module. Don’t forget to attach some files to test the file upload capability of your form.