Build a Custom Functional Django Project

Photo by Alex Chumak on Unsplash

Build a Custom Functional Django Project

Introduction

In this article, we will build a note project to cover the major scope and aspects of building real-world projects using Django.

Create a virtual environment

A virtual environment is a tool that helps store dependencies for a specific Django application, not on a global scope on your laptop or computer.

#if you have python installed properly, pip should be available automatically
#pip is a python package manager/ installer
pipenv shell
#the command above should create a virtual environment if ran on your terminal in the project path

Installing Django

pipenv install django

After successfully running the command above a Pipfile and Pipefile.lock file will be created automatically.

  • Pipfile

    Pipfile is the file used by the Pipenv virtual environment to manage project dependencies and keep track of each dependency version.

  • Pipfile.lock

    Pipfile.lock leverages the security of package hash validation in pip. This guarantees you're installing the same packages on any network as the one where the lock file was last updated, even on untrusted networks.

Create note models

from django.db import models
#use django user user model
from django.contrib.auth.models import User

# Create your models here.
#Note model
class Note(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    body = models.TextField(null=True, blank=True)
    updated = models.DateTimeField(auto_now=True)
    created = models.DateTimeField(auto_now_add=True)

    # ordering notes in list view with the just created or updated ones
    class Meta:
        ordering = ['-updated', '-created']

    # if a note object is called 50 characters of the title will shown
    def __str__(self):
        return self.title[0:50]

Add Django auth URLs

Since we will not be creating custom views authentication add the code below to your project my_project URL file

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


urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('my_app.urls')),
#Django auth default url
    path('', include('django.contrib.auth.urls')),
]

Create migration

The command below creates a file which will have a database schema based on the models in the models.py file in the migration folder.

python manage.py makemigrations

#This should be the response diplayed after running it on the terminal
#Migrations for 'my_app':
  #my_app\migrations\0001_initial.py
    #- Create model Note

Create database

To create the project database run the command below. This command also creates necessary tables like notes and schemas like user , sessions etc.

python manage.py migrate

#and you can run the server
python manage.py runserver

Create a superuser

In a Django project, there are superusers and normal users and the major difference between them is that superusers have access and permission to the project admin page to manage the project's models and data.

python manage.py createsuperuser

Add notes model to Django admin

To access your notes models and play around it using the Create, Read, Update, Delete (crud) functions on the Django admin, the notes model needs to be registered on the admin.py file.

from django.contrib import admin
from .models import Note

# Register your models here.
admin.site.register(Note)

To have a view of the Django admin page make sure the server is up and running and open this link on the browser http://127.0.0.1:8000/admin/. You can only log in with a superuser account.

The CRUD functions can now be done on the Notes models.

Create custom application interfaces and functions

Instead of using the Django admin page, a custom interface and functions for the project will be created.

  • Create forms(forms.py)

    The file forms.py will be created customarily in the application folder my_app

      from django import forms
      from .models import Note
    
      #create a form based on the Note model
      class noteForm(forms.ModelForm):
          class Meta:
              model = Note
              #form fields/inputs that will be shown
              fields = ['title', 'body']
              #added a css class and html placeholder to the form inputs
              #the form-control css class is from bootstrap
              widgets={
                      'title':forms.TextInput(attrs={'class':'form-control', 'placeholder':'Note title'}),
                      'body':forms.Textarea(attrs={'class':'form-control',  'placeholder':'Start writing...'}),
              }
    
  • Create the project functions(views.py)

      from django.shortcuts import render, redirect
      from .models import Note
      #import the noteform
      from .forms import noteForm
      from django.contrib.auth.decorators import login_required
      from django.contrib.auth.models import User
    
      # Create your views here.
    
      #login_required makes sure a user is authenticated before a view
    
      #get all the notes of the currently logged in user
      @login_required(login_url='login')
      def myNotesList(request):
          context = {}
          notes = Note.objects.filter(user=request.user)
          context['notes'] = notes
          return render(request, 'note-list.html', context)
    
      #add a new note
      @login_required(login_url='login')
      def addNote(request):
          context = {}
          form = noteForm
          if request.method == 'POST':
              note = Note.objects.create(
                  user = request.user,
                  title = request.POST.get('title'),
                  body = request.POST.get('body')
              )
              return redirect('view-note', pk=note.pk)
          context['form'] = form
          return render(request, 'add-note.html', context)
    
      #edit a specific note
      @login_required(login_url='login')
      def editNote(request, pk):
          note = Note.objects.get(pk = pk)
          if request.user.id == note.user.id:
              form = noteForm(instance=note)
              if request.method == 'POST':
                  form = noteForm(request.POST, instance=note)
                  if form.is_valid():
                      form.save()
                      return redirect('view-note', pk=note.pk)
              context = {'form':form}
              return render(request, 'edit-note.html', context)
          return redirect('notes-list')
    
      #view a specific note
      @login_required(login_url='login')
      def viewNote(request, pk):
          context = {}
          note = Note.objects.get(pk = pk)
          if request.user.id == note.user.id:
              context['note'] = note
              return render(request, 'view-note.html', context)
          return redirect('notes-list')
    
      #delete a specific note
      @login_required(login_url='login')
      def deleteNote(request, pk):
          note = Note.objects.get(pk=pk)
          if request.user.id == note.user.id:
              note.delete()
              return redirect('notes-list')
          return redirect('notes-list')
    
      ##delete the logged in user account
      @login_required(login_url='login')
      def deleteUser(request):
          user = request.user
          user.delete()
          return redirect('login')
    
    • Create URLs patterns

        #urls.py
        from django.urls import path
        from . import views
      
        urlpatterns =[
            path('', views.myNotesList, name='notes-list'),
            path('add-note/', views.addNote, name='add-note'),
            path('edit-note/<str:pk>/', views.editNote, name='edit-note'),
            path('view-note/<str:pk>/', views.viewNote, name='view-note'),
            path('delete-note/<str:pk>/', views.deleteNote, name='delete-note'),
            path('delete-user/', views.deleteUser, name='delete-user'),
        ]
      

      Add the Login and Logout Redirect URL

      This piece of code is to be in the settings.py and its purpose is to redirect a user to a certain URL/page after logging in or logging out.

        #settings.py
        import os
      
        #where your static files will be located e.g css, ja and images
        STATIC_ROOT = os.path.join(BASE_DIR, 'static')
      
        LOGIN_REDIRECT_URL = "notes-list"
        #we can access the login url through the Django defauly auth urls
        LOGOUT_REDIRECT_URL = "login"
      

Create template and static files(.html and .css)

This should be an overview of how the static and template files should be arranged.

  • Create styles.css file

    This is the file where we write the stylings of the app(CSS).

    • Create a static folder in the application folder my_app

      • Inside the static folder create a css folder

        • Then create the styles.css file inside the css folder.

            * {
              margin: 0;
              padding: 0;
            }
          
            body {
              font-family: "Montserrat", sans-serif;
              background-color: #1f2124;
              color: white;
            }
            .fa.fa-google {
              margin-right: 7px;
            }
          
            .page-header {
              text-align: center;
              font-size: 30px;
              font-weight: 600;
              color: orange;
            }
          
            .login-page-container {
              margin-top: 40px;
            }
          
            .login-container {
              border: 3px solid #252629;
              border-radius: 8px;
              margin-left: auto;
              margin-right: auto;
              margin-top: 50px;
              width: 30%;
              height: 40vh;
            }
          
            .login-container > p {
              text-align: center;
              font-size: 22px;
              margin-top: 15px;
            }
          
            .login-btn {
              margin-left: auto;
              margin-right: auto;
              display: block;
              width: 65%;
              margin-top: 70px;
            }
          
            .login-btn:hover {
              background-color: #252629;
            }
          
            .notes-container {
              margin-left: auto;
              margin-right: auto;
              margin-top: 60px;
              display: block;
              border: 3px solid #252629;
              width: 500px;
              border-radius: 8px;
            }
          
            .note-title {
              margin-left: 15px;
              font-size: 18px;
              padding: 15px;
              color: white;
              text-decoration: none;
            }
          
            .note-title > a {
              margin-left: 15px;
              font-size: 18px;
              color: white;
              text-decoration: none;
            }
          
            .note-title > a:hover {
              margin-left: 10px;
              color: orange;
              text-decoration: none;
            }
          
            .note-header {
              margin-top: 20px;
              padding-left: 30px;
              padding-right: 50px;
              color: orange;
              border-bottom: 3px solid orange;
            }
          
            .note-header > a {
              text-decoration: none;
              color: orange;
            }
          
            .note-logo {
              margin-right: 15px;
            }
          
            .note-count {
              font-size: 19px;
              float: right;
              padding-right: 10px;
            }
          
            .note-created {
              display: flex;
              justify-content: right;
              align-items: center;
              font-size: 10px;
            }
          
            .note-list {
              border-bottom: 1px solid #252629;
              height: 60px;
            }
          
            .note-list:hover {
              background-color: #252629;
            }
          
            #add-note-icon {
              display: flex;
              justify-content: right;
              align-items: center;
              font-size: 30px;
            }
          
            label {
              display: none;
            }
          
            .a-note-title {
              color: orange;
              text-align: center;
              padding: 10px;
            }
          
            .btn.btn-secondary {
              justify-content: right;
              align-items: center;
              background-color: rgba(255, 166, 0, 0.576);
            }
          
            #back-to-notes-list {
              font-size: 30px;
              /* padding: 8px; */
            }
          
            .note-detail-header {
              padding-left: 10px;
              padding-right: 10px;
            }
          
            .icons > a {
              color: white;
              text-decoration: none;
            }
          
            .note-detail-header > a {
              color: white;
              text-decoration: none;
            }
          
            .icons {
              display: flex;
              align-items: center;
              justify-content: right;
            }
          
            #edit-note {
              font-size: 20px;
            }
          
            #delete-note {
              font-size: 20px;
            }
          
            .note-body {
              padding: 10px;
              margin-left: 7px;
              margin-right: 7px;
              display: block;
            }
          
            .markdown-support-note {
              margin-top: 10px;
              color: orange;
              text-align: center;
            }
          
            .dev-info {
              display: flex;
              justify-content: space-between;
              align-items: center;
              margin-top: 100px;
              font-size: 25px;
              color: orange;
            }
          
            .dev-info > span > a {
              text-decoration: underline !important;
              color: orange !important;
            }
          
            .dev-info > a > i {
              font-size: 40px;
              color: orange;
            }
          
            @media all and (min-width: 100px) and (max-width: 600px) {
              .notes-container {
                width: 100%;
              }
          
              .login-container {
                width: 100%;
              }
            }
          
  • Create the custom views template

    Create a template folder in the application folder my_app and

    • Create registration folder

      • Create login.html file

          {% extends 'base.html'%}
          {% load static %}
          <title>{% block title %}Sign In || My Note App{% endblock %}</title>
          {% block content %}
        
          {% if user.is_authenticated %}
          <p></p>
          {% else %}
          <div class="login-page-container">
              <h3 class="page-header" >Sign In</h3>
              <div class="login-container">
                  <p>My Notes App</p>
                  {% if not user.is_authenticated %}
              {% if messages %} {% for message in messages %}
              <div class="text-center alert alert-{{ message.tags }}">{{ message|safe }}</div>
              {% endfor %} {% endif %} {% if form.errors %}
              <p style="font-size: 20px; margin-left: 25px">Your username and password did not match. Please try again.</p>
              {% endif %}
              <form action="" method="post">
                {% csrf_token %}
                <div class="mb-3">
                  <label id="login-label" for="username" class="form-label">Username:</label>
                  <input id="login-form" type="text" class="form-control" name="username" id="username" placeholder="Username" />
                </div>
                <div class="mb-3">
                  <label id="login-label" for="password" class="form-label">Password:</label>
                  <input
                    id="login-form"
                    type="password"
                    class="form-control"
                    name="password"
                    id="password"
                    placeholder="Password"
                  />
                </div>
                <div id="login-btn">
                  <input class="btn btn-secondary" type="submit" value="Login" />
                </div>
              </form>
              <h5 style="text-align: center; bottom: -50px; position: relative;">
                Yet to have an account? <a href="register/">Sign up</a>
              </h5>
              {% else %}
              <br /><br />
              <p style="font-size: 30px; text-align: center; font-weight: 600">You are already logged in.</p>
              {% endif %}
              </div>
          </div>
          <p class="markdown-support-note">markdown supported</p>
          {% endif %}
          {% endblock %}
        
    • Create add-note.html

        {% extends 'base.html' %}
        <title>{% block title %}Add note || My Note App{% endblock %}</title>
        {% block content %}
      
        <div class="notes-container">
          <h4 class="a-note-title">Add a note</h4>
          <form method="POST">
            {% csrf_token %} {{ form.as_p }}
            <button class="btn btn-secondary sm" type="submit">save</button>
          </form>
        </div>
      
        {% endblock %}
      
    • Create base.html

      This file contains the <head>base.html</head> and the nav of all the template pages. Get the code from this link base.html

    • Create edit-note.html

        {% extends 'base.html' %}
        <title>{% block title %}Edit note|| My Note App{% endblock %}</title>
        {% block content %}
      
        <div class="notes-container">
            <h4 class="a-note-title" >Edit note</h4>
            <form method="POST">
            {% csrf_token %}
            {{ form.as_p }}
            <button class="btn btn-secondary sm" type="submit">save</button>
        </form>
        </div>
      
        {% endblock %}
      
    • Create note-list.html

        {% extends 'base.html' %}
        <title>{% block title %}Note list || My Note App{% endblock %}</title>
        {% block content %}
        <div class="notes-container">
          <div class="note-header">
            <h5>
              <span class="note-logo">&#9782;</span>{{request.user}}'s note
              <span class="note-count">{{notes.count}}</span>
            </h5>
            <a href="{% url 'add-note' %}"><span id="add-note-icon" class="material-icons">add_circle</span></a>
          </div>
          {% for note in notes %}
          <div class="note-list">
            <div class="note-title">
              <a href="{% url 'view-note' note.pk %}">{{ note }}</a>
              <small class="note-created">{{note.created}}</small>
            </div>
          </div>
      
          {% endfor %}
        </div>
        {% endblock %}
      
    • Create view-note.html

        {% extends 'base.html' %}
        <title>{% block title %}Note || My Note App{% endblock %}</title>
        {% block content %}
        <div class="notes-container">
          <div class="note-detail-header">
            <a href="{% url 'notes-list' %}"><span id="back-to-notes-list" class="material-icons">chevron_left</span></a>
            <span class="icons">
              <a href="{% url 'edit-note' note.pk%}"><span id="edit-note" class="material-icons">edit</span></a>
              <a href="{% url 'delete-note' note.pk%}"><span id="delete-note" class="material-icons"> delete </span></a>
            </span>
          </div>
          <h4 class="a-note-title">{{note}}</h4>
          <div class="note-body">{{ note.body}}</div>
        </div>
      
        {% endblock %}
      

Our note application should be up, running and functional. I hope you enjoyed reading this article and building along side.