Build a Personal Diary With Django and Python

Build a Personal Diary With Django and Python

by Philipp Acsany basics django front-end projects web-dev

A diary is a personal safe space. With the help of Django, you can create a diary on your own computer without storing data in anyone else’s cloud. By following along with the project below, you’ll see how quickly you can build a functioning web app in Django without any external dependencies.

In this tutorial, you’ll learn how to:

  • Set up a Django project
  • Work with the standard SQLite database
  • Make use of the Django admin site
  • Create models and class-based views
  • Nest and style templates
  • Secure your diary with authentication

This tutorial will guide you step-by-step to your final diary. If you’re just starting out with Django and want to finish your first real project, then this tutorial is for you!

To get the complete source code for the Django project and its steps, click the link below:

Demo Video

On the main page of your diary, you’ll have a list of entries. You can scroll through them and create new ones with a click of a button. The styling is provided in this tutorial, so you can focus on the Django part of the code. Here’s a quick demo video of how it will look in action:

By the end of the tutorial, you’ll be able to flawlessly navigate your diary to create, read, update, and delete entries on demand.

Project Overview

The tutorial is divided into multiple steps. That way, you can take breaks and continue at your own pace. In each step, you’ll tackle a specific area of your diary project:

  1. Setting up your Django diary project
  2. Creating entries on the back end
  3. Displaying entries on the front end
  4. Adding styling
  5. Managing entries on the front end
  6. Improving your user experience
  7. Implementing authentication

By following along, you’ll explore the basics of web apps and how to add common features of a Django project. After finishing the tutorial, you’ll have created your own personal diary app and will have a Django project blueprint to build upon.

Prerequisites

You don’t need any previous knowledge of Django to complete this project. If you want to learn more about the topics you encounter in this tutorial, you’ll find links to resources along the way.

However, you should be comfortable using the command line and have a basic knowledge of Python and classes. Although it helps to know about virtual environments and pip, you’ll learn how to set everything up as you work through the tutorial.

Step 1: Setting Up Your Django Diary

Start the project by creating your project directory and setting up a virtual environment. This setup will keep your code isolated from any other projects on your machine. You can name your project folder and the virtual environment any way you want. In this tutorial, the project folder is named my-diary, and the virtual environment is named .venv:

Shell
$ mkdir my-diary
$ cd my-diary
$ python3 -m venv .venv
$ source .venv/bin/activate

Your prompt now starts with the name of your virtual environment in parenthesis. This is an indicator that the virtual environment is activated. For the rest of the tutorial, your virtual environment must be activated. All of the following steps will take place inside this directory or its subdirectories.

The only other requirement for your diary is Django itself. Install the specific version of this tutorial with pip:

Shell
(.venv) $ python -m pip install Django==3.2.1

This command installs Django and some dependencies that Django requires. That’s everything you need.

Initialize Django

With all the requirements in place, it’s time to start the Django project itself. Use Django’s command-line utility to create the base project structure:

Shell
(.venv) $ django-admin startproject diary .

Don’t forget to add the dot (.) at the end of the command above. The dot prevents Django from creating another directory for your diary project.

Django just created a manage.py file and a folder named diary with five files. You don’t have to understand what they do precisely. If you’re curious, you can take a glimpse into the files. They all contain an explanation at the beginning that describes why they exist. During this tutorial, you’ll only need to edit two of them:

File Edited during tutorial?
manage.py
diary/__init__.py
diary/asgi.py
diary/settings.py
diary/urls.py
diary/wsgi.py

From now on, the manage.py file will take over administrative tasks in the command line. You’ll meet some of them during this tutorial.

Create the Database

Now that the basis of your Django diary project is prepared, you need a place to store the future contents of your diary. For this, you must create a database.

Django is shipped with support for multiple databases and works with an SQLite database by default if no other database configurations are provided. An SQLite database is all you need because you are the only user that connects to it, and your Django diary project will only run locally.

The best part is that you can create an SQLite database with a single command. By running migrations, you apply changes of a database schema into the database:

Shell
(.venv) $ python manage.py migrate

When you look in your project directory, you should see a db.sqlite3 file. Give yourself a pat on the shoulder: You just created a database!

To compare the current state of your project with the downloadable files for this tutorial, click the link below:

The files related to this section are in the source_code_step_1/ directory.

Be a Superuser

As the owner of your personal diary, you’ve earned yourself the role of the superuser. Claim it with this command:

Shell
(.venv) $ python manage.py createsuperuser
Username (leave blank to use 'root'): admin
Email address: admin@example.com
Password: RealPyth0n
Password (again): RealPyth0n
Superuser created successfully.

You’ll be prompted to choose a username, provide an email address, and set a password. This is the key to your diary, so make sure to remember it.

Run a Development Web Server

Another command that you’ll use frequently is runserver. This command runs a lightweight development web server:

Shell
(.venv) $ python manage.py runserver

You can specify the IP address and the port of runserver. By default, the server runs on port 8000 on 127.0.0.1 and is only accessible on your computer. With the server running, you can visit your Django project in your browser by using either http://127.0.0.1:8000 or http://localhost:8000:

Successful Django Installation

This is the home page of your diary. So far, there’s only a rocket to see. That means the installation was successful.

Finish the first step of this tutorial by visiting http://localhost:8000/admin and logging in with your credentials:

Django admin site - Start

This is your very own Django admin site! It’s one of the most powerful features of Django. With just a few adjustments, it gives you the ability to manage content and users right away. For now, there’s not much to see in the Django admin site. Time to change that!

Step 2: Adding Your Diary Entries to the Back End

A Django project contains one or more applications. The scope of an app should be limited. In the beginning, differentiating between a project and apps can be confusing. But in big Django projects, this separation of concerns keeps the codebase clean. Another advantage of this structure is that you can reuse apps for other projects.

Connect the Entries App

In your terminal, the Django development web server may still be running. Stop it by pressing Ctrl+C in the terminal.

In this tutorial, you need only one additional app. The primary purpose of that app is to handle your diary entries, so let’s call the app entries. Run the command to create the entries app:

Shell
(.venv) $ python manage.py startapp entries

This command creates an entries folder in your project with some predefined files. You’ll need to edit only three of them later in this tutorial:

File Edited during tutorial?
entries/__init__.py
entries/admin.py
entries/apps.py
entries/models.py
entries/tests.py
entries/views.py

As you can see, some of them have the same name as files in the diary/ directory. By clicking the link below, you can compare your folder structure with the one in the source_code_step_2/ directory:

So far, Django doesn’t know the app you just created. To connect the entries app to the Django diary project, add the path to the configuration class at the beginning of the INSTALLED_APPS list in diary/settings.py:

Python
# diary/settings.py

INSTALLED_APPS = [
  "entries.apps.EntriesConfig",
  "django.contrib.admin",
  "django.contrib.auth",
  "django.contrib.contenttypes",
  "django.contrib.sessions",
  "django.contrib.messages",
  "django.contrib.staticfiles",
]

The entries app is plugged into the diary project now, and Django finds its configurations. One of these configurations is the model that describes how your diary entries should look in the database.

Create the Entries Model

You already created the database. Now it’s time to define the database table where the entries of your diary will be stored. In Django, you do that with a model class. Just like conventional classes in Python, model names should be singular and uppercase. While your app is called entries, your model will be called Entry.

The fields of the Entry model are the elements that a diary entry will have. In the front, those fields will be displayed as a form. In the back, they will be the columns of your Entry database table. A diary entry in this tutorial contains three fields:

  1. title is the headline.
  2. content is the main text body.
  3. date_created is the creation-date and -time.

In entries/models.py, first import timezone from django.utils. Then create the Entry class in the same file as shown below:

Python
 1# entries/models.py
 2
 3from django.db import models
 4from django.utils import timezone
 5
 6class Entry(models.Model):
 7    title = models.CharField(max_length=200)
 8    content = models.TextField()
 9    date_created = models.DateTimeField(default=timezone.now)
10
11    def __str__(self):
12        return self.title
13
14    class Meta:
15        verbose_name_plural = "Entries"

By importing the timezone module, you can use timezone.now as the default argument for date_created in line 9. That way, the current date and time will be used by default if you don’t define a specific value for it when creating an entry. You’ll make use of this behavior later when you create a form for your diary entries.

In addition to title, content, and date_created, Django will automatically add id as a unique primary key. The string representation of an entry with the primary key 1 would be Entry object (1) by default. When you add .__str__(), you can customize what is shown instead. For a diary entry, the title is a better string representation.

Another variable you have to adjust is verbose_name_plural. Otherwise, Django would spell the plural of your Entry as Entrys and not Entries.

Register the Entry Model

To also see the Entry model in the Django admin site, you need to register it in entries/admin.py:

Python
# entries/admin.py

from django.contrib import admin
from .models import Entry

admin.site.register(Entry)

Django won’t throw an error when you forget to register a model in the admin site. After all, not every model needs to be managed in a user interface. But for the minimal viable product of your diary, you will take advantage of the built-in Django admin site.

Migrate the Entry Model

After adding the new class and registering it on the admin site, you need to make migration files for Django and run them. With makemigrations, you create the migration files, which contain Django’s instructions for building a database. With migrate, you implement them:

Shell
(.venv) $ python manage.py makemigrations
(.venv) $ python manage.py migrate

Once the migrations are finished, run the development web server, move to the browser, and visit the Django admin site at http://localhost:8000/admin:

Django admin site without entries

Currently, there are no entries listed. Finish this step by creating at least one entry to your diary by clicking Add Entry. Not sure what to write? Maybe reflect on how awesome it is that you have a fully functional back end for your Django diary project!

Step 3: Displaying Your Diary Entries on the Front End

You’re now able to add new entries in the Django admin site. But when you visit the homepage of your diary in the browser, it still shows the jittering rocket. In this step, you’ll learn how to display your diary entries on the front end.

If you want to see how the code looks at the end of this step, click the link below:

The files related to this step are in the source_code_step_3/ directory.

Create List and Detail Views

There are two kinds of views in Django: function-based views and class-based views. Both take a web request and return a web response. Generally speaking, function-based views give you more control but also more work. Class-based views give you less control but also less work.

Less work sounds good. But that’s not the only reason you’ll use class-based views for your diary. Your Django diary will use views that are typical for a web app, such as showing a list of database items or their details. That’s why class-based views are a good choice for your diary views.

Django provides many generic views out of the box. In this case, you’ll create subclasses of a DetailView and a ListView and connect them to your Entry model in entries/views.py:

Python
 1# entries/views.py
 2
 3from django.views.generic import (
 4    ListView,
 5    DetailView,
 6)
 7
 8from .models import Entry
 9
10class EntryListView(ListView):
11    model = Entry
12    queryset = Entry.objects.all().order_by("-date_created")
13
14class EntryDetailView(DetailView):
15    model = Entry

As promised, there’s not much code for you to write at this time. The query Entry.objects.all() on line 12 would return all entries ordered by their primary key. Enhancing it with .order_by("-date_created") will return your entries in ascending order, with the newest entry on top of the list.

When you write a view like this, Django will make assumptions in the background, such as the name and location of the template to render by the view.

Create Your Templates

With templates, you can generate HTML dynamically. Django expects the templates for the class-based views you just created to be in a specific location with a particular name. Create the subfolders for your templates:

Shell
(.venv) $ mkdir -p entries/templates/entries

Granted, the path to the templates looks a bit odd. But this way you make sure that Django will find exactly the right templates, even when other apps share the same model names. Inside of entries/templates/entries/, you’ll store all template files for the Entry model. Start by creating entry_list.html and adding this content:

HTML
 1<!-- entries/templates/entries/entry_list.html -->
 2
 3{% for entry in entry_list %}
 4    <article>
 5        <h2 class="{{ entry.date_created|date:'l' }}">
 6            {{ entry.date_created|date:'Y-m-d H:i' }}
 7        </h2>
 8        <h3>
 9            <a href="{% url 'entry-detail' entry.id %}">
10                {{ entry.title }}
11            </a>
12        </h3>
13    </article>
14{% endfor %}

In Django templates, you can even reference CSS classes dynamically. When you take a look at <h2> on line 5, you can see that class="{{ entry.date_created|date:'l' }}" is added to it. This shows the timestamp with a special formatting. That way, the <h2> element has the weekday as a class, and you can give every weekday a unique color in the CSS later.

Inside the entry_list loop, you can access the fields of the Entry model. To not clutter the list with too much information, you’ll only show the content when you visit the detail page of an entry. Create this detail page in entries/templates/entries/ with entry_detail.html as the filename and add this content:

HTML
<!-- entries/templates/entries/entry_detail.html -->

<article>
    <h2>{{ entry.date_created|date:'Y-m-d H:i' }}</h2>
    <h3>{{ entry.title }}</h3>
    <p>{{ entry.content }}</p>
</article>

A detail template expects exactly one entry object. That’s why you can access it directly here without a loop.

Add Routes to Your Views

To see the templates in action, you need to connect your views to URLs. Django works with a urls.py file to dispatch the incoming requests from users in the browser. A file like this already exists in the diary project folder. For the entries app, you must create it first at entries/urls.py and add the paths to EntryListView and EntryDetailView:

Python
 1# entries/urls.py
 2
 3from django.urls import path
 4
 5from . import views
 6
 7urlpatterns = [
 8    path(
 9        "",
10        views.EntryListView.as_view(),
11        name="entry-list"
12    ),
13    path(
14        "entry/<int:pk>",
15        views.EntryDetailView.as_view(),
16        name="entry-detail"
17    ),
18]

The path() function on lines 8 and 13 must have at least two arguments:

  1. A route string pattern, which contains a URL pattern
  2. The reference to a view, which is an as_view() function for class-based views

Additionally, you can pass arguments as kwargs and provide a name. With a name, you can reference views easily in your Django project. So even if you decide to change the URL pattern, you don’t have to update your templates.

Now that the URLs for the entries app are in place, you need to connect them to the urlpatterns list of diary. When you open diary/urls.py, you’ll see the urlpatterns your Django Diary project uses. So far, there’s only the route to "admin/", which is added by default so you can reach the Django admin site. To show the entries of your diary when you visit http://localhost:8000, you first need to dispatch the root URL to the entries app:

Python
# diary/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("entries.urls")),
]

After you create new templates, manually restart the Django development web server. Then visit http://localhost:8000 and enjoy your views:

Diary Entries Unstyled

You can see the details of an entry by clicking the link to it in the list or visiting http://localhost:8000/entries/1, where 1 is the ID of an existing entry.

Now everything is in place to see your entries in the front end. Your diary still looks a bit unstyled, though. Let’s change that in the next step!

Step 4: Making Your Django Diary Look Nice

In this step, you’ll add some style to your diary. If you want to take a look at the finished code of this step, then click the link below and check out the source_code_step_4/ directory:

While your writing is creative, the design of the diary is currently a bit basic. You can start spicing things up by creating a base template at entries/templates/entries/base.html with this content:

HTML
<!-- entries/templates/entries/base.html -->

{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>My Diary</title>
    <link rel="stylesheet" href="{% static 'css/diary.css' %}">
</head>

<body>
    <h1><a href="/">Dear diary …</a></h1>

    {% block content %}{% endblock %}

</body>

</html>

With template inheritance, you don’t have to repeat markup in your templates. Instead, you extend your child templates. Django then automatically merges them together when serving them in the view.

Add a Stylesheet

By inserting {% load static %} at the beginning of a template file, you can reference static files with the {% static %} template tag and a relative path to the CSS file. Create diary.css in entries/static/css/ and expand the box below to view the CSS code you’ll add to the file.

Copy and paste the CSS code below into diary.css:

CSS
/* entries/static/css/diary.css */

* {
    box-sizing: border-box;
}

body {
    font-family: sans-serif;
    font-size: 18px;
}

a {
    color: inherit;
}
a:hover {
    opacity: 0.7;
}

h1 {
    font-size: 2.8em;
}

h1 a {
    text-decoration: none;
}

h2, h3 {
    font-size: 1.4em;
    margin: 0;
    display: inline-block;
    padding: 0.5rem 1rem;
    vertical-align: top;
}

h2 {
    background-color: aquamarine;
}

.mark {
    background-color: gainsboro;
}
.mark a {
    text-decoration: none;
}

article {
    margin-bottom: 0.5rem;
}

p {
    font-size: 1.2em;
    padding-left: 1rem;
    line-height: 1.3em;
    max-width: 36rem;
    color: dimgray;
}

em {
    font-style: normal;
    font-weight: bold;
}

/* Form */

label {
    display: block;
}

button,
textarea,
input {
    font-size: inherit;
    min-height: 2.5em;
    padding: 0 1rem;
}

input[type="text"],
textarea {
    width: 100%;
}

textarea {
    padding: 0.5rem 1rem;
    font-family: sans-serif;
}

button,
input[type="submit"] {
    margin: 0 1rem 2px 1rem;
    cursor: pointer;
    font-weight: bold;
    min-width: 8rem;
}

/* Day coloring */

.Saturday,
.Sunday {
    background-color: lightsalmon;
}

Feel free to enhance and optimize the code above to adjust any elements to your taste. If you don’t know CSS, it’s worth learning the basics if you are into web development.

As stated before, every <h2> element in the entry list has its weekday as a class. By styling .Saturday and .Sunday differently, you can easily spot weekends in the list.

Extend Child Templates

Now it’s time to connect the child templates with the base.html parent template. Update entries/templates/entries/entry_list.html so it looks like this:

HTML
 1<!-- entries/templates/entries/entry_list.html -->
 2
 3{% extends "entries/base.html" %}
 4
 5{% block content %}
 6    {% for entry in entry_list %}
 7        <article>
 8            <h2 class="{{ entry.date_created|date:'l' }}">
 9                {{ entry.date_created|date:'Y-m-d H:i' }}
10            </h2>
11            <h3>
12                <a href="{% url 'entry-detail' entry.id %}">
13                    {{ entry.title }}
14                </a>
15            </h3>
16        </article>
17    {% endfor %}
18{% endblock %}

The {% block %} template tag defines a part of the document that a child template can override. To enable this, you must declare that your child template extends the parent template and define a block element with the same name. With the {% extends %} template tag on line 3, you define entries/base.html as the parent. {% block %} and {% endblock %} on lines 5 and 18 wrap the markup that will be placed in the content block of the parent.

Do the same for entries/templates/entries/entries_detail.html:

HTML
<!-- entries/templates/entries/entries_detail.html -->

{% extends "entries/base.html" %}

{% block content %}
    <article>
        <h2>{{ entry.date_created|date:'Y-m-d H:i' }}</h2>
        <h3>{{ entry.title }}</h3>
        <p>{{ entry.content }}</p>
    </article>
{% endblock %}

Both templates inherit the HTML structure and styling from their parent template. They share the same title and <h1> headline, plus the styling provided by diary.css. To see this in action, start your Django development server and go to http://localhost:8000:

Diary Entries Styled

You can now read your entries with style. However, when you want to create, update, or delete an entry, you have to go to the Django admin site. That’s way too many clicks for when you want to quickly jot down some thoughts. In the next step, you’ll improve your workflow by adding this functionality to the front end.

Step 5: Managing Your Diary Entries on the Front End

When you build and use web apps, there are four basic operations that you perform all the time. These operations are so common that they are frequently just referred to by the CRUD acronym:

  • Create
  • Read
  • Update
  • Delete

In the Django admin site, you can perform all these actions already. In the front end, you only can read your entries so far. To resemble the functionality of the Django admin site, you’ll repeat what you’ve done already for the EntryDetail and EntryList, adding a view, a template, and a URL.

Add the Views

In entries/views.py you’ve imported ListView and DetailView so far. Update your import statements so they look like this:

Python
# entries/views.py

from django.urls import reverse_lazy
from django.views.generic import (
    ListView,
    DetailView,
    CreateView,
    UpdateView,
    DeleteView,
)

Add your three subclasses at the bottom of entries/views.py:

Python
 1# entries/views.py
 2
 3class EntryCreateView(CreateView):
 4    model = Entry
 5    fields = ["title", "content"]
 6    success_url = reverse_lazy("entry-list")
 7
 8class EntryUpdateView(UpdateView):
 9    model = Entry
10    fields = ["title", "content"]
11
12    def get_success_url(self):
13        return reverse_lazy(
14            "entry-detail",
15            kwargs={"pk": self.entry.id}
16        )
17
18class EntryDeleteView(DeleteView):
19    model = Entry
20    success_url = reverse_lazy("entry-list")

This time, it’s not enough to connect the classes to your Entry model. For EntryCreateView and EntryUpdateView, you also need to define which model fields should be displayed in the form, as you can see in lines 5 and 10. Your EntryDeleteView in line 18 only performs the action of deleting an entry item, so you don’t need to define any fields in it.

Additionally, you need to define where the user should be redirected after submitting the form of the view. By default, .get_success_url() simply returns the value of success_url. In EntryUpdateView, you need to overwrite this method.

By providing the entry.id as a keyword argument in line 15, you stay on the entry detail page after editing it. Instead of using URLs, you use reverse_lazy to refer to them by name, just like you did in your templates.

Create the Templates

Like before, Django looks for templates with a specific name:

  • For EntryDeleteView, it’s entry_confirm_delete.html.
  • For EntryCreateView, it’s entry_form.html.
  • For EntryUpdateView it would be entry_update_form.html.

When Django does not find entry_update_form.html, it tries entry_form.html as a fallback. You can take advantage of this by creating a template that handles both views in entries/templates/entries/ and adding a basic submit form:

HTML
 1<!-- entries/templates/entries/entry_form.html -->
 2
 3{% extends "entries/base.html" %}
 4{% block content %}
 5    <form method="post">
 6        {% csrf_token %}
 7        {{ form.as_p }}
 8        <input type="submit" value="Save">
 9    </form>
10    {% if entry %}
11        <a href="{% url 'entry-detail' entry.id %}">
12            <button>Cancel</button>
13        </a>
14    {% else %}
15        <a href="{% url 'entry-list' %}">
16            <button>Cancel</button>
17        </a>
18    {% endif %}
19{% endblock %}

When this template is loaded by CreateView, the form will be empty, and canceling it will bring you to the list of entries again. When loaded by CreateUpdateView, it will be prefilled with the current title and content of the entry. Canceling will bring you to the entry detail page.

There are multiple ways to render a form in a template. With {{ form.as_p }} in line 7, Django will display the fields you defined in the view wrapped in paragraphs. Whenever you post content in Django forms, you must also include the {% csrf_token %} template tag in line 6 to it. This is a security measure to prevent cross-site request forgery.

As with the other templates, you add {% extends "entries/base.html" %} in line 3 to extend the base template. Then you define what to include in the block content tag between lines 4 and 18. You use the same pattern for entry_confirm_delete.html in entries/templates/entries/:

HTML
<!-- entries/templates/entries/entry_confirm_delete.html -->

{% extends "entries/base.html" %}
{% block content %}
    <form method="post">{% csrf_token %}
        <p>
            Are you sure you want to delete
            <em>"{{ entry.title }}"</em>
            created on {{ entry.date_created|date:'Y-m-d' }}?
        </p>
        <input type="submit" value="Confirm">
    </form>
    <a href="{% url 'entry-detail' entry.id %}">
        <button>Cancel</button>
    </a>
{% endblock %}

This template will show up when you are about to delete an entry. Mentioning "{{ entry.title }}" and {{ entry.created_date|date:'Y-m-d' }} in the form reminds you which entry you would delete by pressing Confirm.

Create the URLs

After you’ve created the views and their templates, create their routes to access them in the front end. Add three additional paths to urlpatterns in entries/urls.py:

Python
# entries/urls.py

urlpatterns = [
    path(
        "",
        views.EntryListView.as_view(),
        name="entry-list"
    ),
    path(
        "entry/<int:pk>",
        views.EntryDetailView.as_view(),
        name="entry-detail"
    ),
    path(
        "create",
        views.EntryCreateView.as_view(),
        name="entry-create"
    ),
    path(
        "entry/<int:pk>/update",
        views.EntryUpdateView.as_view(),
        name="entry-update",
    ),
    path(
        "entry/<int:pk>/delete",
        views.EntryDeleteView.as_view(),
        name="entry-delete",
    ),
]

For entry-create, you only need a basic create path. Like the entry-detail created earlier, entry-update and entry-delete need a primary key to identify which entry should be updated or deleted.

Now you can create, update, and delete entries for your diary directly in the front end. Start the development web server and visit http://localhost:8000/create to test it. If you want to compare your code with the one from this tutorial, then click the link below:

You can find the files related to this step in the source_code_step_5/ directory.

Step 6: Improving Your User Experience

Using your diary, you may have stumbled over a few quirks that make navigating around a bit annoying. In this step, you’ll tackle them one by one. You’ll see that small changes in the interface can have a big impact on your diary user experience.

Handle Your Success

It’s always nice to get some feedback, especially when it’s positive. Using the messages framework, you can quickly define one-time flash messages that display after you submit your forms. To make use of this feature, import messages and SuccessMessageMixin to entries/views.py:

Python
# entries/views.py

from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin

EntryListView and EntryDetailView are read views and don’t handle a form. They may display a message in the template, but they don’t send one. That means you don’t need to subclass SuccessMessageMixin for them. EntryCreateView, EntryUpdateView and EntryDeleteView, on the other hand, do add a notification to the message storage, so you need to adjust their functionality:

Python
 1# entries/views.py
 2
 3class EntryCreateView(SuccessMessageMixin, CreateView):
 4    model = Entry
 5    fields = ["title", "content"]
 6    success_url = reverse_lazy("entry-list")
 7    success_message = "Your new entry was created!"
 8
 9class EntryUpdateView(SuccessMessageMixin, UpdateView):
10    model = Entry
11    fields = ["title", "content"]
12    success_message = "Your entry was updated!"
13
14    def get_success_url(self):
15        return reverse_lazy(
16            "entry-detail",
17            kwargs={"pk": self.object.pk}
18        )

After inheriting SuccessMessageMixin in EntryCreateView on line 3 and in EntryUpdateView on line 9, you define a success_message for them on lines 7 and 12. Especially when you perform a destructive action like deleting an entry, it’s crucial to give feedback that everything went fine. To show a message in a DeleteView, you have to add a custom .delete() method and add your custom success_message to the messages framework manually:

Python
 1# entries/views.py
 2
 3class EntryDeleteView(DeleteView):
 4    model = Entry
 5    success_url = reverse_lazy("entry-list")
 6    success_message = "Your entry was deleted!"
 7
 8    def delete(self, request, *args, **kwargs):
 9        messages.success(self.request, self.success_message)
10        return super().delete(request, *args, **kwargs)

You need this extra method on line 8 because the DeleteView class is not an ancestor of a FormView. That’s why you can skip adding the SuccessMessageMixin in your EntryDeleteView on line 3.

Click the link below to see the full content of entries/views.py. You can find it in source_code_step_6/ directory:

Now that the views send messages, you need to enhance the templates to display them.

Get the Message

Messages are stored in the message storage. By looping through it, you show all messages that are currently in the message storage. In Django templates, you accomplish this by using the messages template tag. The way you built the diary and structured the templates, you only need to add it to entries/base.html:

HTML
 1<!-- entries/base.html -->
 2
 3<h1><a href="/">Dear diary …</a></h1>
 4
 5{% if messages %}
 6    <ul class="messages">
 7    {% for message in messages %}
 8        <li class="message">
 9            {{ message }}
10        </li>
11    {% endfor %}
12    </ul>
13{% endif %}
14
15{% block content %}{% endblock %}

By wrapping the messages list in {% if messages %} on lines 4 and 12, you ensure it will only be shown conditionally, if there are any messages in the storage.

Improve Your Navigation

To create, edit, or delete an entry, you need to remember the corresponding URLs and then type them in the address bar. How tedious! Luckily, you can fix this by adding links to the views. Start with a link to the entry-create view in the entries/templates/entries/entry_list.html template:

HTML
 1<!-- entries/templates/entries/entry_list.html -->
 2
 3{% block content %}
 4<article>
 5    <h2 class="mark">{% now "Y-m-d H:i" %}</em></h2>
 6    <a href="{% url 'entry-create' %}"><button>Add new entry</button></a>
 7</article>
 8
 9{% for entry in entry_list %}

This markup will resemble the other diary entry list items. Add a new paragraph to entries/templates/entries/entry_detail.html template right after </article> to quickly edit or delete an entry:

HTML
<!-- entries/templates/entries/entry_detail.html -->

</article>
<p>
    <a href="{% url 'entry-update' entry.id %}">✍️ Edit</a>
    <a href="{% url 'entry-delete' entry.id %}">⛔ Delete</a>
</p>
{% endblock %}

This code adds two links underneath the entry details that link to entry-update and entry-delete, respectively.

Style Your Messages

Finally, style the flash messages by adding .messages and .message at the end of entries/static/css/diary.css:

CSS
/* entries/static/css/diary.css */

/* Messages */

.messages {
    padding: 0;
    list-style: none;
}

.message {
    width: 100%;
    background: lightblue;
    padding: 1rem;
    text-align: center;
    margin: 1rem 0;
}

Stop the development web server by pressing Ctrl+C in the terminal and restart it. Then visit http://localhost:8000 to see your changes in action. If the messages look unstyled, you may have to clear the cache of your browser so it reloads the stylesheet changes:

Diary List With Message

You now own a fully usable diary web app built entirely in Django. Although your diary is only stored locally, it is a good idea to make sure only you can access it. Let’s add that restriction before you start journaling.

Step 7: Putting a Lock on Your Django Diary

What a small, golden lock is for a physical diary, a login form is for your digital one. The key to this lock is the name and password combination that you’re already using to log in to the Django admin site.

Reuse Your Django Admin Login

The authentication system that Django provides is quite basic. For other projects with regular users, you may consider customizing it. But for your Django diary, it’s sufficient to reuse the login of the Django admin site. You’ll see that this works just fine in a moment. First, let’s take care of logging out.

You log out by visiting a special URL. Django already provides a URL with the name admin:logout to log you out of the Django admin site. When you are logged in and visit this URL from anywhere in your project, Django automatically logs you out. To quickly access this URL, add a link to it at the bottom of entries/templates/entries/base.html:

HTML
<!-- entries/templates/entries/base.html -->

{% load static %}
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>My Diary</title>
    <link rel="stylesheet" href="{% static 'css/diary.css' %}">
</head>

<body>
<h1><a href="/">Dear diary …</a></h1>

{% if messages %}
    <ul class="messages">
    {% for message in messages %}
        <li class="message">
            {{ message }}
        </li>
    {% endfor %}
    </ul>
{% endif %}

{% block content %}{% endblock %}

<hr>
<a href="{% url 'admin:logout' %}">Logout</a>
</body>

</html>

Start your development web server, go to your homepage at http://localhost:8000, and click the Logout link. Now when you visit http://localhost:8000/admin, you see the login form. That means you’re logged out. When you visit http://localhost:8000 again, your diary entries are still accessible, though. Time to change that!

Restrict Access to Your Views

In Django, you can define who is allowed to see which views. By default, they are open to everyone who visits your website. To make sure that the views require an authenticated user to be accessed, import LoginRequiredMixin at the top of entries/views.py:

Python
# entries/views.py

from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
from django.views.generic import (
    ListView,
    DetailView,
    CreateView,
    UpdateView,
    DeleteView,
)

from .models import Entry

Once a view class uses the LoginRequiredMixin, it requires a successful login first. Additionally, you must define a login_url so Django knows where to redirect you when you’re not logged in. Instead of doing this for all classes individually, you can create a base class that inherits LoginRequiredMixin and defines the login_url in entries/views.py:

Python
# entries/views.py

class LockedView(LoginRequiredMixin):
    login_url = "admin:login"

Now you can inherit LockedView in all your other views that should only be accessible for authenticated users. Edit entries/views.py so your classes look like this:

Python
# entries/views.py

class EntryListView(LockedView, ListView):
    model = Entry
    queryset = Entry.objects.all().order_by("-created_date")

class EntryDetailView(LockedView, DetailView):
    model = Entry

class EntryCreateView(LockedView, SuccessMessageMixin, CreateView):
    model = Entry
    fields = ["title", "content"]
    success_url = reverse_lazy("entry-list")
    success_message = "Your new entry was created!"

class EntryUpdateView(LockedView, SuccessMessageMixin, UpdateView):
    model = Entry
    fields = ["title", "content"]
    success_message = "Your entry was updated!"

    def get_success_url(self):
        return reverse_lazy("entry-detail", kwargs={"pk": self.object.pk})

class EntryDeleteView(LockedView, SuccessMessageMixin, DeleteView):
    model = Entry
    success_url = reverse_lazy("entry-list")
    success_message = "Your entry was deleted!"

    def delete(self, request, *args, **kwargs):
        messages.success(self.request, self.success_message)
        return super().delete(request, *args, **kwargs)

Now when you visit http://localhost:8000, you’ll be redirected to the Django login form. After logging in, you’ll be redirected to the entries list and see everything that is for your eyes only:

Django Diary Final Entry

And that’s it! You’ve successfully built your very own personal diary in Django. If you want to compare your code with the final code of the project, then click the link below:

The complete code for the project is in the source_code_step_final/ directory.

Next Steps

The diary you built is fully functional and ready to use. But it is also an excellent foundation to build upon. Maybe you already have some ideas as to how you can improve your diary even more. Or you can try out one of the ideas below:

  • Add a page for your most recent entries.
  • Give every weekday a color.
  • Show the creation date in the HTML <title> tag.
  • Create an emoji of the day selection for an entry.
  • Paginate the list of entries.

Throughout this tutorial, you were introduced to some base concepts and patterns that Django provides. Hopefully, it sparked some interest to dive deeper into this Python web framework. If you want to continue your journey and learn more, you can check out these tutorials:

You can apply the knowledge you gained by completing your diary project to other Django tutorials and take your web app skills to the next level!

Conclusion

In this tutorial, you created a personal diary from scratch using Django. You learned how to build a full-featured web application that you can use day to day. It takes advantage of Django’s many strengths, including the admin site, class-based views, the message framework, and the templating system.

This Django project is also a perfect basis for your future Django projects. The steps you finished in this diary project are basically the same for other projects, like a blog or a to-do app.

In this tutorial, you learned how to:

  • Set up a Django project
  • Work with the standard SQLite database
  • Make use of the Django admin site
  • Create models and class-based views
  • Nest and style templates
  • Secure your diary with authentication

You can download the complete source code for the Django diary by clicking the link below:

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About Philipp Acsany

Philipp is a Berlin-based software engineer with a graphic design background and a passion for full-stack web development.

» More about Philipp

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning