In Part 3, we learnt about apps, and set up our external database on ElephantSQL. In this tutorial, we will learn how to use databases with Django to create our Web Forum app.
In the last tutorial, we started our web app. You can use the same repl for this tutorial.
Setting up Django Admin
The first thing we need to do is to set up a Django admin account. This is an account that we can use to access the database on our website. Start the repl, and instead of just hitting enter, type:
> python manage.py createsuperuser
You will be asked to enter a username and password - make sure that they are ones that you can remember! You do not have to enter your email if you don't want to. Note: when you enter your password, no text will appear. This is for privacy reasons - typing works normally, it is just not displayed.
Now that you have created your admin account, you can run the server and open it in a new tab. To access your admin page, you need to add /admin to the end of your URL - so your URL should be https://repl-name--username.repl.co/admin Enter your login details that you just created, and you should be presented with the Django admin page:
Now we are ready to start creating our database.
What is a database?
Before we create our database, let's try to understand what a database actually is. You can visualise a database like a Excel spreadsheet Google sheet.
A database is similar, but many features are re-named:
Column (e.g. First Name, Has A Pet?) becomes Attribute
Row (e.g. row 4) becomes Record
In reality, the data is not stored like this, but this is a good way of thinking of it.
Another key difference with a database is that each attribute in a database (column) has to be the same data type. Examples of data types are: text, datetime, number or boolean. Notice that these are not quite the same as data types in programming - e.g. a "string" is "text" in a database.
In nearly all databases, a primary key is used. This is a unique identifier, i.e. an attribute that has a different value for every single record in the database. It is most often a number. Django adds a primary key by default, so we don't need to worry about including one when designing our entity.
Creating our first Entity
Let's create an entity to store information about each Post that is made to the web forum. Before creating your entity, it is good to make a plan about what attributes the entity should have. For this entity, some good attributes could be:
title - Title of post. Limited to 50 characters.
text - Content of the post. Not limited.
author - Creator of the post. Limited to 30 characters.
date - Date that post was created.
Django creates and modifies entities using models. To see this in action, navigate to Posts/models.py and add the following code:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=50)
text = models.TextField()
author = models.CharField(max_length=30)
date = models.DateTimeField(auto_now_add=True)
What does this code do?
On Line 1, we import models. This is a very handy module in Django that allows us to interface with databases very easily.
On Line 3 we define our Post class. This is our database entity. It inherits from models.Model, which means that all the code from models.Model, which interfaces with the database, applies to our entity.
On Lines 4-7 we declare the attributes that our entity is going to have
title is a CharField, or character field - this just means that it has letters in it. We have set the maximum size to be 50 characters.
text is a TextField. This is similar to a CharField, but has no limit to the amount of text it can hold.
author is another CharField, this time with a maximum length of 30 characters.
date is a DateTimeField - not surprisingly, for storing date and time. The auto_now_add=True is a useful Django function which means that the date and time will be set automatically to whenever the Post was created.
Now we have created our model, we need to apply it to the database. This is called migrating. Run the repl, but instead of just hitting enter to start the server, type the following line:
> python manage.py makemigrations
This tells Django to look for changes to the database model. Now you need to apply those changes, by typing:
> python manage.py migrate
This creates our entity in the database! The last thing we need to do, in order to see the database on our admin page, is change the file Posts/admin.py. Add the following:
from django.contrib import admin
from .models import Post
admin.site.register(Post)
This code imports the admin module from Django, and tells Django to add our entity (Post) to the admin page. If you go to your admin page now, you can see that our entity has been added!
Click on Add next to Post and create a record for the database.
If you save the record, then you can see that the record has been added to the database (The post "Post object (1)" was added successfully.).
Database Web Pages
It's all very well being able to change the database when you're the administrator, but how do we get the database to interact with a web page? The answer is that we use some of Django's pre-defined views. Our current home page is not very interesting - let's change it so that it lists all of the posts in the database.
View
Go to Posts/views.py and delete all the code from it. Now we can start coding our view.
from django.views.generic import ListView
from .models import Post
class HomePageView(ListView):
model = Post
template_name='home.html'
Line 1 of our code imports ListView, which is a Django view specifically for listing records in an entity.
On Line 2 we import our Post entity from models.py. This is the entity that our page will be based off of.
On Line 4, we create our view, HomePageView. Our view inherits from ListView, which means that we can use our view to list all of the records.
On Line 5 we define which entity we are using - the Post model. This is just the entity that we created in models.py and imported on Line 2
On Line 6, we state which template we will be using - home.html
Template
We will also need to update our template in order to display all of our records. Go to templates/home.html and delete all the html from it. Now we can start writing our template.
{% extends 'base.html' %}
{% block title %}Home{% endblock title %}
{% block content %}
{% for post in object_list %}
<div class="post">
<h3>{{ post.title }}</h3>
<p>{{ post.author }}</p>
</div>
<p>----------------------</p>
{% endfor %}
{% endblock content %}
The first 4 lines are standard html along with Django's template language, which we have covered before. On Line 5, we have our first new piece of code.
{% for post in object_list %}
This is a new part of Django's template language. When we use ListView, it passes in to the html document a list of all the records in the entity. This list can be referenced with object_list. So here, we are saying "for each post in object_list, do the following". On Line 6, we use a div element, with a post class. This is not necessary, but will be useful when styling the page with CSS later. On Lines 7-8 we put the title of the post, and the author, on to the screen. On Line 9, we close the div, then on Line 10 we add a divider to separate the items on the page. On Line 11 we end the for loop, so only the html between Lines 5-11 are repeated for each record
URL
We already created the URL for the home page, so we can skip this step.
It worked! Go to the admin page, and add another post. Now if you go back to the homepage, you should see something like this:
Congratulations! You've made your first database-driven web page!
Individual Pages
Now we are going to make individual pages for each post - so each post has its own page that people can visit. To do this, we will use another Django class, DetailView.
View
Add the following to Posts/views.py:
from django.views.generic import ListView, DetailView # new
from .models import Post
# stuff
class PostPageView(DetailView):
model = Post
template_name = 'post_page.html'
We create a view that inherits from DetailView, and set the model to our Post entity and the template to post_page.html (that we haven't created yet).
Template
Create a new file at templates/post_page.html, and add the following html:
This sets up a basic html template for the post. It can be confusing as to why post is used - the reason is that it is the lowercase name of our entity. You may see it also written as object, e.g.
<h1>{{ object.title }}</h1>
The rest of the html is fairly straightforward - we are just putting in all the data that we defined in our entity model in Posts/models.py.
URL
To configure the URL, we are going to use the primary key of the database (remember, the primary key is a unique number given to every record of the database). Django automatically adds a primary key to each entity, and its keys are always incremented - the first record added to the database has a primary key of 1, the second has a primary key of 2, and so on.
Go to Posts/urls.py, and add the following code:
from django.urls import path
from .views import HomePageView, PostPageView # new
urlpatterns = [
path('post/<int:pk>/',PostPageView.as_view(), name='post'), # new
path('', HomePageView.as_view(), name='home'),
]
This should all be familiar - the only new thing here is on Line 5. The <int:pk> tells Django to use the primary key of the number after /post/ in the URL. int just tells Django that the key will be an integer. If you run the server now, and go to https://repl-name--yourname.repl.co/post/1, you should get a page displaying the information about the first post you created!
Linking to the Post
However, typing in the URL all the time is not very user-friendly, so let's go back to the home page and add a link to each post.
We just need to edit the template for this, so navigate to templates/home.html and change the html to:
{% extends 'base.html' %}
{% block title %}Home{% endblock title %}
{% block content %}
{% for post in object_list %}
<div class="post">
<h3><a href="{% url 'post' post.pk %}">{{ post.title }}</a></h3>
<p>{{ post.author }}</p>
</div>
<p>----------------------</p>
{% endfor %}
{% endblock content %}
We've met the url function before - it takes a name and returns the URL. Here, the name takes an argument - the primary key of the post - so we just put that afterwards, and Django takes care of everything.
If you go to your home page now, you can click on the titles to view the post!
Creating and Editing Posts
Now, what about creating and editing posts? Django makes this too very quick and easy.
First, let's create a page where you can make a new post.
View
Go to Posts/views.py, and add the following code:
# stuff
from django.views.generic.edit import CreateView
# stuff
class PostCreateView(CreateView):
model = Post
template_name = 'new_post.html'
fields = ['title','author','text']
First, we import CreateView, which is yet another pre-built class that Django supplies us with. This one is made especially for creating records for entities.
Then we declare our PostCreateView, inheriting from CreateView. We set the entity model to Post and the template to new_post.html (which we haven't created yet). Then we set our fields. This is the data that we want the user to create. For this, the date will be created automatically, so we just need 'title', 'author' and of course 'text'.
Template
Create a new file at templates/new_post.html, and add the following html:
{% extends 'base.html' %}
{% block title %}Create a new post{% endblock title %}
{% block content %}
<h2>Post a Question to the Forum!</h2>
<form action="" method="POST">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save">
</form>
{% endblock content %}
Here, the most interesting bit is from Lines 8-11.
On Line 8 we create a htmlform element. This is an element that lets users input information. The action attribute is where to send the data when it is submitted - if we set it to "", Django will take care of it.
The method is how the data is submitted. There are two main methods, POST and GET. GET puts the information in the URL - it is often used for searching. For example, a google search gives you a URL such as https://www.google.com/search?q=django%20tutorial&safe=active&ssui=on. This is a GET because the data is in the URL. POST doesn't put the data in the URL, so it is more secure.
Then we have {% csrf_token %}. This is a Django function to stop people using Cross-Site Request Forgery to attack users on your website. Always have this on your forms!
On Line 9 we tell Django to put the form in as <p> elements.
On Line 10, we have a "submit" button. When the user clicks this, the data will be sent to Django
The last thing we need to do is specify what URL the form needs to go to once the form has been filled in. To do this, go to Posts/models.py and add the following:
from django.db import models
from django.urls import reverse # new
class Post(models.Model):
# stuff
def get_absolute_url(self):
return reverse('post',args=[str(self.id)])
First, we import the reverse function - this is similar to the url function that we use with the template language. When the form is submitted, Django automatically checks the get_absolute_url method, so we just need to define it in order for the redirect to happen. Here we are redirecting to the post page, giving the id (pk) of the record as an argument. This means that when the form is completed, it will redirect to the page with the post on it.
Next, we will make a view that allows website visitors to edit existing posts to the forum. To do this, we will use the UpdateView that Django gives us.
The process for creating new pages should be pretty familiar by now.
View
Add the following to Posts/views.py:
from django.views.generic.edit import CreateView, UpdateView
class PostUpdateView(UpdateView):
model = Post
template_name = 'edit_post.html'
fields = ['title','text']
This is pretty similar to the CreateView - the only difference is that we don't let the user change the author.
Template
Create a file at templates/edit_post.html and add the following html:
This will create an edit page at https://repl-name--yourname.repl.co/post/<pk>/edit To test this out, try just entering 1 instead of <pk> and you should be able to edit your first post!
Deleting Records
Finally, we will add a page that allows users to delete posts, using Django's DeleteView.
View
Add this to Posts/views.py:
from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
class PostDeleteView(DeleteView):
model = Post
template_name = 'delete_post.html'
success_url = reverse_lazy('home')
This is fairly standard. The success_url tells Django what page to redirect to after the post has been deleted. We are using reverse_lazy instead of reverse here - what is the difference? reverse runs straight away, which means it can run before the rest of the program is ready - before 'home' has been initialised. To fix this, reverse_lazy is used, which waits until everything has loaded before getting the URL.
Template
Create a file at templates/delete_post.html and add the following html:
{% extends 'base.html' %}
{% block title %}Delete Post{% endblock title %}
{% block content %}
<h1>Delete Post</h1>
<form action="" method="POST">{% csrf_token %}
<p>Are you sure you want to delete <strong>{{ post.title }}</strong></p>
<input type="submit" value="Confirm">
</form>
{% endblock content %}
This will just create a page with a Confirm button and a confirmation message. <strong> creates bold text.
That was quite a long tutorial, but we learnt how to use databases with Django! We went through it quite quickly, so you may want to read it more than once to gain a proper understanding of what is happening. You can post any questions below. In the next tutorial, we will look at user authentication, as well as making the site a bit more user-friendly.
As always, please upvote if you found this tutorial helpful, it supports me and lets me know that you want more! If you have any questions, post in the comments and I (or someone else) will answer them.
@oakwindicloud Thank you :) I posted this tutorial ages ago, and I never got round to the next part. I'll see if I can find the tutorial file and complete it.
Learning Web Development with Python and Django
Part 4
Part 1
Welcome!
In Part 3, we learnt about apps, and set up our external database on ElephantSQL. In this tutorial, we will learn how to use databases with Django to create our Web Forum app.
In the last tutorial, we started our web app. You can use the same repl for this tutorial.
Setting up Django Admin
The first thing we need to do is to set up a Django admin account. This is an account that we can use to access the database on our website.
Start the repl, and instead of just hitting
enter
, type:You will be asked to enter a username and password - make sure that they are ones that you can remember! You do not have to enter your email if you don't want to.
Note: when you enter your password, no text will appear. This is for privacy reasons - typing works normally, it is just not displayed.
Now that you have created your admin account, you can run the server and open it in a new tab.
To access your admin page, you need to add
/admin
to the end of your URL - so your URL should be https://repl-name--username.repl.co/adminEnter your login details that you just created, and you should be presented with the Django admin page:
Now we are ready to start creating our database.
What is a database?
Before we create our database, let's try to understand what a database actually is. You can visualise a database like a
Excel spreadsheetGoogle sheet.A database is similar, but many features are re-named:
In reality, the data is not stored like this, but this is a good way of thinking of it.
Another key difference with a database is that each attribute in a database (column) has to be the same data type. Examples of data types are: text, datetime, number or boolean. Notice that these are not quite the same as data types in programming - e.g. a "string" is "text" in a database.
In nearly all databases, a primary key is used. This is a unique identifier, i.e. an attribute that has a different value for every single record in the database. It is most often a number. Django adds a primary key by default, so we don't need to worry about including one when designing our entity.
Creating our first Entity
Let's create an entity to store information about each Post that is made to the web forum.
Before creating your entity, it is good to make a plan about what attributes the entity should have. For this entity, some good attributes could be:
Django creates and modifies entities using
models
. To see this in action, navigate toPosts/models.py
and add the following code:What does this code do?
Line 1
, we importmodels
. This is a very handy module in Django that allows us to interface with databases very easily.Line 3
we define ourPost
class. This is our database entity. It inherits frommodels.Model
, which means that all the code frommodels.Model
, which interfaces with the database, applies to our entity.Lines 4-7
we declare the attributes that our entity is going to havetitle
is aCharField
, or character field - this just means that it has letters in it. We have set the maximum size to be 50 characters.text
is aTextField
. This is similar to aCharField
, but has no limit to the amount of text it can hold.author
is anotherCharField
, this time with a maximum length of 30 characters.date
is aDateTimeField
- not surprisingly, for storing date and time. Theauto_now_add=True
is a useful Django function which means that the date and time will be set automatically to whenever thePost
was created.Now we have created our model, we need to apply it to the database. This is called migrating.
Run the repl, but instead of just hitting
enter
to start the server, type the following line:This tells Django to look for changes to the database model. Now you need to apply those changes, by typing:
This creates our entity in the database! The last thing we need to do, in order to see the database on our
admin
page, is change the filePosts/admin.py
. Add the following:This code imports the
admin
module from Django, and tells Django to add our entity (Post
) to the admin page.If you go to your admin page now, you can see that our entity has been added!
Click on
Add
next toPost
and create a record for the database.If you save the record, then you can see that the record has been added to the database (
The post "Post object (1)" was added successfully.
).Database Web Pages
It's all very well being able to change the database when you're the administrator, but how do we get the database to interact with a web page? The answer is that we use some of Django's pre-defined views. Our current home page is not very interesting - let's change it so that it lists all of the posts in the database.
View
Go to
Posts/views.py
and delete all the code from it. Now we can start coding our view.Line 1
of our code importsListView
, which is a Django view specifically for listing records in an entity.Line 2
we import ourPost
entity frommodels.py
. This is the entity that our page will be based off of.Line 4
, we create our view,HomePageView
. Our view inherits fromListView
, which means that we can use our view to list all of the records.Line 5
we define which entity we are using - thePost
model. This is just the entity that we created inmodels.py
and imported onLine 2
Line 6
, we state which template we will be using -home.html
Template
We will also need to update our template in order to display all of our records.
Go to
templates/home.html
and delete all thehtml
from it. Now we can start writing our template.The first 4 lines are standard
html
along with Django's template language, which we have covered before.On
Line 5
, we have our first new piece of code.This is a new part of Django's template language. When we use
ListView
, it passes in to thehtml
document a list of all the records in the entity. This list can be referenced withobject_list
. So here, we are saying "for each post in object_list, do the following".On
Line 6
, we use adiv
element, with apost
class. This is not necessary, but will be useful when styling the page withCSS
later.On
Lines 7-8
we put the title of the post, and the author, on to the screen.On
Line 9
, we close thediv
, then onLine 10
we add a divider to separate the items on the page.On
Line 11
we end thefor
loop, so only thehtml
betweenLines 5-11
are repeated for each recordURL
We already created the URL for the home page, so we can skip this step.
If you run the server now, and navigate to your home page (https://repl-name--yourname.repl.co), then you should see something like this:
It worked! Go to the admin page, and add another post. Now if you go back to the homepage, you should see something like this:
Congratulations! You've made your first database-driven web page!
Individual Pages
Now we are going to make individual pages for each post - so each post has its own page that people can visit. To do this, we will use another Django class,
DetailView
.View
Add the following to
Posts/views.py
:We create a view that inherits from
DetailView
, and set the model to ourPost
entity and the template topost_page.html
(that we haven't created yet).Template
Create a new file at
templates/post_page.html
, and add the followinghtml
:This sets up a basic html template for the post. It can be confusing as to why
post
is used - the reason is that it is the lowercase name of our entity. You may see it also written asobject
, e.g.The rest of the
html
is fairly straightforward - we are just putting in all the data that we defined in our entity model inPosts/models.py
.URL
To configure the URL, we are going to use the primary key of the database (remember, the primary key is a unique number given to every record of the database). Django automatically adds a primary key to each entity, and its keys are always incremented - the first record added to the database has a primary key of 1, the second has a primary key of 2, and so on.
Go to
Posts/urls.py
, and add the following code:This should all be familiar - the only new thing here is on
Line 5
. The<int:pk>
tells Django to use the primary key of the number after/post/
in the URL.int
just tells Django that the key will be an integer.If you run the server now, and go to https://repl-name--yourname.repl.co/post/1, you should get a page displaying the information about the first post you created!
Linking to the Post
However, typing in the URL all the time is not very user-friendly, so let's go back to the home page and add a link to each post.
We just need to edit the template for this, so navigate to
templates/home.html
and change thehtml
to:We've met the
url
function before - it takes aname
and returns the URL. Here, thename
takes an argument - the primary key of the post - so we just put that afterwards, and Django takes care of everything.If you go to your home page now, you can click on the titles to view the post!
Creating and Editing Posts
Now, what about creating and editing posts? Django makes this too very quick and easy.
First, let's create a page where you can make a new post.
View
Go to
Posts/views.py
, and add the following code:First, we import
CreateView
, which is yet another pre-built class that Django supplies us with. This one is made especially for creating records for entities.Then we declare our
PostCreateView
, inheriting fromCreateView
. We set the entity model toPost
and the template tonew_post.html
(which we haven't created yet). Then we set our fields. This is the data that we want the user to create. For this, the date will be created automatically, so we just need 'title', 'author' and of course 'text'.Template
Create a new file at
templates/new_post.html
, and add the followinghtml
:Here, the most interesting bit is from
Lines 8-11
.Line 8
we create ahtml
form element. This is an element that lets users input information. Theaction
attribute is where to send the data when it is submitted - if we set it to""
, Django will take care of it.method
is how the data is submitted. There are two main methods,POST
andGET
.GET
puts the information in the URL - it is often used for searching. For example, a google search gives you a URL such as https://www.google.com/search?q=django%20tutorial&safe=active&ssui=on. This is aGET
because the data is in the URL.POST
doesn't put the data in the URL, so it is more secure.{% csrf_token %}
. This is a Django function to stop people using Cross-Site Request Forgery to attack users on your website. Always have this on your forms!Line 9
we tell Django to put the form in as<p>
elements.Line 10
, we have a"submit"
button. When the user clicks this, the data will be sent to DjangoLine 11
, we end the form.URL
Go to
urls.py
and add the following:This is all standard.
The last thing we need to do is specify what URL the form needs to go to once the form has been filled in. To do this, go to
Posts/models.py
and add the following:First, we import the
reverse
function - this is similar to theurl
function that we use with the template language. When the form is submitted, Django automatically checks theget_absolute_url
method, so we just need to define it in order for the redirect to happen. Here we are redirecting to thepost
page, giving theid
(pk
) of the record as an argument. This means that when the form is completed, it will redirect to the page with the post on it.Now we can go to our https://repl-name--yourname.repl.co/create, fill in the form, and create a new post!
Editing
Next, we will make a view that allows website visitors to edit existing posts to the forum. To do this, we will use the
UpdateView
that Django gives us.The process for creating new pages should be pretty familiar by now.
View
Add the following to
Posts/views.py
:This is pretty similar to the
CreateView
- the only difference is that we don't let the user change the author.Template
Create a file at
templates/edit_post.html
and add the followinghtml
:This is almost identical to the
CreateView
.URL
Finally, add the following to
Posts/urls.py
:This will create an
edit
page at https://repl-name--yourname.repl.co/post/<pk>/editTo test this out, try just entering 1 instead of
<pk>
and you should be able to edit your first post!Deleting Records
Finally, we will add a page that allows users to delete posts, using Django's
DeleteView
.View
Add this to
Posts/views.py
:This is fairly standard. The
success_url
tells Django what page to redirect to after the post has been deleted. We are usingreverse_lazy
instead ofreverse
here - what is the difference?reverse
runs straight away, which means it can run before the rest of the program is ready - before 'home' has been initialised. To fix this,reverse_lazy
is used, which waits until everything has loaded before getting the URL.Template
Create a file at
templates/delete_post.html
and add the followinghtml
:This will just create a page with a
Confirm
button and a confirmation message.<strong>
creates bold text.URL
Finally, add this to
Posts/urls.py
:If you go to https://repl-name--yourname.repl.co/post/<pk>/delete , you should be able to delete your post.
Congratulations!
That was quite a long tutorial, but we learnt how to use databases with Django! We went through it quite quickly, so you may want to read it more than once to gain a proper understanding of what is happening. You can post any questions below. In the next tutorial, we will look at user authentication, as well as making the site a bit more user-friendly.
As always, please upvote if you found this tutorial helpful, it supports me and lets me know that you want more! If you have any questions, post in the comments and I (or someone else) will answer them.
Hi. Thank you for this tutorial. It was very helpful. When will you post the next lesson?
@oakwindicloud Thank you :) I posted this tutorial ages ago, and I never got round to the next part. I'll see if I can find the tutorial file and complete it.
So hopefully I will post it soon :)