Learn Django by Building Category Manager

In this post, we will continue to learn Django by building a simple category manager application. This will give you a generalized view of how Django works as a whole.

Introduction

In this category manager app, we will have the following views

  • index view – displays all the categories in the table.
  • detail view – permalink page for a single entry.
  • add category action – handles posting category.
  • edit category action – handle edit of existing category.
  • delete category – delete a category

In Django, web pages are delivered by views. Each view is represented by a Python function (or method, in the case of class-based views).

DecisionMentor app

For the sake of this tutorial we will use function-based views. Django maps the respective views based on the URL.

Setup Django Project (Category Manager)

Let’s start by creating a new project mysite. If you do not have prior knowledge of setting up a new project please follow my previous post i.e. Getting Started with Django.

Step 1: Create a new project

#terminal:
(myenv)$ python manage.py startapp categories

Step 2: Setup a database and a model

Add, categories in INSTALLED_APPS in setting.py to migrate the tables in database

#settings.py
...
INSTALLED_APPS = [
    ...
    'categories',
]
...

Note: if you are using MySQL or Postgres, change database attributes in setting.py.

#categories/models.py
from django.db import models

class Categories(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField()

    def __str__(self):
        return self.name

Each class in models.py represents a table. Each field is specified as a class attribute, and each attribute maps to a database column.

#terminal
(myenv)$ python manage.py makemigrations
(myenv)$ python manage.py migrate
database migrations

Step 3: Admin setup

Create superuser

#terminal
$ python manage.py createsuperuser

Add a username and password to login admin panel via http://127.0.0.0:8000/admin

Register model in Admin panel

#categories/admin.py
from django.contrib import admin
from .models import Categories

admin.site.register(Categories)  #register categories in admin panel

This will provide the basic CRUD functionality in the admin panel. (Add few categories)

CRUD in admin panel

Step 4: Create a template for general user

First, create a directory called templates in your categories directory. Django will look for templates in there.

Within the templates directory that you have just created, create another directory called categories. Inside this folder, that create a file called index.html.

In other words, your template should be at categories/templates/categories/index.html.

Note: Your project’s TEMPLATES settings describe how Django will load and render templates.

The default settings file configures a DjangoTemplates backend whose APP_DIRS option is set to True.

By convention DjangoTemplates looks for a “templates” subdirectory in each of the INSTALLED_APPS.

This app will contain four templates: index.html, detail.html, add.html and edit.html as:

#categories/index.htm
<!DOCTYPE html>
<html>
   <head><title>index</title></head> 
   <style>
        table{
            border: 2px solid black;
            border-collapse:  collapse;
            width: 50%;
        }
        th, td {
            border: 1px solid black;
            padding: 5px;
            text-align: center;
        }
        th {
            background-color: lightgrey;
        }
   </style>
   <body>
        <h1>List of Categories</h1>
        <a href="{% url "categories:add" %}">Add</a> <br/><br/>
        <table>
            <thead>
                <tr>
                <th>Name</th>
                <th>View</th>
                <th>Edit</th>
                <th>Delete</th>
                </tr>
            </thead>
            <tbody>
                {% for category in all_categories %}
                <tr>
                <td>{{ category.name }}</td>
                <td><a href="{% url "categories:detail" category.id %}">view</a></td>
                <td><a href="{% url "categories:edit" category.id %}">edit</a></td>
                <td><a href="{% url "categories:delete" category.id %}">delete</a></td>
                </tr>
                {% endfor %}
            </tbody>
            </table>      
    </body>
   </html>
#categories/detail.html
<h2>{{ category.name }}</h2>
<p>&emsp;{{ category.description }}</p>
#categories/add.html
<h2>New Category</h2>
    <form method="POST">{% csrf_token %}
        <table>
            {{ form.as_table }}
        </table>
        
        <button type="submit">Save</button>
    </form>
#categories/edit.html
<h2>Edit Category</h2>
    <form method="POST">{% csrf_token %}
        <table>
            {{ form.as_table }}
        </table>
        
        <button type="submit">Save</button>
    </form>

The add.html and edit.html files contain forms for which modelForms in used.

Step 5: Add a forms.py file

Add a forms.py file inside categories application to render a model form in template.

#forms.py
from django.forms import ModelForm
from .models import Categories

class CategoryForm(ModelForm):
    class Meta:
        model = Categories
        fields = ('name', 'description',)

It will generate a form tags with respective name in templates

Step 6: Write views for respective functionalities.

Each view is responsible for returning an HttpResponse object containing the content for the requested page, or raising an exception such as Http404

#categories/views.py
from django.shortcuts import get_object_or_404, render
from django.shortcuts import redirect

from .models import Categories
from .forms import CategoryForm

def index(request):
    all_categories = Categories.objects.all()    #retrive all categories
    context = {'all_categories': all_categories}
    return render(request, 'categories/index.html', context)


def detail_category(request, pk):
    category = get_object_or_404(Categories, pk=pk)   #retrive single category
    return render(request, 'categories/detail.html', {'category': category})

#create   
def new_category(request):
    if request.method == "POST":
        form = CategoryForm(request.POST)
        if form.is_valid():
            category = form.save(commit=False)
            category.save()
            return redirect('categories:detail', pk=category.pk)
    else:
        form = CategoryForm()
    return render(request, 'categories/add.html', {'form': form})

#update
def edit_category(request, pk):
    category = get_object_or_404(Categories, pk=pk)
    if request.method == "POST":
        form = CategoryForm(request.POST, instance=category)
        if form.is_valid():
            category = form.save(commit=False)
            category.save()
            return redirect('categories:detail', pk=category.pk)
    else:
        form = CategoryForm(instance=category)
    return render(request, 'categories/edit.html', {'form': form})

#delete
def delete_category(request, pk):
    category = get_object_or_404(Categories, pk=pk)
    category.delete()
    return redirect('categories:index')

In views.py, index(request) function retrieves all the categories and renders those categories in index.html template. Similarly, detail(request, pk) retrieves only a single category which is mapped from urls.py and render the category in detail.html template.

new_category(request) uses a modelform to render the form in the add.html template and save the user data in the database. Similarly, edit_category(request, pk) uses the modelform to render edit.html template and edit a specific category which is mapped from URL.

delete(request, pk) function deletes a category from the database.

step 7: write URLs to maps respective views functions.

add a new file called urls.py in categories application directory

#categories/urls.py
from django.urls import path

from . import views

app_name = 'categories'
urlpatterns = [
    path('', views.index, name='index'),
    path('add', views.new_category, name='add'),
    path('<int:pk>/', views.detail_category, name='detail'),
    path('edit/<int:pk>/', views.edit_category, name='edit'),
    path('delete/<int:pk>', views.delete_category, name='delete'),
]

Note: these path maps the urls to their respective views.

Also include the app URLs in project URLs

#mysite/urls.py
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('category/', include('categories.urls'))
]

Now you can view the existing categories via http://127.0.0.1:8000/category/ by starting development server

#terminal
(myenv)$ python manage.py runserver
index page of category  application
http://127.0.0.1:8000/category/
(index.html)

Wrapping Up

In this post, we created a simple category manager where we learned CRUD operations i.e. Create, Retrieve, Update, and Delete in Django.

In the next post, we will add functionality to disable/hide categories from public view in this application.