Django: Blog Website
Published:
Bab ini membahas pembuatan aplikasi Blog berbasis database dengan fitur inti membaca daftar post dan melihat halaman detail per post, sekaligus menyiapkan fondasi untuk create, read, update, dan delete pada bab berikutnya.
Pada bab ini kita akan mempelajari:
- Setup proyek
project6dengan virtual environment. - Membuat model
Postdengan field judul, author, dan body. - Memahami konsep primary key dan foreign key dalam Django ORM.
- Mengelola data menggunakan Django Admin.
- Menyusun function-based views, URL routing, dan template inheritance.
- Menambahkan static files (CSS) agar tampilan blog lebih rapi.
- Membuat halaman detail post dan menerapkan
get_absolute_url(). - Menulis pengujian model, URL, template, dan konten.
- Workflow Git untuk commit awal proyek.
1. Gambaran Blog Website
Di bab ini kita mulai membangun aplikasi blog yang lebih realistis. Jika pada bab sebelumnya kita baru menampilkan data sederhana, kini kita bergerak ke pola yang umum di hampir semua website CRUD.
Fokus utama bagian ini:
- Menyusun struktur data post secara benar.
- Menghubungkan post dengan user melalui relasi database.
- Menampilkan daftar dan detail post di halaman web.
- Menambahkan gaya visual dasar dengan static files.
- Menyiapkan test agar perubahan tetap aman.
Sebelum masuk ke langkah setup, berikut gambaran struktur direktori akhir yang akan kita capai pada proyek project6:
project6/
├── .venv/
├── db.sqlite3
├── manage.py
├── django_project/
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── blog/
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations/
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── static/
│ └── css/
│ └── base.css
└── templates/
├── base.html
├── home.html
└── post_detail.html
2. Setup Awal Proyek project6
Langkah setup mirip dengan bab sebelumnya: buat folder proyek, aktifkan virtual environment, instal dependensi, lalu buat project dan app.
# Windows
cd onedrive\desktop\pawf\django
mkdir project6
cd project6
python -m venv .venv
.venv\Scripts\Activate.ps1
# macOS
cd ~/desktop/pawf/django
mkdir -p project6
cd project6
python3 -m venv .venv
source .venv/bin/activate
Instal paket, buat project, buat app, dan migrasi awal:
python -m pip install django~=6.0.4
python -m pip install black
django-admin startproject django_project .
python manage.py startapp blog
python manage.py migrate
python manage.py runserver
Tambahkan app blog ke INSTALLED_APPS di django_project/settings.py:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog", # new
]
3. Model Blog Post
Kita ingin tabel Post dengan tiga field utama:
titleauthorbody
Dalam Django, model adalah representasi tabel database dalam bentuk class Python. Setiap atribut pada class akan dipetakan menjadi kolom pada tabel.
Awalnya kita bisa mendefinisikan model seperti berikut di blog/models.py:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=200)
body = models.TextField()
def __str__(self):
return self.title
Kemudian buat migrasi dan terapkan ke database:
python manage.py makemigrations blog
python manage.py migrate
Catatan penting:
CharFieldcocok untuk teks pendek dengan batas panjang.TextFieldcocok untuk konten panjang.- Method
__str__()membantu tampilan objek lebih manusiawi di admin dan shell.

4. Primary Key dan Foreign Key
Django otomatis menambahkan primary key bertipe auto-increment ke model, biasanya bernama id.
Primary key berfungsi sebagai identitas unik untuk setiap baris data. Foreign key digunakan untuk menghubungkan satu tabel dengan tabel lain sehingga relasi antar data tetap konsisten.
Karena kita ingin setiap post terkait user yang valid, field author sebaiknya diubah menjadi foreign key ke model user bawaan Django seperti berikut di blog/models.py:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
"auth.User",
on_delete=models.CASCADE,
)
body = models.TextField()
def __str__(self):
return self.title
Setelah mengubah skema model, jalankan migrasi lagi:
python manage.py makemigrations blog
python manage.py migrate
Makna relasi ini:
- Satu user dapat memiliki banyak post (many-to-one dari post ke user).
- Jika user dihapus, post terkait ikut dihapus karena
on_delete=models.CASCADE.

5. Django Admin
Buat akun superuser:
python manage.py createsuperuser
Daftarkan model pada blog/admin.py:
from django.contrib import admin
from .models import Post
admin.site.register(Post)
Agar tampilan list admin menampilkan beberapa kolom sekaligus, gunakan ModelAdmin perbaiki model pada blog/admin.py:
from django.contrib import admin
from .models import Post
class PostAdmin(admin.ModelAdmin):
list_display = (
"title",
"author",
"body",
)
admin.site.register(Post, PostAdmin)
Setelah itu, buka http://127.0.0.1:8000/admin/, login, lalu tambahkan minimal dua post contoh.
Admin Homepage
Admin Create Post
Admin Post List
6. Views, URLs, dan Templates
Tiga komponen ini adalah alur utama request-response di Django: URL menentukan rute, view memproses logika, dan template merender tampilan HTML ke pengguna.
6.1 Function-Based View
Di blog/views.py, buat view daftar post:
from django.shortcuts import render
from .models import Post
def post_list(request):
posts = Post.objects.all()
return render(request, "home.html", {"posts": posts})
6.2 Routing URL
Buat file urls.py pada blog isi dengan kode berikut:
from django.urls import path
from .views import post_list
urlpatterns = [
path("", post_list, name="home"),
]
Perbarui django_project/urls.py:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")),
]
6.3 Template Inheritance
Buat direktori template:
mkdir templates
Perbarui django_project/settings.py:
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "templates"], # update
"APP_DIRS": True,
...
},
]
Buat templates/base.html:
<html>
<head>
<title>Django blog</title>
</head>
<body>
<header>
<h1><a href="{% url 'home' %}">Django blog</a></h1>
</header>
<div>
{% block content %}
{% endblock content %}
</div>
</body>
</html>
Buat templates/home.html:
{% extends "base.html" %}
{% block content %}
{% for post in posts %}
<div class="post-entry">
<h2><a href="">{{ post.title }}</a></h2>
<p>Author: {{ post.author }}</p>
<p>{{ post.body }}</p>
</div>
{% endfor %}
{% endblock content %}

7. Static Files dan Styling
Static files adalah aset frontend seperti CSS, JavaScript, dan gambar yang tidak dirender sebagai data dinamis. Pada bab ini kita gunakan CSS untuk meningkatkan keterbacaan halaman blog.
Tambahkan folder static tingkat proyek:
mkdir static
mkdir static/css
Atur di django_project/settings.py:
STATIC_URL = "/static/" # update
STATICFILES_DIRS = [BASE_DIR / "static"] # add
Buat file static/css/base.css:
body {
font-family: "Source Sans Pro", sans-serif;
font-size: 18px;
}
header {
border-bottom: 1px solid #999;
margin-bottom: 2rem;
display: flex;
}
header h1 a {
color: red;
text-decoration: none;
}
.post-entry {
margin-bottom: 2rem;
}
.post-entry h2 {
margin: 0.5rem 0;
}
.post-entry h2 a,
.post-entry h2 a:visited {
color: blue;
text-decoration: none;
}
.post-entry h2 a:hover {
color: red;
}
.post-entry p {
margin: 0;
font-weight: 400;
}
Lalu muat static files pada templates/base.html:
{% load static %} # add
<html>
<head>
<title>Django blog</title>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400" rel="stylesheet"> # add
<link href="{% static 'css/base.css' %}" rel="stylesheet"> # add
</head>
...

8. Halaman Detail dan get_absolute_url()
8.1 View dan URL detail
Perbarui blog/views.py:
from django.shortcuts import get_object_or_404, render # upadate
from .models import Post
def post_list(request):
posts = Post.objects.all()
return render(request, "home.html", {"posts": posts})
# add
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, "post_detail.html", {"post": post})
Perbarui blog/urls.py:
from django.urls import path
from .views import post_detail, post_list
urlpatterns = [
path("post/<int:pk>/", post_detail, name="post_detail"),
path("", post_list, name="home"),
]
Buat templates/post_detail.html:
{% extends "base.html" %}
{% block content %}
<div class="post-entry">
<h2>{{ post.title }}</h2>
<p>{{ post.body }}</p>
</div>
{% endblock content %}
8.2 Tautan dari homepage ke halaman detail
Pada templates/home.html, ganti tautan judul post:
<h2><a href="{% url 'post_detail' post.pk %}">{{ post.title }}</a></h2>

8.3 Best practice get_absolute_url()
Supaya URL canonical tidak perlu diulang di banyak template, tambahkan method berikut pada blog/models.py:
get_absolute_url() membantu setiap objek model mengetahui URL detail-nya sendiri. Pendekatan ini membuat template lebih bersih dan mengurangi hardcode path.
from django.db import models
from django.urls import reverse # add
# update
class Post(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey("auth.User", on_delete=models.CASCADE)
body = models.TextField()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("post_detail", kwargs={"pk": self.pk})
Lalu pakai di template templates/home.html:
<h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
9. Testing
Berikut contoh pengujian yang memeriksa model, URL, template, dan konten untuk list maupun detail view.
Pengujian dilakukan dengan django.test.TestCase, yang secara otomatis membuat database sementara agar data uji terisolasi dari database pengembangan.
blog/tests.py:
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from .models import Post
class BlogTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = get_user_model().objects.create_user(
username="testuser", email="test@email.com", password="secret"
)
cls.post = Post.objects.create(
title="A good title",
body="Nice body content",
author=cls.user,
)
def test_post_model(self):
self.assertEqual(self.post.title, "A good title")
self.assertEqual(self.post.body, "Nice body content")
self.assertEqual(self.post.author.username, "testuser")
self.assertEqual(str(self.post), "A good title")
self.assertEqual(self.post.get_absolute_url(), "/post/1/")
def test_url_exists_at_correct_location_listview(self):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
def test_url_exists_at_correct_location_detailview(self):
response = self.client.get("/post/1/")
self.assertEqual(response.status_code, 200)
def test_post_listview(self):
response = self.client.get(reverse("home"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Nice body content")
self.assertTemplateUsed(response, "home.html")
def test_post_detailview(self):
response = self.client.get(reverse("post_detail", kwargs={"pk": self.post.pk}))
no_response = self.client.get("/post/100000/")
self.assertEqual(response.status_code, 200)
self.assertEqual(no_response.status_code, 404)
self.assertContains(response, "A good title")
self.assertTemplateUsed(response, "post_detail.html")
Jalankan test:
python manage.py test
Tips penting saat mengetes URL detail: jangan lupa slash di depan dan belakang (/post/1/).
10. Git dan Commit Awal
Inisialisasi Git dan cek perubahan:
git init
git status
Buat .gitignore agar folder environment, cache, dan database lokal tidak ikut ter-commit:
.venv/
__pycache__/
db.sqlite3
Lalu lakukan commit:
git add .
git commit -am "Add project blog website with model, admin, views, urls, templates, static files, detail page, get_absolute_url, and tests"
Connect to GitHub and push:
git push -u origin main
11. Kesimpulan
Pada chapter ini kita berhasil membangun fondasi Blog Website dari nol:
- Model
Postdengan relasi user. - Manajemen data melalui Django Admin.
- Halaman list dan detail post berbasis URL dinamis.
- Styling awal dengan static files.
- Best practice URL model dengan
get_absolute_url(). - Test suite yang mencakup model, URL, template, dan konten.
Bab berikutnya akan melanjutkan ke generic class-based views dan form agar operasi create, update, delete tidak hanya melalui admin, tetapi langsung dari antarmuka aplikasi.





