Django モデルとORM
モデルの定義
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField('カテゴリ名', max_length=100)
slug = models.SlugField(unique=True)
order = models.IntegerField('表示順', default=0)
class Meta:
verbose_name = 'カテゴリ'
verbose_name_plural = 'カテゴリ'
ordering = ['order']
def __str__(self):
return self.name
class Post(models.Model):
class Status(models.TextChoices):
DRAFT = 'draft', '下書き'
PUBLISHED = 'published', '公開'
title = models.CharField('タイトル', max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(
User, on_delete=models.CASCADE,
related_name='posts', verbose_name='著者'
)
category = models.ForeignKey(
Category, on_delete=models.SET_NULL,
null=True, blank=True, verbose_name='カテゴリ'
)
body = models.TextField('本文')
status = models.CharField(
'状態', max_length=10,
choices=Status.choices, default=Status.DRAFT
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = '記事'
verbose_name_plural = '記事'
ordering = ['-created_at']
def __str__(self):
return self.title
主要なフィールド型
| フィールド |
用途 |
主なオプション |
| CharField |
短い文字列 |
max_length(必須) |
| TextField |
長い文字列 |
- |
| IntegerField |
整数 |
- |
| FloatField |
浮動小数点 |
- |
| BooleanField |
真偽値 |
default |
| DateTimeField |
日時 |
auto_now, auto_now_add |
| ForeignKey |
多対1リレーション |
on_delete(必須) |
| ManyToManyField |
多対多リレーション |
- |
| OneToOneField |
1対1リレーション |
on_delete(必須) |
| FileField |
ファイル |
upload_to |
| ImageField |
画像 |
upload_to |
| SlugField |
URLスラグ |
unique |
| JSONField |
JSON |
default=dict |
クエリセット(ORM操作)
from blog.models import Post, Category
# 作成
post = Post.objects.create(
title='最初の記事',
slug='first-post',
author=user,
body='記事の本文です。',
status='published'
)
# 全件取得
posts = Post.objects.all()
# フィルタリング
published = Post.objects.filter(status='published')
recent = Post.objects.filter(created_at__gte='2026-01-01')
by_author = Post.objects.filter(author__username='admin')
# 除外
not_draft = Post.objects.exclude(status='draft')
# 1件取得
post = Post.objects.get(slug='first-post') # なければDoesNotExist
post = Post.objects.filter(slug='first-post').first() # なければNone
# チェイン
posts = Post.objects.filter(
status='published'
).order_by('-created_at')[:10]
# 集約
from django.db.models import Count, Avg
category_counts = Category.objects.annotate(
post_count=Count('post')
).order_by('-post_count')
# Q オブジェクト(複雑な条件)
from django.db.models import Q
results = Post.objects.filter(
Q(title__icontains='Python') | Q(body__icontains='Python')
)
リレーション操作
# 正引き(ForeignKey先を取得)
post = Post.objects.get(id=1)
print(post.category.name)
# 逆引き(related_name)
user = User.objects.get(username='admin')
user_posts = user.posts.all() # related_name='posts'
# select_related(JOIN、ForeignKeyの最適化)
posts = Post.objects.select_related('author', 'category').all()
# prefetch_related(ManyToManyや逆引きの最適化)
categories = Category.objects.prefetch_related('post_set').all()
マイグレーション
# マイグレーションファイル作成
python manage.py makemigrations blog
# 適用
python manage.py migrate
# 状態確認
python manage.py showmigrations
# SQLを確認
python manage.py sqlmigrate blog 0001
管理画面
# blog/admin.py
from django.contrib import admin
from .models import Post, Category
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'order']
prepopulated_fields = {'slug': ('name',)}
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'status', 'created_at']
list_filter = ['status', 'category', 'created_at']
search_fields = ['title', 'body']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
まとめ
- モデルクラスでテーブル構造を定義
objects.filter() / exclude() / get() でデータを取得
select_related / prefetch_related でN+1問題を防止
makemigrations / migrate でスキーマ変更を管理
admin.py で管理画面をカスタマイズ