π Django - Guide 5: Add Items Table with Foreign Keys
This guide demonstrates how to create a new Items table in your Django project and establish foreign key relationships to the Categories and UOM (Units of Measure) tables created in previous guides. It will cover the CRUD functionality for the Items table, including how to handle these foreign key relationships in your forms and templates.
By the end of this guide, you will be able to:
- β
Create a new
Itemstable with foreign keys toCategoriesandUOM. - β Handle foreign key relationships to display category and UOM names instead of IDs.
- β
Build CRUD views for the
Itemstable. - β
Use ModelForms for adding and updating
Itemsrecords. - β Reuse the partial template for table selection globally.
π 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
β β
β βββ items/
β βββ models.py
β βββ views.py
β βββ urls.py
β βββ forms.py
β βββ templates/items/
β βββ 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. We're working with a pre-existing database.
π§© 2. Model for Items Table
apps/items/models.py
from django.db import models
class StockItems(models.Model):
STATUS_CHOICES = [
(1, 'Active'),
(0, 'Inactive'),
]
id = models.AutoField(primary_key=True)
code = models.CharField(max_length=255, unique=True)
description = models.TextField(null=True, blank=True)
category = models.ForeignKey('categories.StockItemCategories', on_delete=models.SET_NULL, null=True, blank=True)
uom = models.ForeignKey('uom.StockItemUOM', on_delete=models.SET_NULL, null=True, blank=True)
status = models.IntegerField(choices=STATUS_CHOICES, default=1)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'stock_items'
managed = False # Do NOT let Django alter the table
def __str__(self):
return self.code
π 3. Form for Add/Update
apps/items/forms.py
from django import forms
from .models import StockItems
from categories.models import StockItemCategories
from uom.models import StockItemUOM
class StockItemsForm(forms.ModelForm):
STATUS_CHOICES = [
(1, 'Active'),
(0, 'Inactive'),
]
# ForeignKey to StockItemCategories (Categories)
category = forms.ModelChoiceField(
queryset=StockItemCategories.objects.filter(status=1), # Only show active categories
required=True,
widget=forms.Select(attrs={'class': 'form-select'}),
empty_label='Select a category'
)
# ForeignKey to StockItemUOM (Units of Measure)
uom = forms.ModelChoiceField(
queryset=StockItemUOM.objects.all(), # All UOMs are available
required=True,
widget=forms.Select(attrs={'class': 'form-select'}),
empty_label='Select a UOM'
)
# Status dropdown (active or inactive)
status = forms.ChoiceField(
choices=STATUS_CHOICES,
widget=forms.Select(attrs={'class': 'form-select'})
)
class Meta:
model = StockItems
fields = ['code', 'description', 'category', 'uom', 'status']
widgets = {
'code': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control'}),
}
π₯οΈ 4. Views
apps/items/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone
from .models import StockItems
from .forms import StockItemsForm
def index(request):
records = StockItems.objects.all().order_by('id')
return render(request, 'items/index.html', {
'title': 'Items List',
'records': records,
})
def add_record(request):
if request.method == 'POST':
form = StockItemsForm(request.POST)
if form.is_valid():
record = form.save(commit=False)
record.created_at = timezone.now()
record.updated_at = timezone.now()
record.save()
return redirect('items:index')
else:
form = StockItemsForm()
return render(request, 'items/form.html', {
'title': 'Add Item',
'form': form,
})
def update_record(request, pk):
record = get_object_or_404(StockItems, pk=pk)
if request.method == 'POST':
form = StockItemsForm(request.POST, instance=record)
if form.is_valid():
updated = form.save(commit=False)
updated.updated_at = timezone.now()
updated.save()
return redirect('items:index')
else:
form = StockItemsForm(instance=record)
return render(request, 'items/form.html', {
'title': f'Update Item ID {record.id}',
'form': form,
})
def delete_record(request, pk):
record = get_object_or_404(StockItems, pk=pk)
record.delete()
return redirect('items:index')
π 5. URLs
apps/items/urls.py
from django.urls import path
from . import views
app_name = 'items'
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'),
]
ποΈ 6. Templates
Index β apps/items/templates/items/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 'items:add' %}" class="btn btn-success btn-sm" style="position: absolute; right: 25px;">Add Item</a>
</div>
<!-- Table with Items records -->
<div class="table-responsive">
<table class="table table-sm table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Code</th>
<th>Description</th>
<th>Category</th>
<th>UOM</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.code }}</td>
<td>{{ row.description }}</td>
<td>{{ row.category.name }}</td> <!-- Display category name -->
<td>{{ row.uom.name }}</td> <!-- Display UOM name -->
<td>{{ row.get_status_display }}</td>
<td>{{ row.created_at }}</td>
<td class="text-nowrap d-flex gap-1">
<a href="{% url 'items:update' row.id %}" class="btn btn-light btn-sm">Update</a>
<a href="{% url 'items:delete' row.id %}" onclick="return confirm('Delete this record?')" class="btn btn-light btn-sm">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>