こんにちは、かつコーチです。
前回はマイグレーションの流れを解説しました。
今回はその前提となるモデル自体の書き方と、データの作成・取得・更新・削除、いわゆるCRUD操作の基本をまとめます。
モデルとは何か
Pythonのクラスがテーブルの設計図になる
モデルとは、データベースのテーブル構造をPythonのクラスとして表現したものです。
Djangoでは、SQLのCREATE TABLE文を直接書くのではなく、モデルクラスを定義することでテーブルの設計を管理します。
# blog/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
このクラスの1つのインスタンスが、テーブルの1行(レコード)に対応します。
よく使うフィールドタイプ
文字列系:CharFieldとTextField
短い文字列にはCharField、長い文章にはTextFieldを使います。
title = models.CharField(max_length=200)
content = models.TextField()
CharFieldはmax_lengthの指定が必須です。
長さの上限を決めない自由記述にはTextFieldを使う、と覚えておけば迷いません。
数値・真偽値系:IntegerField・BooleanField
view_count = models.IntegerField(default=0)
is_published = models.BooleanField(default=False)
数値にはIntegerField、フラグ管理にはBooleanFieldを使います。
どちらもdefaultを指定しておくと、レコード作成時に値を省略できて便利です。
日時系:DateTimeFieldとauto_now系オプション
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
auto_now_add=True:レコード作成時にのみ現在時刻を自動セットするauto_now=True:保存されるたびに現在時刻で上書きする
「作成日時」にはauto_now_add、「更新日時」にはauto_now、と使い分けるのが定番です。
筆者は最初この2つを逆に指定してしまい、「作成日時のはずなのに、編集するたびに日付が変わってしまう」という不具合をリリース後に発見したことがあります。
似た名前のオプションなので、命名の意味をきちんと確認してから使うことをおすすめします。
選択肢を限定する:choices
決まった選択肢の中から選ばせたいフィールドには、choicesを指定します。
class Post(models.Model):
STATUS_CHOICES = [
("draft", "下書き"),
("published", "公開"),
]
status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")
こうしておくと、Django Adminのフォームが自動でドロップダウンになり、想定外の値が入力される事故を防げます。
CRUD操作の基本
CRUDとは、Create(作成)・Read(読取)・Update(更新)・Delete(削除)の頭文字を取った言葉です。
Djangoでは、これらをSQLを書かずにPythonのメソッド呼び出しだけで行えます。
Create:データを作成する
# 方法1:createメソッド
Post.objects.create(title="はじめての投稿", content="よろしくお願いします。")
# 方法2:インスタンスを作ってからsave()
post = Post(title="2つ目の投稿", content="今日も投稿します。")
post.save()
create()は「作成と保存を1行で行う」書き方、save()は「オブジェクトを組み立ててから保存する」書き方です。
保存前にいくつかの処理を挟みたい場合は、後者の書き方が向いています。
Read:データを取得する
# 全件取得
posts = Post.objects.all()
# 条件を指定して取得(複数件)
published_posts = Post.objects.filter(status="published")
# 1件だけ取得
post = Post.objects.get(id=1)
filter()は条件に一致する複数件を返し、get()は条件に一致する1件だけを返します。
get()は該当が0件でも複数件でも例外を投げる点に注意が必要です。
Update:データを更新する
# 1件取得してから更新
post = Post.objects.get(id=1)
post.title = "タイトルを更新しました"
post.save()
# 条件に一致する複数件を一括更新
Post.objects.filter(status="draft").update(status="published")
1件だけ更新するなら「取得 → 値を変更 → save()」、複数件をまとめて更新するならupdate()を使うと効率的です。
Delete:データを削除する
post = Post.objects.get(id=1)
post.delete()
削除は取り消せない操作なので、本番環境で実行する前には対象件数をfilter()で確認する癖をつけておきましょう。
つまずきやすいポイント
get()で存在しないデータを検索して例外が発生する
❌ Before:存在確認をせずにget()だけを使う
def post_detail(request, post_id):
post = Post.objects.get(id=post_id)
return render(request, "blog/detail.html", {"post": post})
このコードは、URLに存在しないpost_idが指定されると、次の例外で画面全体がエラーになります。
django.core.exceptions.DoesNotExist: Post matching query does not exist.
筆者も削除済みの投稿へのリンクが残っていたページで、ユーザーがそのリンクを踏んでサーバーエラー画面を表示させてしまうという事態を経験しました。
✅ After:get_object_or_404を使う
from django.shortcuts import render, get_object_or_404
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id)
return render(request, "blog/detail.html", {"post": post})
get_object_or_404()を使えば、該当データがない場合にサーバーエラーではなく、綺麗な「404 Not Found」画面を返してくれます。
ユーザーへの見え方も、開発者にとってのエラー原因の分かりやすさも、こちらのほうが優れています。
まとめ
この記事のポイント
- モデルはPythonのクラスとして、テーブル構造とフィールドを定義する
CharField・TextField・IntegerField・BooleanFieldなど、用途に合わせてフィールドタイプを選ぶ- CRUD操作は
create()・filter()・get()・update()・delete()で行う - 単一データの取得で存在確認が必要な場面では
get_object_or_404()を使う
次に読むべき記事
- ForeignKey・ManyToManyでリレーションを組む
- QuerySetの基本とフィルタの書き方
- フォーム(forms.Form/ModelForm)でバリデーションを実装する
タグ: Django, 初心者向け, データベース
