In this chapter we’ll build a basic blog application that displays a list of all blog posts on the homepage, has a dedicated page for each blog post, and introduces static files via CSS for styling.

Complete code can be found on Github.

Initial Setup

As covered in previous chapters, our steps for setting up a new Django project are as follows:

  • make a new directory for your code
  • create and enter a new virtual environment
  • install Django
  • create a new Django project
  • perform a migration to set up the database

Execute the following commands in a new command line console. And don’t forget to include the period . at the end of the command for creating our new blog_project.

$ mkdir ~/Desktop/blog
$ cd ~/Desktop/blog
$ python3 -m venv ~/.virtualenvs/blog
$ source ~/.virtualenvs/blog/bin/activate
(blog) $ pip install django
(blog) $ django-admin.py startproject blog_project .
(blog) $ ./manage.py migrate
(blog) $ ./manage.py runserver

We’re putting our code in the directory ~Desktop/blog but it could be located anywhere on your computer. Next we create and enter a new virtual environment called blog. Then we install Django and create our Django project called blog_project. We perform our first migration to set up our database and then spin up the local server to confirm everything installed correctly.

If you navigate to http://127.0.0.1:8000/ in your browser you should see the friendly “Welcome to Django” page.

Django welcome page

From the command line exit the local server by typing Control-c. Let’s create an app within our project which we’ll call blog_app.

(blog) $ ./manage.py startapp blog_app

To ensure Django knows about our new app, add it to the INSTALLED_APPS in our settings.py file:

# blog_project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # my apps
    'blog_app',
]

Ok, initial installation complete! Next we’ll create our database model for blog posts.

Database Models

What are the characteristics of a typical blog application? In our case let’s keep things simple and assume each post has a title, text, and an author. We can turn this into a database model by opening the blog_app/models.py file and enter the code below:

# blog_app/models.py
from django.db import models


class Post(models.Model):
    author = models.ForeignKey('auth.User')
    title = models.CharField(max_length=200)
    text = models.TextField()

    def __str__(self):
        return self.title

At the top we’re importing the class models and then creating a subclass of models.Model called Post. Using this subclass functionality we automatically have access to everything within django.db.models.Models and can add additional fields and methods as desired.

For the author field we’re using a ForeignKey which allows for a many-to-one relationship. This means that a given user can be the author of many different blog posts but not the other way around. For title we’re limiting the length to 200 characters and for text we’re using a TextField which will automatically expand as needed to fit the user’s text. There are many field types available in Django; you can see the full list here.

Now that our new database model is created we need to create a new migration record for it and migrate the change into our database. This two-step process can be completed with the commands below:

(blog) $ ./manage.py makemigrations blog_app
(blog) $ ./manage.py migrate

Our database is configured! What’s next?

We need a way to access our data. Enter the Django admin! First create a superuser account by typing the command below and following the prompts to set up an email and password. Note that when typing your password, it will not appear on the screen for security reasons.

(blog) $ ./manage.py createsuperuser
Username (leave blank to use 'wsv'): wsv
Email:
Password:
Password (again):
Superuser created successfully.

Now start running the Django server again with the command ./manage.py runserver and open up the Django admin at http://127.0.0.1:8000/admin/. Login with your new superuser account.

Oops! Where’s our new Post model?

Admin homepage

We forgot to update blog_app/admin.py so let’s do that now.

# blog_app/admin.py
from django.contrib import admin
from .models import Post

admin.site.register(Post)

If you refresh the page you’ll see the update. Admin homepage

Let’s add two blog posts so we have some sample data to work with. Click on the + Add button next to Posts to create a new entry. Make sure to add an “author” to each post which for now will be your superuser name (mine is wsv).

Admin first post

Admin second post

Admin homepage with two posts

Now that our database model is complete we need to create the necessary views, URLs, and templates so we can display the information on our web application.

URLs

We want to display our blog posts on the homepage so, as in previous chapters, we’ll first configure our project-level URLConfs and then our app-level URLConfs to achieve this.

On the command line quit the existing server with Control-c and create a new urls.py file within our blog_app:

(blog) $ touch blog_app/urls.py

Now update it with the code below.

# blog_app/urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.BlogListView.as_view(), name='post_list'),
]

We’re importing our soon-to-be-created views at the top. The r' tells Python to expect a regular expression where ^ means beginning and $ end, so anything in between the two will be displayed. Then we make it a named URL, post_list, which we can refer to in our views later on. While it’s optional to add a named URL it’s a best practice you should adopt as it helps keep things organized as your number of URLs grows.

We also should update our project-level urls.py file so that it knows to forward all requests directly to the blog app.

# blog_project/urls.py
from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'', include('blog_app.urls')),
]

We’ve added include on the top line to the default settings. And we’re adding a simple '' in the regular expression indicating that URL requests should be redirected as is to blog_app’s URLs for further instructions.

Views

You can use either function-based or class-based views in Django. The advantage of class-based views (CBVs) is that they come with built-in class-based generic views that solve many common issues. Some developers love generic class-based views; others avoid them. For now we’ll use them as our needs are basic and they save us writing a lot of code.

If you want to see a function-based way to build a blog application, I highly recommend the Django Girls Tutorial.

In our views file, add the code below to display the contents of our Post model using ListView.

# blog_app/views.py
from django.views.generic import ListView

from . models import Post


class BlogListView(ListView):
    model = Post
    template = 'blog_app/post_list.html'

On the top two lines we import ListView and our database model Post. Then we subclass ListView and add links to our model and template. This saves us a lot of code versus implementing it all from scratch.

Templates

With our URLConfs and Views now complete, we’re only missing the third piece of the puzzle: templates! As we already saw in Chapter 4: Message board app, we can inherit from other templates to keep our code clean. Thus we’ll start off with a base.html file and a post_list.html file that inherits from it. Then later when we add templates for creating and editing blog posts, they too can inherit from base.html.

Django will look for templates within each app in a nested structure (see below).

├── blog_app
│   ├── templates
│       ├── blog_app
│           ├── base.html
│           ├── post_list.html

We can create this with the following commands:

(blog) $ mkdir blog_app/templates
(blog) $ mkdir blog_app/templates/blog_app
(blog) $ touch blog_app/templates/blog_app/base.html
(blog) $ touch blog_app/templates/blog_app/post_list.html

Our base template is as follows.

<!-- blog_app/templates/blog_app/base.html -->
<html>
  <head>
    <title>Django blog</title>
  </head>
  <body>
    <header>
      <h1><a href="/">Django blog</a></h1>
    </header>
    <div class="container">
      {% block content %}
      {% endblock content %}
    </div>
  </body>
</html>

Note that code between {% block content %} and {% endblock content %} can be filled by other templates. Speaking of which, here is the code for post_list.html.

<!-- blog_app/templates/blog_app/post_list.html -->
{% extends 'blog_app/base.html' %}

{% block content %}
  {% for post in object_list %}
    <div class="post">
      <h2><a href="">{{ post.title }}</a></h1>
      <p>{{ post.text }}</p>
    </div>
  {% endfor %}
{% endblock content %}

At the top we note that this template extends base.html and then wrap our desired code with content blocks. Then we use the Django Templating Language to set up a simple for loop for each blog post. Note that object_list comes from ListView and contains all the objects in our view.

If you start the Django server again ./manage.py runserver and refresh http://127.0.0.1:8000/ we can see it’s working.

Blog homepage with two posts

But it looks terrible. Let’s fix that!

Static files

We need to add some CSS which is referred to as a static file because, unlike our dynamic database content, it doesn’t change. Fortunately it’s simple to add static files like CSS–and eventually JavaScript–to our Django project.

First quit our local server with Control-c and then create a new folder called static within our blog_app directory. Django will automatically search within each app for a folder with this name when looking for static files.

(blog) $ mkdir blog_app/static

Now create a new folder for CSS and a new file for CSS itself.

(blog) $ mkdir blog_app/static/css
(blog) $ touch blog_app/static/css/base.css

What should we put in our file? How about changing the title to be red?

/* blog_app/static/css/base.css */
header h1 a {
    color: red;
}

Now we need to add the static files to our templates by adding {% load staticfiles %} to the top of base.html. Because our other templates inherit from base.html we only have to add this once. And include a new line at the bottom of the <head></head> code that explicitly references our new base.css file.

<!-- blog_app/templates/blog_app/base.html -->
{% load staticfiles %}
<html>
  <head>
    <title>Django blog</title>
    <link rel="stylesheet" href="{% static 'css/base.css' %}">
  </head>
  ...

Start up the server again with .manage.py runserver and look at our updated homepage at http://127.0.0.1:8000/.

Blog homepage with red title

We can do a little better though. How about if we add a custom font and some more CSS? Since this is not a tutorial on CSS simply add the following between <head></head> tags to add Source Sans Pro, a free font from Google.

<!-- blog_app/templates/blog_app/base.html -->
{% load staticfiles %}
<html>
<head>
  <title>Django blog</title>
  <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400" rel="stylesheet">
  <link rel="stylesheet" href="{% static 'css/base.css' %}">
</head>
  ...

Then update our css file by copy and pasting the following code:

/* blog_app/static/css/base.css */
body {
  font-family: 'Source Sans Pro', sans-serif;
  font-size: 18px;
}

header {
  border-bottom: 1px solid #999;
  margin-bottom: 2rem;
  display: flex;
}

header h1 a {
  color: red;
  text-decoration: none;
}

.nav-left {
  margin-right: auto;
}

.nav-right {
  display: flex;
  padding-top: 2rem;
}

.post-entry {
  margin-bottom: 2rem;
}

.post-entry h2 {
  margin: .5rem 0;
}

.post-entry h2 a, .post-entry h2 a:visited {
  color: blue;
  text-decoration: none;
}

.post-entry p {
  margin: 0;
  font-weight: 400;
}

.post-entry h2 a:hover {
  color: red;
}

Refresh the homepage at http://127.0.0.1:8000/ and you should see the following.

Blog homepage with CSS

Much improved.

Individual blog pages

Let’s add the functionality for individual blog pages. That means creating a new view, url, and template. Ready?

Start with the view. We can use the generic view DetailView to simplify things. At the top of the file add DetailView to the list of imports and then create our new view called BlogDetailView.

# blog_app/views.py
from django.views.generic import ListView, DetailView

from . models import Post


class BlogListView(ListView):
    model = Post
    template = 'post_list.html'


class BlogDetailView(DetailView):
    model = Post
    template = 'post_detail.html'

In this new view we define the model we’re using, Post, and the template we want it associated with, post_detail.html. By default DetailView will provide a context object we can use in our template called either object or the name of our model, so post. Also, DetailView expects either a primary key or a slug passed to it as the identifier. More on this shortly.

Now exit the local server Control-c and create our new template for a post detail as follows:

(blog) $ touch blog_app/templates/blog_app/post_detail.html

And then type in the following code:

<!-- blog_app/templates/blog_app/post_detail.html -->
{% extends 'blog_app/base.html' %}

{% block content %}
  <div class="post-entry">
    <h2>{{ post.title }}</h2>
    <p>{{ post.text }}</p>
  </div>

{% endblock content %}

At the top we specify that this template inherits from base.html. And then display the title and text from our context object, which DetailView makes accessible as post.

Personally I found the naming of context objects in generic views extremely confusing at first. Because our context object from DetailView is either our model name post or object we could also update our template as follows and it would work exactly the same.

<!-- blog_app/templates/blog_app/post_detail.html -->
{% extends 'blog_app/base.html' %}

{% block content %}
  <div class="post-entry">
    <h2>{{ object.title }}</h2>
    <p>{{ object.text }}</p>
  </div>

{% endblock content %}

And if you find using post or object confusing, we can also explicitly set the name of the context object in our view. So if we wanted to call it anything_you_want and then use that in the template, the code would look as follows and it would work the same.

# blog_app/views.py
...
class BlogDetailView(DetailView):
    model = Post
    template = 'post_detail.html'
    context_object_name = 'anything_you_want'
<!-- blog_app/templates/blog_app/post_detail.html -->
{% extends 'blog_app/base.html' %}

{% block content %}
  <div class="post-entry">
    <h2>{{ anything_you_want.title }}</h2>
    <p>{{ anything_you_want.text }}</p>
  </div>

{% endblock content %}

The “magic” naming of the context object is a price you pay for the ease and simplicity of using generic views. They’re great if you know what they’re doing but can be hard to customize if you want different behavior.

Ok, what’s next? How about adding a new URLConf for our view, which we can do as follows.

# blog_app/urls.py
from django.conf.urls import url
from . views import BlogListView, BlogDetailView

urlpatterns = [
    url(r'^$', BlogListView.as_view(), name='post_list'),
    url(r'^post/(?P<pk>\d+)/$', BlogDetailView.as_view(), name='post-detail'),
]

All blog post entries will start with post/ and match the named group (?P<pk>\d+)/$' which takes the form P<name>pattern where name is the name of the group and pattern is some pattern to match. So we’re capturing all the digits, represented by \d+, and passing them to our DetailView as a parameter pk. DetailView expects to receive either a primary key or a slug in order to work. A slug for our “Hello, World” blog post would take the form of post/hello-world/.

If you now start up the server with ./manage.py runserver and go directly to http://127.0.0.1:8000/post/1/ you’ll see a dedicated page for our first blog post.

Blog post one detail

Woohoo! You can also go to http://127.0.0.1:8000/post/2/ to see the second entry.

To make our life easier, we should update the link on the homepage so we can directly access individual blog posts from there. Currently in post_list.html our link is empty: <a href="">. Update it to <a href="{% url 'post-detail' post.pk %}">.

<!-- blog_app/templates/blog_app/post_list.html -->
{% extends 'blog_app/base.html' %}

{% block content %}
  {% for post in object_list %}
  <div class="post-entry">
    <h2><a href="{% url 'post-detail' post.pk %}">{{ post.title }}</a></h2>
    <p>{{ post.text }}</p>
  </div>
  {% endfor %}
{% endblock content %}

We start off by telling our Django template we want to reference a URLConf by using the code {% url ... %}. Which URL? The one named post-detail, which is the name we gave BlogDetailView in our URLConf just a moment ago. And since our view expects a parameter, we pass that in too. It’s the parameter called pk on our post database model.

Don’t worry if the previous paragraph is a little confusing. I promise with practice the relationships between urls.py, views.py, and templates will make more sense.

To confirm everything works, refresh the main page at http://127.0.0.1:8000/ and click on the title of each blog post to confirm the new links work.

Conclusion

We’ve now built a basic blog application from scratch! Using the Django admin we can create, edit, or delete the content.

In the next section, Chapter 6: Blog app with forms, we’ll add forms so we don’t have to use the Django admin at all for these changes.




Sign up for the Django For Beginners newsletter for updates when new chapters are available and special discounts for the print edition of the book.