📘 Django App Guide 4: Add 2nd Table (Categories) & Include Partial Template Globally
This guide shows how to add a second table to your Django project by copying the structure of an existing table (in this case, UOM) and modifying it to create a new, independent app (Categories). The process can be repeated as many times as needed to add more tables without affecting existing apps.
It also demonstrates how to include a partial template globally using Django's {% include %} tag, allowing you to reuse common interface elements like table selectors, buttons, or headers across multiple apps, ensuring a consistent look and reducing duplication in your templates.
By the end of this guide, you will be able to:
- ✅ Duplicate and adapt an existing CRUD app for a new table.
- ✅ Maintain independent routes, forms, and views for each table.
- ✅ Include reusable template parts for shared UI components.
- ✅ Handle automatic timestamps (
created_atandupdated_at) for new records. - ✅ Keep full control over your database without creating new tables manually.
(Using models.py, forms.py, existing SQLite table stock_items_categories, and reusable partial templates)
This guide shows how to build a Django app that:
- ✅ Displays all
stock*tables for reference (read-only) - ✅ CRUD for
stock_items_categories - ✅ Uses separate routes for Add / Update
- ✅ Auto-saves
created_atandupdated_at - ✅ Uses a
selectfor Status - ✅ Includes a reusable partial template (
_table_select.html) globally - ❌ Does NOT create or modify existing tables
- ❌ Does NOT create a new database — SQLite database is provided
This guide continues from Guide 3 and focuses on adding the Categories app and using partial templates for code reuse.
📁 Project Structure
project_folder/
├── manage.py
│
├── core/
│ ├── settings.py
│ └── urls.py
│
├── apps/
│ ├── uom/
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── urls.py
│ │ ├── forms.py
│ │ └── templates/uom/
│ │ ├── index.html
│ │ └── form.html
│ │
│ └── categories/
│ ├── models.py
│ ├── views.py
│ ├── urls.py
│ ├── forms.py
│ └── templates/categories/
│ ├── index.html
│ └── form.html
│
├── templates/
│ ├── base.html
│ └── includes/
│ └── _table_select.html # Partial template for table selection
└── db.sqlite3
⚙️ 1. Configure SQLite in settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
✔ No migrations needed for existing tables.
🧩 2. Model for stock_items_categories
apps/categories/models.py
from django.db import models
class StockItemCategories(models.Model):
STATUS_CHOICES = [
(1, 'Active'),
(2, 'Inactive'),
]
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255, unique=True)
description = models.TextField(null=True, blank=True)
status = models.IntegerField(choices=STATUS_CHOICES, default=1)
created_at = models.DateTimeField()
updated_at = models.DateTimeField()
class Meta:
db_table = 'stock_items_categories'
managed = False # Do NOT let Django alter the table
def __str__(self):
return self.name
📝 3. Form for Add/Update
apps/categories/forms.py
from django import forms
from .models import StockItemCategories
class StockItemCategoriesForm(forms.ModelForm):
STATUS_CHOICES = [
(1, 'Active'),
(2, 'Inactive'),
]
status = forms.ChoiceField(
choices=STATUS_CHOICES,
widget=forms.Select(attrs={'class': 'form-select'})
)
class Meta:
model = StockItemCategories
fields = ['name', 'description', 'status'] # created_at / updated_at excluded
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.TextInput(attrs={'class': 'form-control'}),
}
🖥️ 4. Views
apps/categories/views.py
import sqlite3
from django.conf import settings
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
from .models import StockItemCategories
from .forms import StockItemCategoriesForm
DB_PATH = settings.BASE_DIR / 'db.sqlite3'
def index(request):
# List all stock* tables for display only
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'stock%'")
tables = [row[0] for row in cursor.fetchall()]
conn.close()
records = StockItemCategories.objects.all().order_by('id')
return render(request, 'categories/index.html', {
'title': 'Categories List - Database db.sqlite3 / SQLite Tables',
'records': records,
'tables': tables,
'current_table': 'stock_items_categories',
})
def add_record(request):
if request.method == 'POST':
form = StockItemCategoriesForm(request.POST)
if form.is_valid():
record = form.save(commit=False)
now = timezone.now()
record.created_at = now
record.updated_at = now
record.save()
return redirect('categories:index')
else:
form = StockItemCategoriesForm()
return render(request, 'categories/form.html', {
'title': 'Add Category',
'form': form,
})
def update_record(request, pk):
record = get_object_or_404(StockItemCategories, pk=pk)
if request.method == 'POST':
form = StockItemCategoriesForm(request.POST, instance=record)
if form.is_valid():
updated = form.save(commit=False)
updated.updated_at = timezone.now()
updated.save()
return redirect('categories:index')
else:
form = StockItemCategoriesForm(instance=record)
return render(request, 'categories/form.html', {
'title': f'Update Category ID {record.id}',
'form': form,
})
def delete_record(request, pk):
record = get_object_or_404(StockItemCategories, pk=pk)
record.delete()
return redirect('categories:index')
🌐 5. URLs
apps/categories/urls.py
from django.urls import path
from . import views
app_name = 'categories'
urlpatterns = [
path('', views.index, name='index'),
path('add/', views.add_record, name='add'),
path('update/<int:pk>/', views.update_record, name='update'),
path('delete/<int:pk>/', views.delete_record, name='delete'),
]
Root core/urls.py:
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('apps.uom.urls')), # Home app
path('categories/', include('apps.categories.urls')), # Categories app
]
if settings.DEBUG:
import debug_toolbar
urlpatterns = [path('__debug__/', include(debug_toolbar.urls))] + urlpatterns
🖌️ 6. Templates
Partial Template — _table_select.html
<select class="form-select form-select-sm" id="tableSelect" style="width:auto; min-width:150px;">
{% for table in tables %}
<option value="{{ table }}" {% if table == current_table %}selected{% endif %}>{{ table }}</option>
{% endfor %}
</select>
Index — apps/categories/templates/categories/index.html
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="card shadow-lg p-4 mb-3">
<h4 class="mb-4 text-center">{{ title }}</h4>
<!-- Table selection + Add button -->
<div class="mb-3 d-flex flex-wrap align-items-center gap-2">
<label for="tableSelect" class="form-label mb-0">Table:</label>
{% include 'includes/_table_select.html' %}
<a href="{% url 'categories:add' %}" class="btn btn-success btn-sm" style="position: absolute; right: 25px;">Add</a>
</div>
<!-- Table with Categories records -->
<div class="table-responsive">
<table class="table table-sm table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Description</th>
<th>Status</th>
<th>Created At</th>
<th>Updated At</th>
<th width="120">Actions</th>
</tr>
</thead>
<tbody>
{% for row in records %}
<tr>
<td>{{ row.id }}</td>
<td>{{ row.name }}</td>
<td>{{ row.description }}</td>
<td>{{ row.get_status_display }}</td>
<td>{{ row.created_at }}</td>
<td>{{ row.updated_at }}</td>
<td class="text-nowrap d-flex gap-1">
<a href="{% url 'categories:update' row.id %}" class="btn btn-light btn-sm">Update</a>
<a href="{% url 'categories:delete' row.id %}"
onclick="return confirm('Delete this record?')"
class="btn btn-light btn-sm">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
Form — apps/categories/templates/categories/form.html
{% extends 'base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="card shadow-lg p-4 mb-3">
<h3 class="mb-4">{{ title }}</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary btn-sm">Save</button>
<a href="{% url 'categories:index' %}" class="btn btn-secondary btn-sm">Cancel</a>
</form>
</div>
{% endblock %}
🎉 Done!
- Full CRUD for
stock_items_categories - Timestamps
created_atandupdated_atdisplayed, automatically added on creation - Status dropdown selection
- Global partial template
_table_select.htmlreused for table selector - Add / Update forms exclude timestamps
- Delete inline with confirmation
- Existing SQLite DB only — no migrations needed