Pythonで本格的なWebアプリケーションを作るなら、Djangoは強力な選択肢です。
認証や管理画面、ORM、テンプレートなどが最初から備わった「電池込み」フレームワークで、初心者でも短時間でCRUDを実装できます。
本記事は環境構築からデータの作成・更新・削除まで、手を動かしながら体系的に学べる内容です。
- Django入門(できることと基本用語)
- Djangoの環境構築(venvとインストール)
- Djangoの基本構成(URL/ビュー/テンプレート/モデル)
- DjangoでCRUDを作る(初心者向け手順)
- 実行の流れ(おさらい)
- 参考: フルソース抜粋
- blog/views.py(CRUDをまとめて)
- blog/urls.py(ルーティング)
- blog/models.py(モデル)
- blog/forms.py(ModelForm)
- blog/templates/blog/base.html(ベース)
- blog/templates/blog/post_list.html(一覧)
- blog/templates/blog/post_detail.html(詳細)
- blog/templates/blog/post_form.html(作成・編集)
- blog/templates/blog/post_confirm_delete.html(削除確認)
- blog/static/blog/style.css(簡易スタイル)
- まとめ
Django入門(できることと基本用語)
高機能なWebアプリをPythonで作る理由とDjangoの特徴
なぜDjangoなのか(特徴の全体像)
DjangoはPythonの代表的なWebフレームワークで、開発の初速と保守性を両立します。
特に管理サイト、認証、フォーム、ORM、テンプレート、セキュリティ保護(CSRF対応など)が最初から使え、実務で必要になる機能の大半を標準でカバーしています。
学習を進めると、ジェネリックビューやクラスベースビューなど生産性を上げる機能にも移行しやすいです。
標準装備の機能(抜粋)
機能 | 概要 | 初学者メリット |
---|---|---|
管理サイト(admin) | データをGUIで管理可能 | まずはGUIでデータを確認できる |
ORM | PythonのクラスでDB操作 | SQLを覚える前にCRUDを体験できる |
認証 | ログイン/ログアウト/ユーザー管理 | ユーザー機能をすぐ試せる |
マイグレーション | スキーマ変更を安全に反映 | モデル変更が怖くなくなる |
テンプレート | 表示とロジックを分離 | 分業しやすくHTMLが書きやすい |
セキュリティ | CSRF/XSS/クリックジャッキング対策 | 初学者でも安全な初期設定 |
初心者が押さえる用語(URL/ビュー/テンプレート/モデル)
MVT(モデル・ビュー・テンプレート)の関係
DjangoはMVCに似たMVT
パターンです。
URLにアクセスが来るとビュー(Python関数/クラス)が処理し、必要ならモデル(DB)からデータを取り、テンプレート(HTML)に埋め込んで返します。
用語 | 役割 | 例 |
---|---|---|
URL | どのビューを呼ぶかのルーティング | /posts/ で記事一覧ビューへ |
ビュー | 受け取ったリクエストを処理 | DBから記事を集めてテンプレートへ渡す |
テンプレート | HTMLにデータを埋め込む | 記事タイトルをループで表示 |
モデル | DBテーブルの定義 | Post(title, body, created_at, …) |
本記事のゴール(環境構築からCRUDまで)
ゴールと完成イメージ
- Python仮想環境の準備からDjangoプロジェクト作成までを自力で行う
- モデル(Post)を作り、記事の一覧・詳細・新規作成・編集・削除(CRUD)を実装
- 管理サイトにログインしてデータをGUIで確認
最終的に、初心者が自分のアイデアを形にする最低限の土台を得ます。
Djangoの環境構築(venvとインストール)
Pythonとpipの確認方法
バージョン確認
インストール済みかをコマンドで確認します。
環境によりpython
かpy
を使います。
# macOS/Linux
python3 --version
pip3 --version
# Windows
py --version
py -m pip --version
Python 3.12.5
pip 24.2 from .../site-packages/pip (python 3.12)
仮想環境の作成と有効化(venv)
venvを使う理由と手順
プロジェクトごとに依存関係を分離するため、仮想環境を使います。
任意の作業フォルダで以下を実行します。
# macOS/Linux
python3 -m venv .venv
source .venv/bin/activate # 有効化
# プロンプトに (.venv) が付けばOK
# Windows (PowerShell)
py -m venv .venv
.venv\Scripts\Activate.ps1 # 有効化
有効化後の確認:
python --version
pip --version
Djangoのインストール(pip)
最新安定版を導入
pip install "Django>=5.0,<6.0"
インストールログ(例):
Collecting Django...
Successfully installed Django-5.0.7 ...
プロジェクト作成(django-admin startproject)
雛形を作成して構造を把握
django-admin startproject myproject
cd myproject
プロジェクトの構造(抜粋):
myproject/
├─ manage.py
└─ myproject/
├─ settings.py # 設定
├─ urls.py # ルーティング
├─ asgi.py
└─ wsgi.py
開発サーバー起動(runserver)
まずは立ち上げて動作確認
python manage.py runserver
Watching for file changes with StatReloader
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
ブラウザでhttp://127.0.0.1:8000/
にアクセスし、Djangoのウェルカムページが見えれば成功です。

データベースはSQLiteを使う(初期設定のままでOK)
はじめてはSQLiteが最適
設定不要ですぐ使えるDBとしてSQLiteが既定になっています。
学習の間はそのままで問題ありません。
後でPostgreSQLなどに切り替えるのも容易です。
Djangoの基本構成(URL/ビュー/テンプレート/モデル)
アプリ作成(startapp)とプロジェクトの関係
アプリを作って機能を分割
プロジェクト配下に機能単位のアプリを作ります。
ここではblogというアプリを作成します。
python manage.py startapp blog
生成物(抜粋):
blog/
├─ apps.py
├─ models.py # DBモデル
├─ views.py # ビュー
├─ urls.py # ※自分で作成(後述)
├─ templates/ # HTMLを置く(後述)
└─ static/ # 画像/CSS/JS(後述)
設定(settings.py)の最低限
アプリを有効化し、ロケールも整える
myproject/settings.py
を編集します。
# myproject/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog", # 追加: 作成したアプリ
]
LANGUAGE_CODE = "ja" # 日本語
TIME_ZONE = "Asia/Tokyo" # 日本時間
USE_I18N = True
USE_TZ = True # タイムゾーン対応は有効のままでOK
# 静的ファイルのURL
STATIC_URL = "static/"
本番ではDEBUG=FalseとALLOWED_HOSTSの設定が必須ですが、学習中のローカル開発では既定のままで構いません。
URL設定(urls.py)の基本
プロジェクトURLからアプリURLへ委譲
まず、アプリ側のurls.py
を新規作成し、ルーティングを定義します。
# blog/urls.py
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.index, name="index"), # トップページ
]
次に、プロジェクトのurls.py
でアプリのURLを読み込みます。
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")), # blogアプリに委譲
]
ビューの作成(関数ベースビュー)
最初のビューを定義
# blog/views.py
from django.http import HttpResponse
def index(request):
# 学習の第一歩: シンプルなレスポンスを返す
return HttpResponse("Djangoへようこそ。これが最初のページです。")
ブラウザでhttp://127.0.0.1:8000/
にアクセスし、メッセージが表示されればOKです。

テンプレートの作成(HTMLとテンプレートタグ入門)
テンプレートを使ってHTMLを返す
テンプレートはアプリ配下にtemplates/blog
を作って配置します。
# ディレクトリ作成(必要に応じて)
mkdir -p blog/templates/blog
# blog/views.py
from django.shortcuts import render
def index(request):
# テンプレートに値を渡す
context = {"message": "テンプレートから表示しています"}
return render(request, "blog/index.html", context)
<!-- blog/templates/blog/index.html -->
<!doctype html>
<html lang="ja">
<head><meta charset="utf-8"><title>はじめてのテンプレート</title></head>
<body>
<h1>{{ message }}</h1> <!-- {{ }} で変数を表示 -->
</body>
</html>
静的ファイルの置き方(static)
CSSや画像の配信
アプリ配下にstatic/blog
を作り、CSSを置きます。
mkdir -p blog/static/blog
/* blog/static/blog/style.css */
body { font-family: system-ui, sans-serif; }
h1 { color: #2a6; }
テンプレートから読み込みます。
{% load static %}
<link rel="stylesheet" href="{% static 'blog/style.css' %}">
モデル作成(models.py)とマイグレーション
DBテーブルを定義して反映
# blog/models.py
from django.db import models
class Post(models.Model):
# 記事モデル: タイトルと本文、作成/更新日時
title = models.CharField(max_length=200, verbose_name="タイトル")
body = models.TextField(verbose_name="本文")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="作成日時")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時")
def __str__(self):
# 管理画面などでの表示名
return self.title
マイグレーションを作成・反映します。
python manage.py makemigrations
python manage.py migrate
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0001_initial... OK
管理サイト(admin)の有効化とログイン
管理画面にモデルを登録
# blog/admin.py
from django.contrib import admin
from .models import Post
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ("id", "title", "created_at", "updated_at")
search_fields = ("title",)
管理ユーザーを作成してログインします。
python manage.py createsuperuser
# ユーザー名/メール/パスワードを対話入力
サーバーを起動しhttp://127.0.0.1:8000/admin/
へアクセス、ログイン後にPostが操作できれば成功です。


上記のようにならない場合、各種コマンドを実行した際のカレントディレクトリや、コマンドの実行漏れ、ファイルを作成する場所を間違えている、コードに転記ミスがあるなどの問題が発生しています。
ここまでの流れで作成されているはずのプロジェクトをダウンロードできるようにしておきますので、何を間違えているのかチェックしてみるといいでしょう。
Djangoのサンプルプロジェクトです。
ログイン情報:
ユーザー名 : user
パスワード : qwerty
DjangoでCRUDを作る(初心者向け手順)
モデル定義とテーブル作成
PostモデルをCRUDの対象にする
すでに定義済みのPost
モデルを使います。
初回であればmakemigrations
とmigrate
を済ませておきます。
以降、画面を通じてPostの作成・編集・削除を実装していきます。
一覧表示(Read一覧)
ビューとテンプレートを用意
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from .models import Post
def post_list(request):
# 新しい順に並べる
posts = Post.objects.order_by("-created_at")
return render(request, "blog/post_list.html", {"posts": posts})
<!-- blog/templates/blog/post_list.html -->
{% extends "blog/base.html" %}
{% block title %}記事一覧{% endblock %}
{% block content %}
<h2>記事一覧</h2>
<p><a href="{% url 'blog:post_create' %}">新規作成</a></p>
<ul>
{% for post in posts %}
<li>
<a href="{% url 'blog:post_detail' post.pk %}">{{ post.title }}</a>
<small>作成: {{ post.created_at|date:"Y-m-d H:i" }}</small>
</li>
{% empty %}
<li>まだ記事がありません。</li>
{% endfor %}
</ul>
{% endblock %}
ベーステンプレートも作っておくと共通化できます。
<!-- blog/templates/blog/base.html -->
{% load static %}
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>{% block title %}Django Blog{% endblock %}</title>
<link rel="stylesheet" href="{% static 'blog/style.css' %}">
</head>
<body>
<header>
<h1><a href="{% url 'blog:post_list' %}">Django Blog</a></h1>
<nav><a href="{% url 'blog:post_create' %}">新規作成</a></nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
詳細表示(Read詳細)
1件のレコードを表示する
# blog/views.py
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk) # 見つからない時は404
return render(request, "blog/post_detail.html", {"post": post})
<!-- blog/templates/blog/post_detail.html -->
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<article>
<h2>{{ post.title }}</h2>
<p><small>更新: {{ post.updated_at|date:"Y-m-d H:i" }}</small></p>
<div style="white-space: pre-wrap;">{{ post.body }}</div>
</article>
<p>
<a href="{% url 'blog:post_update' post.pk %}">編集</a> /
<a href="{% url 'blog:post_delete' post.pk %}">削除</a> /
<a href="{% url 'blog:post_list' %}">一覧へ戻る</a>
</p>
{% endblock %}
新規作成(Create)とフォーム(forms.py)
ModelFormでフォームを自動生成
# blog/forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
# ModelFormでtitleとbodyのフォームを生成
class Meta:
model = Post
fields = ["title", "body"]
labels = {"title": "タイトル", "body": "本文"}
# blog/views.py
from .forms import PostForm
def post_create(request):
if request.method == "POST":
form = PostForm(request.POST) # 送信データでフォームを作成
if form.is_valid():
form.save() # DBへINSERT
return redirect("blog:post_list")
else:
form = PostForm() # 空フォーム
return render(request, "blog/post_form.html", {"form": form, "mode": "create"})
<!-- blog/templates/blog/post_form.html -->
{% extends "blog/base.html" %}
{% block title %}{% if mode == "create" %}新規作成{% else %}編集{% endif %}{% endblock %}
{% block content %}
<h2>{% if mode == "create" %}新規作成{% else %}編集{% endif %}</h2>
<form method="post">
{% csrf_token %} <!-- CSRF対策は必須 -->
{{ form.as_p }}
<p>
<button type="submit">{% if mode == "create" %}作成{% else %}更新{% endif %}</button>
<a href="{% url 'blog:post_list' %}">キャンセル</a>
</p>
</form>
{% endblock %}
編集(Update)の実装
既存レコードをフォームに差し込む
# blog/views.py
def post_update(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, instance=post) # 既存を上書き
if form.is_valid():
form.save() # DBへUPDATE
return redirect("blog:post_detail", pk=post.pk)
else:
form = PostForm(instance=post) # 初期値入りフォーム
return render(request, "blog/post_form.html", {"form": form, "mode": "edit"})
削除(Delete)の実装
確認画面を挟んで安全に削除
# blog/views.py
def post_delete(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
post.delete() # DBからDELETE
return redirect("blog:post_list")
return render(request, "blog/post_confirm_delete.html", {"post": post})
<!-- blog/templates/blog/post_confirm_delete.html -->
{% extends "blog/base.html" %}
{% block title %}削除確認{% endblock %}
{% block content %}
<h2>削除確認</h2>
<p>「{{ post.title }}」を削除します。よろしいですか?</p>
<form method="post">
{% csrf_token %}
<button type="submit">削除する</button>
<a href="{% url 'blog:post_detail' post.pk %}">戻る</a>
</form>
{% endblock %}
URLとテンプレートの紐付け
画面遷移の要(ルーティング)を整理
ブログアプリのURL定義をまとめます。
# blog/urls.py
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="post_list"),
path("posts/<int:pk>/", views.post_detail, name="post_detail"),
path("posts/new/", views.post_create, name="post_create"),
path("posts/<int:pk>/edit/", views.post_update, name="post_update"),
path("posts/<int:pk>/delete/", views.post_delete, name="post_delete"),
]
プロジェクト側での読み込みは以前の通りです。
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")),
]
テンプレートでは{% url 'blog:post_detail' post.pk %}
のように名前付きURLを使うと、パスが変わってもリンクが壊れにくくなります。
動作確認とエラー対処
よくあるつまずきと解決策
- TemplateDoesNotExist: テンプレートのパスが正しいか確認します。アプリ内なら
blog/templates/blog/...
の2階層を守るのが安全です。 - ModuleNotFoundError:
INSTALLED_APPS
に"blog"
を追加し忘れていないか確認します。 - CSRF verification failed:
POST
フォームに{% csrf_token %}
を入れたか確認します。 - マイグレーションエラー: モデルを変えたら
makemigrations
→migrate
の順に実行します。衝突時はpython manage.py showmigrations
で状況を確認します。 - 404 Not Found: URLパターンとリンクの
name
が一致しているか、pk
を正しく渡しているかを再確認します。
開発サーバーのログは手がかりになります。
エラー時はトレースバックを上から順に読み、どのファイルの何行目で止まったかに注目してください。
実行の流れ(おさらい)
1. サーバー起動から画面表示まで
- ブラウザが
/posts/
へアクセス - プロジェクト
urls.py
→アプリurls.py
の順にマッチ post_list
ビューがPost
をORMで取得post_list.html
にレンダリングしてHTMLを返す
この一連の流れを理解すると、URL/ビュー/テンプレート/モデルが頭の中でつながるようになります。
参考: フルソース抜粋
blog/views.py(CRUDをまとめて)
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from .models import Post
from .forms import PostForm
def post_list(request):
posts = Post.objects.order_by("-created_at")
return render(request, "blog/post_list.html", {"posts": posts})
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
return render(request, "blog/post_detail.html", {"post": post})
def post_create(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid():
form.save()
return redirect("blog:post_list")
else:
form = PostForm()
return render(request, "blog/post_form.html", {"form": form, "mode": "create"})
def post_update(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
return redirect("blog:post_detail", pk=post.pk)
else:
form = PostForm(instance=post)
return render(request, "blog/post_form.html", {"form": form, "mode": "edit"})
def post_delete(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
post.delete()
return redirect("blog:post_list")
return render(request, "blog/post_confirm_delete.html", {"post": post})
blog/urls.py(ルーティング)
# blog/urls.py
from django.urls import path
from . import views
app_name = "blog"
urlpatterns = [
path("", views.post_list, name="post_list"),
path("posts/<int:pk>/", views.post_detail, name="post_detail"),
path("posts/new/", views.post_create, name="post_create"),
path("posts/<int:pk>/edit/", views.post_update, name="post_update"),
path("posts/<int:pk>/delete/", views.post_delete, name="post_delete"),
]
blog/models.py(モデル)
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200, verbose_name="タイトル")
body = models.TextField(verbose_name="本文")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="作成日時")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時")
def __str__(self):
return self.title
blog/forms.py(ModelForm)
# blog/forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ["title", "body"]
labels = {"title": "タイトル", "body": "本文"}
blog/templates/blog/base.html(ベース)
{% load static %}
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>{% block title %}Django Blog{% endblock %}</title>
<link rel="stylesheet" href="{% static 'blog/style.css' %}">
</head>
<body>
<header>
<h1><a href="{% url 'blog:post_list' %}">Django Blog</a></h1>
<nav><a href="{% url 'blog:post_create' %}">新規作成</a></nav>
</header>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
blog/templates/blog/post_list.html(一覧)
{% extends "blog/base.html" %}
{% block title %}記事一覧{% endblock %}
{% block content %}
<h2>記事一覧</h2>
<p><a href="{% url 'blog:post_create' %}">新規作成</a></p>
<ul>
{% for post in posts %}
<li>
<a href="{% url 'blog:post_detail' post.pk %}">{{ post.title }}</a>
<small>作成: {{ post.created_at|date:"Y-m-d H:i" }}</small>
</li>
{% empty %}
<li>まだ記事がありません。</li>
{% endfor %}
</ul>
{% endblock %}
blog/templates/blog/post_detail.html(詳細)
{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
<article>
<h2>{{ post.title }}</h2>
<p><small>更新: {{ post.updated_at|date:"Y-m-d H:i" }}</small></p>
<div style="white-space: pre-wrap;">{{ post.body }}</div>
</article>
<p>
<a href="{% url 'blog:post_update' post.pk %}">編集</a> /
<a href="{% url 'blog:post_delete' post.pk %}">削除</a> /
<a href="{% url 'blog:post_list' %}">一覧へ戻る</a>
</p>
{% endblock %}
blog/templates/blog/post_form.html(作成・編集)
{% extends "blog/base.html" %}
{% block title %}{% if mode == "create" %}新規作成{% else %}編集{% endif %}{% endblock %}
{% block content %}
<h2>{% if mode == "create" %}新規作成{% else %}編集{% endif %}</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<p>
<button type="submit">{% if mode == "create" %}作成{% else %}更新{% endif %}</button>
<a href="{% url 'blog:post_list' %}">キャンセル</a>
</p>
</form>
{% endblock %}
blog/templates/blog/post_confirm_delete.html(削除確認)
{% extends "blog/base.html" %}
{% block title %}削除確認{% endblock %}
{% block content %}
<h2>削除確認</h2>
<p>「{{ post.title }}」を削除します。よろしいですか?</p>
<form method="post">
{% csrf_token %}
<button type="submit">削除する</button>
<a href="{% url 'blog:post_detail' post.pk %}">戻る</a>
</form>
{% endblock %}
blog/static/blog/style.css(簡易スタイル)
/* blog/static/blog/style.css */
body { font-family: system-ui, sans-serif; margin: 2rem; }
header h1 a { text-decoration: none; color: #2a6; }
main { margin-top: 1rem; }
まとめ
Djangoは「最初から本気で作れる」実用志向のフレームワークです。
本記事では仮想環境の準備からプロジェクト作成、URL/ビュー/テンプレート/モデルというMVTの要素を丁寧にたどり、管理サイトの活用とModelFormによるCRUDまでを一気通貫で実装しました。
ここまで到達すれば、一覧/詳細/作成/編集/削除というWebアプリの基本骨格を自力で構築できます。
次の一歩としては、ページネーション、認証と権限、クラスベースビュー、汎用ビュー、バリデーションの強化、デプロイなどに挑戦すると、より実務レベルのアプリに進化します。
今日作った最小アプリを起点に、あなたのアイデアをDjangoで形にしていきましょう。