概要
Pythonシェルの環境下でモデル操作をしてみる。Pythonドキュメントのチュートリアルの流れに沿っている。Laravelのtinkerでの操作と似ている。
シェルの起動・終了
シェルの起動はmanage.py
のshell
コマンド。終了はexit()
。
1 2 3 4 5 6 7 |
$ ./manage.py shell Python 3.6.8 (default, Nov 16 2020, 16:55:22) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> exit() $ |
モデルの操作
準備・確認
モデル操作の開始時にモデルのインポートが必要。
from アプリケーション.models import モデルクラス
1 |
>>> from polls.models import Choice, Question |
タイムゾーン表示のためにtimezone
オブジェクトをインポート。
1 |
>>> from django.utils import timezone |
timezone.now()
で現在時刻を表示してみる。内部的にはUTCで扱われているので、その時点の日本時間とは9時間ずれている。
1 2 |
>>> timezone.now() datetime.datetime(2022, 3, 18, 8, 52, 16, 897846, tzinfo=<UTC>) |
モデルの全データの取得。まだデータは登録されていない。
1 2 |
>>> Question.objects.all() <QuerySet []> |
データの作成・保存・編集
作成
Question
モデルのコンストラクターでデータを作成。この段階ではメモリー上に作成されただけで、データベースには保存されていない。
1 |
>>> q = Question(question_text="What's new?", pub_date=timezone.now()) |
pprint
とvars
でデータの内容を確認してみる。
1 2 3 4 5 6 |
>>> import pprint >>> pprint.pprint(vars(q)) {'_state': <django.db.models.base.ModelState object at 0x7f423cecf048>, 'id': None, 'pub_date': datetime.datetime(2022, 3, 18, 8, 53, 29, 990021, tzinfo=<UTC>), 'question_text': "What's new?"} |
各フィールドを見てみる。まだデータベースに登録されていないので、id
は空になっている。
1 2 3 4 5 |
>>> q.id >>> q.question_text "What's new?" >>> q.pub_date datetime.datetime(2022, 3, 18, 8, 53, 29, 990021, tzinfo=<UTC>) |
保存
save()
メソッドでデータベースに保存。
1 |
>>> q.save() |
MySQLでテーブルへの保存を確認。
1 2 3 4 5 6 7 |
mysql> select * from questions; +----+---------------+----------------------------+ | id | question_text | pub_date | +----+---------------+----------------------------+ | 1 | What's new? | 2022-03-18 08:53:29.990021 | +----+---------------+----------------------------+ 1 row in set (0.00 sec) |
データベースに保存されたのでid
がセットされた。
1 2 |
>>> q.id 1 |
編集
データのquestion_text
の内容を書き替えてデータベースに保存。
1 2 |
>>> q.question_text="What's up?" >>> q.save() |
データベースを確認して内容が変更されていることを確認。
1 2 3 4 5 6 7 |
mysql> select * from questions; +----+---------------+----------------------------+ | id | question_text | pub_date | +----+---------------+----------------------------+ | 1 | What's up? | 2022-03-18 08:53:29.990021 | +----+---------------+----------------------------+ 1 row in set (0.00 sec) |
データの取得
Managerオブジェクト
個別のモデルごとにManager
クラスのインスタンスが生成され、モデルのobjects
メンバーとして準備される。データの取得やフィルタリングなど操作は、このインスタンスのメソッドによって行われる。
モデル.objects
all()による全データの読み込み
all()
メソッドによってテーブルに登録された全データを読み込める。
モデルクラス.objects.all()
Qustion
には1つだけデータが登録されている。
1 2 |
>>> Question.objects.all() <QuerySet [<Question: Question object (1)>]> |
__str__の定義
データ表示が殺風景なオブジェクトの表示になっているが、これを変更してquestion_text
やchoice_text
を表示させるようにする。具体的には各モデルに__str__
メソッドを定義する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') def __str__(self): return self.question_text py class Meta: db_table = 'questions' class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0) def __str_(self): return self.choice_text class Meta: db_table = 'choices' |
これでデータ内容の表示がわかりやすくなった。
1 2 |
>>> Question.objects.all() <QuerySet [<Question: What's up?>]> |
get()による1つのデータの抽出
get()
メソッドで条件を指定してデータを1つ取り出せる。
モデルクラス.objects.get(条件)
id
が1
のデータを取り出す。
1 2 |
>>> Question.objects.get(id=1) <Question: What's up?> |
該当するデータがない場合はModel.DoesNotExist
例外、複数のデータが存在する場合はModelMultipuleObjectsReturned
例外。以下はDoesNotExist
の例。
1 2 3 4 |
>>> Question.objects.get(id=2) Traceback (most recent call last): .... polls.models.Question.DoesNotExist: Question matching query does not exist. |
主キー(primary key)を指定してでも取り出せる。Djangoではprimary keyのショートカットとしてpk
が準備されている。
1 2 |
>>> Question.objects.get(pk=1) <Question: What's up?> |
フィルターによる抽出
filter()
メソッドでデータを抽出できる
モデルクラス.objects.filter(条件)
以下はid=1
の条件で抽出した結果で、今回の例の場合はデータを1つ含むQuerySet
が得られる。
1 2 |
>>> Question.objects.filter(id=1) <QuerySet [<Question: What's up?>]> |
存在しないid
で抽出すると空のQuerySet
が得られる。
1 2 |
>>> Question.objects.filter(id=2) <QuerySet []> |
文字列の前方一致の抽出例。引数のフィールドにアンダースコアを2つ(‘__
‘)連ねて様々な条件を指定できる。フィルターの条件の書き方はこちらに整理した。
フィールド__条件=値
ここではフィールド__startswith='文字列'
として、フィールドが指定した文字列で始まるデータを抽出している。
1 2 |
>>> Question.objects.filter(question_text__startswith='What') <QuerySet [<Question: What's up?>]> |
登録の年が同じデータを抽出する例。まず現在日時の年を取り出して変数current_year
にセットして、pub_date
の年がこれに等しいデータをフィルタリング。datetime
型のフィールドの年の条件でフィルタリングするにはフィールド名__year=年
とする。__year
のアンダースコアは2つ。
1 2 3 |
>>> current_year = timezone.now().year >>> Question.objects.filter(pub_date__year=current_year) <QuerySet [<Question: What's up?>]> |
リレーションがあるモデル
親データに属する子データ群
親データに属する(これを参照している)データのManager
オブジェクトを取り出すには以下のように書く。
親データ.小文字の子モデル名_set
たとえば変数q
が Question
モデルのid=1
のデータを指している状態で、これに属するChoice
データ群の全てを取り出すと以下のようになる。まだChoice
データを1つも登録していないので結果は空。
1 2 |
>>> q.choice_set.all() <QuerySet []> |
親データに属する子データの作成
親データに関連付けられた子データを新たに作成するときは以下のように書く。
親データ.小文字の子モデル名_set.create(...)
q
に関連付けられたChoice
データを3つ登録。
1 2 3 4 5 6 |
>>> q.choice_set.create(choice_text='Not much', votes=0) <Choice: Not much> >>> q.choice_set.create(choice_text='The sky', votes=0) <Choice: The sky> >>> q.choice_set.create(choice_text='Just hacking again') <Choice: Just hacking again> |
MySQLでテーブルの内容を確認すると、Question
テーブルのid=1
のデータを参照する3つのデータが登録されている。
1 2 3 4 5 6 7 8 9 |
mysql> select * from choices; +----+--------------------+-------+-------------+ | id | choice_text | votes | question_id | +----+--------------------+-------+-------------+ | 1 | Not much | 0 | 1 | | 2 | The sky | 0 | 1 | | 3 | Just hacking again | 0 | 1 | +----+--------------------+-------+-------------+ 3 rows in set (0.00 sec) |
<閑話休題>
“What’s up?”はくだけた言い方の挨拶で「調子はどう?」、「何か(変わりは)あったかい?」といった感じ。”Not much.”は「普通だよ」とか「特にないね」といった常套句のようだ。”The sky.”はupにひっかけて「(上は)空さ」というジョーク表現(無理やり日本語字幕にするなら「何かあったかい?」「暖かいね」とかなどと考えたが単なる駄洒落か・・・)。”Just hacking again.”はよくわからなかった。
子データのall/count/filter
登録されたデータをall
メソッドで取り出してみる。
1 2 |
>>> q.choice_set.all() <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> |
データの個数はcount
メソッドで。
1 2 |
>>> q.choice_set.count() 3 |
子データ群のフィルタリングは通常と同じように指定。
1 2 3 4 |
>>> q.choice_set.filter(choice_text__startswith='Not') <QuerySet [<Choice: Not much>]> >>> q.choice_set.filter(votes=0) <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> |
親データの条件によるフィルタリング
親データの条件でフィルタリングする場合、filter()
の中で小文字の親モデル名を使うが、直接以下のように書くとエラーになる。
filter(小文字の親モデル名_フィールド__条件)
→エラー
以下の例では、Choice
の親データquestion
のquestion_text
フィールドの条件でフィルタリング仕様としてエラーになっている。
1 2 3 4 5 |
>>> q.choice_set.filter(question_question_text__startswith='What') Traceback (most recent call last): .... django.core.exceptions.FieldError: Cannot resolve keyword 'question_question_text' into field. Choices are: choice_text, id, question, question_id, votes |
親データのフィールドの条件の場合、親データとフィールドの間のアンダーラインを2つにする。
親データ.子データ_set.filter(親データ__フィールド__条件)
たとえばQuestion
とChoice
の場合は以下のようになる。
q.choice_set.filter(question__フィールド__条件)
以下はテキストが'What'
で始まる親データに属する子データを抽出している。
1 2 |
>>> q.choice_set.filter(question__question_text__startswith='What') <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> |
以下は登録年が今年の親データに属する子データを抽出している。
1 2 |
>>> q.choice_set.filter(question__pub_date__year=current_year) <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> |
データの削除
まずテキストに'huck'
(大文字小文字を問わない)を含むchoice
データをデータベースから得る。
1 2 3 4 5 6 7 |
>>> c = q.choice_set.get(choice_text__icontains='hack') >>> pprint.pprint(vars(c)) {'_state': <django.db.models.base.ModelState object at 0x7efcb4a041d0>, 'choice_text': 'Just hacking again', 'id': 3, 'question_id': 1, 'votes': 0} |
そのデータをdelete
メソッドでデータベースから削除。
モデル.delete()
1 2 |
>>> c.delete() (1, {'polls.Choice': 1}) |
MySQLで削除されていることを確認。
1 2 3 4 5 6 7 8 |
mysql> select * from choices; +----+-------------+-------+-------------+ | id | choice_text | votes | question_id | +----+-------------+-------+-------------+ | 1 | Not much | 0 | 1 | | 2 | The sky | 0 | 1 | +----+-------------+-------+-------------+ 2 rows in set (0.00 sec) |