Django
「完璧主義者のための、期限のあるWebフレームワーク」として知られる、フルスタック型の高機能Webフレームワーク。ORM、管理画面、認証機能を標準搭載。
GitHub概要
django/django
The Web framework for perfectionists with deadlines.
スター84,248
ウォッチ2,284
フォーク32,718
作成日:2012年4月28日
言語:Python
ライセンス:BSD 3-Clause "New" or "Revised" License
トピックス
appsdjangoframeworkmodelsormpythontemplatesviewsweb
スター履歴
データ取得日時: 2025/7/17 10:32
フレームワーク
Django
概要
Djangoは、Pythonで書かれた高水準のWebフレームワークで、迅速な開発と実用的でクリーンな設計を可能にします。
詳細
Django(ジャンゴ)は2005年にカンザス州ローレンスの新聞社で開発され、2008年にDjango Software Foundationによってオープンソース化されたWebフレームワークです。「batteries included」(必要なものが全て含まれている)の哲学に基づいて設計されており、Web開発に必要な機能が標準で提供されています。Model-View-Template(MVT)アーキテクチャを採用し、データベース操作を抽象化するORM(Object-Relational Mapping)、自動生成される管理画面、URLルーティング、テンプレートエンジン、フォーム処理、認証システム、セキュリティ機能などを備えています。スケーラビリティとセキュリティを重視して設計されており、Instagram、Pinterest、Mozilla、NASA等の大規模サービスで採用されています。「Don't Repeat Yourself」(DRY)原則と「Convention over Configuration」により、効率的な開発を実現します。
メリット・デメリット
メリット
- 包括的フレームワーク: 認証、管理画面、ORM等の豊富な標準機能
- 高いセキュリティ: SQL injection、XSS、CSRF等の脆弱性対策が標準装備
- 自動管理画面: データベースモデルから管理インターフェースを自動生成
- 優秀なORM: データベース操作の抽象化により保守性が向上
- スケーラビリティ: 大規模Webアプリケーションの開発・運用に適している
- 豊富なドキュメント: 詳細で分かりやすい公式ドキュメント
- 活発なコミュニティ: 大規模なコミュニティと豊富な第三者パッケージ
デメリット
- 学習コスト: 多機能ゆえに初心者には習得が困難
- 重量感: 小規模プロジェクトには過剰な機能
- 柔軟性の制約: フレームワークの規約に従う必要がある
- パフォーマンス: Ruby on Railsより処理速度が劣る場合がある
- モノリシック構造: マイクロサービスアーキテクチャには向かない
主要リンク
書き方の例
Hello World
# urls.py
from django.contrib import admin
from django.urls import path
from django.http import HttpResponse
def hello_world(request):
return HttpResponse("Hello, Django World!")
urlpatterns = [
path('admin/', admin.site.urls),
path('', hello_world, name='hello'),
]
# settings.py (基本設定)
SECRET_KEY = 'your-secret-key-here'
DEBUG = True
ALLOWED_HOSTS = []
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
# プロジェクト作成コマンド
# django-admin startproject myproject
# cd myproject
# python manage.py runserver
モデル定義とORM
# models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "categories"
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
tags = models.ManyToManyField('Tag', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_published = models.BooleanField(default=False)
class Meta:
ordering = ['-created_at']
def __str__(self):
return self.title
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
# ORM使用例
# マイグレーション作成・実行
# python manage.py makemigrations
# python manage.py migrate
# データベース操作例
from myapp.models import Post, Category, Tag
# データ作成
category = Category.objects.create(name="技術", description="技術関連の記事")
post = Post.objects.create(
title="Djangoの使い方",
content="Djangoは素晴らしいフレームワークです。",
author_id=1,
category=category,
is_published=True
)
# クエリ例
all_posts = Post.objects.all()
published_posts = Post.objects.filter(is_published=True)
recent_posts = Post.objects.filter(created_at__gte='2024-01-01')
tech_posts = Post.objects.filter(category__name="技術")
# 複雑なクエリ
from django.db.models import Q, Count
popular_posts = Post.objects.annotate(
tag_count=Count('tags')
).filter(
Q(is_published=True) & Q(tag_count__gt=2)
).order_by('-created_at')
URLルーティング
# myproject/urls.py (メインURLconf)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
path('api/', include('api.urls')),
]
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
# 基本的なURL
path('', views.index, name='index'),
path('posts/', views.post_list, name='post_list'),
# パラメータ付きURL
path('posts/<int:post_id>/', views.post_detail, name='post_detail'),
path('category/<slug:category_slug>/', views.category_posts, name='category_posts'),
# 正規表現URL
path('archive/<int:year>/<int:month>/', views.archive, name='archive'),
# 名前付きグループ
path('tag/<str:tag_name>/', views.posts_by_tag, name='posts_by_tag'),
]
# views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from .models import Post, Category
def index(request):
recent_posts = Post.objects.filter(is_published=True)[:5]
return render(request, 'blog/index.html', {'posts': recent_posts})
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id, is_published=True)
return render(request, 'blog/post_detail.html', {'post': post})
def category_posts(request, category_slug):
category = get_object_or_404(Category, slug=category_slug)
posts = Post.objects.filter(category=category, is_published=True)
return render(request, 'blog/category_posts.html', {
'category': category,
'posts': posts
})
テンプレート処理
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
<header>
<nav>
<a href="{% url 'blog:index' %}">ホーム</a>
<a href="{% url 'blog:post_list' %}">記事一覧</a>
</nav>
</header>
<main>
{% block content %}
{% endblock %}
</main>
<footer>
<p>© 2024 My Blog</p>
</footer>
</body>
</html>
<!-- templates/blog/index.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %}ホーム - {{ block.super }}{% endblock %}
{% block content %}
<h1>最新記事</h1>
{% if posts %}
{% for post in posts %}
<article class="post-preview">
<h2>
<a href="{% url 'blog:post_detail' post.id %}">{{ post.title }}</a>
</h2>
<p class="post-meta">
投稿者: {{ post.author.username }} |
カテゴリ:
{% if post.category %}
<a href="{% url 'blog:category_posts' post.category.slug %}">
{{ post.category.name }}
</a>
{% else %}
未分類
{% endif %} |
{{ post.created_at|date:"Y年m月d日" }}
</p>
<p>{{ post.content|truncatewords:30 }}</p>
{% if post.tags.all %}
<div class="tags">
タグ:
{% for tag in post.tags.all %}
<a href="{% url 'blog:posts_by_tag' tag.name %}" class="tag">
{{ tag.name }}
</a>
{% if not forloop.last %}, {% endif %}
{% endfor %}
</div>
{% endif %}
</article>
{% endfor %}
{% else %}
<p>記事がまだありません。</p>
{% endif %}
<!-- カスタムフィルターの使用例 -->
<p>{{ post.content|linebreaks|safe }}</p>
<p>投稿から{{ post.created_at|timesince }}経過</p>
{% endblock %}
<!-- templates/blog/post_detail.html -->
{% extends 'base.html' %}
{% block title %}{{ post.title }} - {{ block.super }}{% endblock %}
{% block content %}
<article>
<h1>{{ post.title }}</h1>
<p class="post-meta">
投稿者: {{ post.author.get_full_name|default:post.author.username }} |
{{ post.created_at|date:"Y年m月d日 H:i" }}
{% if post.updated_at != post.created_at %}
(更新: {{ post.updated_at|date:"Y年m月d日 H:i" }})
{% endif %}
</p>
<div class="post-content">
{{ post.content|linebreaks }}
</div>
{% if post.tags.all %}
<div class="tags">
{% for tag in post.tags.all %}
<span class="tag">{{ tag.name }}</span>
{% endfor %}
</div>
{% endif %}
</article>
<a href="{% url 'blog:index' %}">← 記事一覧に戻る</a>
{% endblock %}
フォーム処理
# forms.py
from django import forms
from django.contrib.auth.models import User
from .models import Post, Category
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'content', 'category', 'tags', 'is_published']
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'タイトルを入力してください'
}),
'content': forms.Textarea(attrs={
'class': 'form-control',
'rows': 10,
'placeholder': '記事の内容を入力してください'
}),
'category': forms.Select(attrs={'class': 'form-control'}),
'tags': forms.CheckboxSelectMultiple(),
'is_published': forms.CheckboxInput(attrs={'class': 'form-check-input'})
}
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 5:
raise forms.ValidationError('タイトルは5文字以上で入力してください。')
return title
def clean_content(self):
content = self.cleaned_data['content']
if len(content) < 50:
raise forms.ValidationError('本文は50文字以上で入力してください。')
return content
class ContactForm(forms.Form):
name = forms.CharField(
max_length=100,
label='お名前',
widget=forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'お名前を入力してください'
})
)
email = forms.EmailField(
label='メールアドレス',
widget=forms.EmailInput(attrs={
'class': 'form-control',
'placeholder': '[email protected]'
})
)
subject = forms.CharField(
max_length=200,
label='件名',
widget=forms.TextInput(attrs={'class': 'form-control'})
)
message = forms.CharField(
label='メッセージ',
widget=forms.Textarea(attrs={
'class': 'form-control',
'rows': 6,
'placeholder': 'メッセージを入力してください'
})
)
def clean_email(self):
email = self.cleaned_data['email']
if not email.endswith(('.com', '.net', '.org')):
raise forms.ValidationError('有効なメールアドレスを入力してください。')
return email
# views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.core.mail import send_mail
from .forms import PostForm, ContactForm
@login_required
def create_post(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user
post.save()
form.save_m2m() # Many-to-Manyフィールドの保存
messages.success(request, '記事が正常に作成されました。')
return redirect('blog:post_detail', post_id=post.id)
else:
form = PostForm()
return render(request, 'blog/create_post.html', {'form': form})
@login_required
def edit_post(request, post_id):
post = get_object_or_404(Post, id=post_id, author=request.user)
if request.method == 'POST':
form = PostForm(request.POST, instance=post)
if form.is_valid():
form.save()
messages.success(request, '記事が正常に更新されました。')
return redirect('blog:post_detail', post_id=post.id)
else:
form = PostForm(instance=post)
return render(request, 'blog/edit_post.html', {
'form': form,
'post': post
})
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# メール送信処理
name = form.cleaned_data['name']
email = form.cleaned_data['email']
subject = form.cleaned_data['subject']
message = form.cleaned_data['message']
full_message = f"""
お名前: {name}
メールアドレス: {email}
メッセージ:
{message}
"""
send_mail(
subject=f"[お問い合わせ] {subject}",
message=full_message,
from_email=email,
recipient_list=['[email protected]'],
fail_silently=False,
)
messages.success(request, 'お問い合わせを送信しました。')
return redirect('blog:contact')
else:
form = ContactForm()
return render(request, 'blog/contact.html', {'form': form})
管理者インターフェース
# admin.py
from django.contrib import admin
from django.utils.html import format_html
from .models import Post, Category, Tag
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'post_count', 'created_at']
search_fields = ['name', 'description']
list_filter = ['created_at']
ordering = ['name']
def post_count(self, obj):
return obj.post_set.count()
post_count.short_description = '記事数'
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['name', 'post_count']
search_fields = ['name']
def post_count(self, obj):
return obj.post_set.count()
post_count.short_description = '使用回数'
class TagInline(admin.TabularInline):
model = Post.tags.through
extra = 1
verbose_name = 'タグ'
verbose_name_plural = 'タグ'
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = [
'title', 'author', 'category', 'is_published',
'created_at', 'view_on_site'
]
list_filter = [
'is_published', 'created_at', 'category', 'tags'
]
search_fields = ['title', 'content', 'author__username']
date_hierarchy = 'created_at'
ordering = ['-created_at']
# 詳細画面のレイアウト
fieldsets = (
('基本情報', {
'fields': ('title', 'content')
}),
('分類', {
'fields': ('category', 'tags'),
'classes': ('collapse',)
}),
('設定', {
'fields': ('is_published',),
'classes': ('collapse',)
}),
('メタ情報', {
'fields': ('author', 'created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
# 読み取り専用フィールド
readonly_fields = ['created_at', 'updated_at']
# フィルター条件をカスタマイズ
def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
# 保存時の処理をカスタマイズ
def save_model(self, request, obj, form, change):
if not change: # 新規作成時
obj.author = request.user
super().save_model(request, obj, form, change)
# アクションを追加
def make_published(self, request, queryset):
count = queryset.update(is_published=True)
self.message_user(request, f'{count}件の記事を公開しました。')
make_published.short_description = '選択した記事を公開する'
def make_unpublished(self, request, queryset):
count = queryset.update(is_published=False)
self.message_user(request, f'{count}件の記事を非公開にしました。')
make_unpublished.short_description = '選択した記事を非公開にする'
actions = [make_published, make_unpublished]
# カスタム表示
def view_on_site(self, obj):
if obj.is_published:
return format_html(
'<a href="/posts/{}/" target="_blank">サイトで表示</a>',
obj.id
)
return '-'
view_on_site.short_description = 'サイト表示'
# 管理サイトのカスタマイズ
admin.site.site_header = '私のブログ管理'
admin.site.site_title = 'Blog Admin'
admin.site.index_title = 'ダッシュボード'
# スーパーユーザー作成コマンド
# python manage.py createsuperuser