Learn to Code via Tutorials on Repl.it

← Back to all posts
Learning Web Development w/ Python Part 4
ArchieMaclean (646)

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:

> 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:

  • Sheet (e.g. People, Repls) becomes Entity (or table)
  • 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.


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:

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:

{% extends 'base.html' %}

{% block title %}{{ post.title }}{% endblock title %}

{% block content %}
<p>Posted by: {{ post.author }}</p>
<p>On: {{ post.date }}</p>
<h1>{{ post.title }}</h1>
<p>{{ post.text }}</p>
{% endblock content %}

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 html form 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
  • On Line 11, we end the form.

URL

Go to urls.py and add the following:

from .views import HomePageView, PostPageView, PostCreateView

urlpatterns = [
  path('create',PostCreateView.as_view(), name='create_post'),
  path('post/<int:pk>/',PostPageView.as_view(), name='post'),
  path('', HomePageView.as_view(), name='home'),
]

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:

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.

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:

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:

{% extends 'base.html' %}

{% block title %}Edit Post{% endblock title %}

{% block content %}
<h1>Post title</h1>
<form action="" method="POST">{% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Update">
</form>
{% endblock content %}

This is almost identical to the CreateView.

URL

Finally, add the following to Posts/urls.py:

from .views import HomePageView, PostPageView, PostCreateView, PostUpdateView

urlpatterns = [
  path('post/<int:pk>/edit/',PostUpdateView.as_view(), name='update_post'),
  # stuff
]

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.

URL

Finally, add this to Posts/urls.py:

from .views import HomePageView, PostPageView, PostCreateView, PostUpdateView, PostDeleteView

urlpatterns = [
  path('post/<int:pk>/delete/',PostDeleteView.as_view(),name='delete_post'),
  # stuff
]

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.

Commentshotnewtop
oakwindicloud (0)

Hi. Thank you for this tutorial. It was very helpful. When will you post the next lesson?

ArchieMaclean (646)

@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 :)

AS4 (3)

Can we use this code in our own projects? Also when is the next tutorial?

ArchieMaclean (646)

@AS4 You can! Also, a fair amount of Django is repetitive, so for a lot of things you can just copy/paste and change a few names of files/classes to get it up and running.

The next part should hopefully be out in 2 days or so.