DEV Community

Cover image for Build a Custom Functional Django Project
chryzcode
chryzcode

Posted on

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
Enter fullscreen mode Exit fullscreen mode

Installing Django

pipenv install django
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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')),
]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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

      {% load static %}
      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="UTF-8" />
          <meta http-equiv="X-UA-Compatible" content="IE=edge" />
          <meta name="viewport" content="width=device-width, initial-scale=1.0" />
          <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet" />
          <link
            href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
            rel="stylesheet"
            integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
            crossorigin="anonymous"
          />
          <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
          <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
          <link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet" />
          <link rel="stylesheet" href="{% static 'css/styles.css' %}" />
         <title>{% block title %}My Note App{% endblock %}</title>
        </head>
        <body>
          <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container-fluid">
          <span class="navbar-brand" style="color: orange;">&#9782; My Note App</span>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarSupportedContent">
      
          {% if user.is_authenticated %}
      
            <ul class="navbar-nav me-auto mb-2 mb-lg-0"">
              <li class="nav-item"><a class="nav-link" href="{% url 'add-note' %}">Add a note</a></li>
            </ul>
            {% else %}
            <ul class="navbar-nav me-auto mb-2 mb-lg-0"">
              <li class="nav-item"><a class="nav-link" href="{% url 'add-note' %}">Add a note</a></li>
            </ul>
      {% endif %}
      
        {% if user.is_authenticated %}
        <div class="btn-group">
          <ul class="nav justify-content-end">
            <li class="nav-item dropdown" >
              <a style="color: orange;" class="nav-link dropdown-toggle" class="nav-link" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                {{ request.user }}
              </a>
              <ul class="dropdown-menu bg-dark" aria-labelledby="navbarDropdown">
                <li class="nav-item"><a style="color: orange;" class="nav-link" href="{% url 'logout' %}">Log out</a></li>
                <li><a style="color: orange;" class="dropdown-item" href="{% url 'delete-user' %}">Delete acc</a></li>
            </ul>
        </div>
        {% endif %}
          </div>
      </nav>
      
          <div class="container">{% block content %} {% endblock %}
            <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
          </div>
        </body>
      </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.

Top comments (0)