Django

「完璧主義者のための、期限のあるWebフレームワーク」として知られる、フルスタック型の高機能Webフレームワーク。ORM、管理画面、認証機能を標準搭載。

PythonフレームワークBackendWebORMMVT管理画面

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

スター履歴

django/django Star History
データ取得日時: 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>&copy; 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' %}">&larr; 記事一覧に戻る</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