Creating the Application

To start, we will create a virtual environment for our project and install the required libraries

mkdir DjangoNotes
cd DjangoNotes
python3 -m venv venv
source venv/bin/activate

pip install -r requirements.txt

Now that we have our environment setup we can create out Django application.

django-admin startproject DjangoNotes
python manage.py startapp notes

Whilst we are still doing some admin setup, we need to initialise our database and create ourselves a superuser.

python manage.py makemigrations notes
python manage.py migrate
python manage.py createsuperuser

We should no be able to log in to the admin interface that Django has made for us. If you run the server python manage.py runserver, and navigate your web browser to http://127.0.0.1:8000/admin. If you login with you username and password that you use when creating your super user, you will be dumped onto a pretty sparse looking page. Theres a couple of things you can do here so feel free to have a look around. You can add and remove users as well as create and manage different groups.

Firstly, we need to add all the installed apps to the Django settings file. Open up the settings.py file located inside the DjangoNotes directory. Scroll down till you see the INSTALLED_APPS variable and add the following to it:

INSTALLED_APPS = [
    [ ... ]
    'notes',
    'taggit',
    'taggit_serializer',
    'rest_framework',
    'crispy_forms'
]

Now are apps have been enabled there is some configuration we need to do before we can access/use them. We first need to create a model for our note app to use, otherwise there will be nowhere to store note data. Models allow us to access a database in an object orientated way, with returned rows being accessed as object. We need to edit the models.py file of our note app notes/models.py. Open up the file and add the following content to it.

from django.contrib.auth.models import User
from django.db import models
from django.urls import reverse
from django.http import request
from django.template.defaultfilters import slugify
from django.utils import timezone
from taggit.managers import TaggableManager


# Create your models here.
class Note(models.Model):
    """ Defins how notes are stored in the database """
    
    # Note Title
    title = models.CharField(max_length=250)
    # Note title (URL friendly)
    slug = models.SlugField(max_length=250, unique=True, editable=False)
    # The user that wrote the note. Foreign key to the users table
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="notes", null=True, editable=False)
    # Content of the note
    body = models.TextField()
    # WHen was the note created, autofilled to now
    created = models.DateTimeField(default=timezone.now, editable=False)
    # When was the note last modified, autofilled to whenever the note is updated
    modified = models.DateTimeField(auto_now=True)

    # Allows us to access all the objects in the Note database
    objects = models.Manager()
    # Tag object that allows us to tag notes
    tags = TaggableManager(blank=True)

    def __str__(self):
        """ Override how the note is printed so that we get a more helpful representation of the note """
        return "<Note {}>".format(self.title)

    def get_absolute_url(self):
        """ Returns the URL of this note for the "detail.html" file """
        return reverse('note:note_detail', args=[self.slug])

    def save(self, *args, **kwargs):
        """ When we save the note, create a slug from the title of the note """
        self.slug = slugify(self.title)
        super(Note, self).save(*args, **kwargs)

As we have just made a change that affects our database we need to update it. Run the following commands to update the database:

python manage.py makemigrations notes
python manage.py migrate

We now have a model for our note app, so we can add it to our admin interface. This means that when we log in to the admin site, we will be able to manage the note database using Djangos pre-made interface. Open up the admin.py file and add the following lines to it.

from .models import Note

@admin.register(Note)
class NoteAdmin(admin.ModelAdmin):
    list_display = ("title", "body")
    list_filter = ("created", "modified", "author")
    search_fields = ("title", "body")
    raw_id_fields = ("author", )
    ordering = ("created", )

    def save_model(self, request, obj, form, change):
        if getattr(obj, 'author', None) is None:
            obj.author = request.user
        obj.save()

This creates a form that allows us to add new noes to the database. I will briefly explain the following lines in the above class:

  • list_diplay: When we create/edit notes the fields that we see will the the Title and Body fields of our note.
  • list_filter: When we view the available notes in the database we can order them by when the note was created/modified and who created the note
  • search_fields: This defines how notes are searched for and which fields to search in
  • raw_id_fields: This tells the interface to use the logged in users ID for the author field
  • ordering: When we view the notes they will be ordered by when they were created
  • save_model function: When a note is saved the author field is set to the currently logged in user if it is None. Otherwise it is left as is.

I suggest at this point you add a bunch of notes and have a play around with all the sorting options etc. just to get a feel of how the admin interface works and how it links to the fields we specified above


So we have some notes stored within our application but we need a way to view them. We will create 2 different views for our website. One will list all the notes in the database, with their respective author and tags and the other view will give us the content of the note. I am using bootstrap to make the interface look nice but feel free to use whatever CSS you like. We will be using templates to render the files so go ahead and create all the following files inside the notes/ folder.

templates/
|  - base.html
|  - pagination.html
|  - notes/
|  |  - note/
|  |  |  - detailed.html
|  |  |  - list.html
|  - registration/
|  |  - login.html

I won’t go into all the details of the templating engine that Django uses but you can read more about it here. To start with we will only add content to some of the files with the others being explained later on. Firstly, open up base.html and copy in the below code. This will be the file that all our other files extend from as it includes the relevant scripts and stylesheets that all our pages need.

{% load static %}
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}{% endblock %}</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
</head>
<body>
    {% block content %}

    {% endblock %}
</body>
</html>

Now we can create our other 2 views for the site, list and detail. Copy the following into list.html

{% extends "base.html" %}
{% block title %}My Notes{% endblock %}
{% block content %}
    <div class="container">
        <div class="pb-2 mt-4 mb-2 border-bottom">
            <h3 class="w-100">
                <a href="/">My Notes</a>
                <a href="{% url "logout" %}" class="btn btn-danger btn-sm float-right">Logout</a>
            </h3>
        </div>
        {% if tag %}
            <div class="alert alert-info" role="alert">
                Posts tagged with "{{ tag.name }}"
            </div>
        {% endif %}
        <div class="list-group">
            {% for note in notes %}
                <a href="{{ note.get_absolute_url }}" class="list-group-item list-group-item-action flex-column align-items-start">
                    <div class="d-flex w-100 justify-content-between">
                        <h5 class="mb-1">{{ note.title }}</h5>
                        <small class="text-muted">{{ note.created }}</small>
                    </div>
                    <p class="mb-1">
                        {{ note.body|truncatewords:30|linebreaks }}
                    </p>
                    <small>Published by {{ note.author }}</small>
                    <div class="tags" style="display: inline-block">
                        {% for tag in note.tags.all %}
                            <form style="display: inline-block" action="{% url "note:notes_list_by_tag" tag.slug %}">
                                <button type="submit" class="btn btn-sm btn-secondary"> {{ tag.name }}</button>
                            </form>
                        {% endfor %}
                    </div>
                </a>
            {% empty %}
                <div class="alert alert-danger" role="alert">
                    No notes found
                </div>
            {% endfor %}
        </div>

</div>
{% endblock %}

Copy the following into detail.html:

{% extends "base.html" %}


{% block title %}{{ note.title }}{% endblock %}
{% block content %}
    <div class="container">
        <div class="pb-2 mt-4 mb-2 border-bottom">
            <h3 class="w-100">
                <a href="/">My Notes</a>
                <a href="{% url "logout" %}" class="btn btn-danger btn-sm float-right">Logout</a>
            </h3>
        </div>
        <div class="card">
            <div class="card-header">
                {{ note.title }}
                <span class="float-right">{{ note.created }}</span>
            </div>
            <div class="card-body">
                {{ note.body|linebreaks }}
                <hr />
                <h5>
                    Tags:
                    {% for tag in note.tags.all %}
                        <a href="{% url "notes:notes_list_by_tag" tag.slug %}">{{ tag }}</a>
                        {% if not forloop.last %}, {% endif %}
                    {% endfor %}
                </h5>
            </div>
            <div class="card-footer text-muted">
                Created by {{ note.author }}
            </div>
        </div>
    </div>
{% endblock %}

We now have two ways to render our notes but we have no way to access these. This is because we haven’t added any URLs to our application. First off, we need to create the two views, so open up the views.py file inside the notes folder and add the following:

from django.shortcuts import render, get_object_or_404
from taggit.models import Tag
from .models import Note

def note_detail(request, note):
    note = get_object_or_404(
        Note, slug=note
    )
    return render(request, "notes/note/detail.html", {"note": note})

def note_list(request):
    all_notes = Note.objects.all().order_by("created")
    return render(
        request,
        "notes/note/list.html",
        {"notes": all_notes}
    )

We just created the views now we can add URLs that point to these views. Open up the file urls.py in the notes/ directory and add the following to it. This creates 3 URLs for our application. The default path, or “”, will direct us to the list of all the notes contained within our application. The second, “tag/slug:tag_slug”, is very similar to the first except it will only show posts that have the specified tag in them. Lastly, we have the detailed view. This view is the notes slug and renders using the note_detail function defined in our views file.

from django.urls import path
from . import views

app_name = "notes"

urlpatterns = [
    path("", views.note_list, name="note_list"),
    path("tag/<slug:tag_slug>/", views.note_list, name="notes_list_by_tag"),
    path("<slug:note>/", views.note_detail, name="note_detail")
]

Now add the applications URLs to the Django project so open up the urls.py file located in DjangoNotes/ and add the following to the urlpatterns variable. This will map the URLs that we just defined onto “/” of our website. You may need to add include to the list of imports from django.urls.

path('', include('notes.urls', namespace='note'))

If you run the web server again and navigate to “/” you should see something akin to this. When you click on the note, you should see the detailed view.

Django Notes List View

Django Notes Detail View