Build a Custom Functional Django Project
Tags: webdev, tutorial, python, django
Published: 2023-11-13 07:55:28

### 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.

```python
#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
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699702826904/d13901f8-d48c-4550-991a-3b459b33dd7d.png align="center")

### Installing Django

```python
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

```python
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

```python
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
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
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
python manage.py createsuperuser
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699704982005/bf282f15-4584-4acf-b7fa-95ff6dc2465a.png align="center")

### 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.

```python
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/](http://127.0.0.1:8000/admin/). You can only log in with a superuser account.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699705260323/0248d6d5-c93d-430a-b5db-92c95ac268b9.png align="center")

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`
    
    ```python
    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`**)**
    
    ```python
    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**
        
        ```python
        #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.
        
        ```python
        #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.
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1699756083020/3006f144-8f1b-4470-bc6e-17b07c1ba26b.png align="center")
    
* **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`.
                
                ```css
                * {
                  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
            
            ```xml
            {% 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`
        
        ```xml
        {% 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
        
        ```html
        {% 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`
        
        ```xml
        {% 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`
        
        ```xml
        {% 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`
        
        ```xml
        {% 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.**