Skip to content

Djangoでウェブアプリを作る(9) – APIで遊んでみる

前回の記事:
Djangoでウェブアプリを作る(8)

今回参照する公式チュートリアル:
https://docs.djangoproject.com/ja/3.2/intro/tutorial02

前回は、作成したモデルをデータベースに適用した。今回は、Pythonシェル上でデータベースの操作をPythonコードで直接行うという趣旨のようだ。

スポンサードリンク

データベースをPythonでいじる

さぁ、Python 対話シェルを起動して、Django が提供するAPIで遊んでみましょう・・・

ああ、良かった。今日は、楽な回だな・・・

えーっと、次のコマンドを実行しろ・・・

python manage.py shell

はい。実行した。ふむふむ、Django仕様のPythonシェルか・・・

C:\django_project\mysite>python manage.py shell
Python 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

シェルに入ったらデータベースAPIの世界を探検してみましょう・・って、何をしているのか分からない・・ とりあえず、コメント部分を日本語にしてみる。

 # 作成したモデルクラスをインポートします。
from polls.models import Choice, Question

# システムにはまだ質問がありません。
>>> Question.objects.all()
<QuerySet []>

# 新しい質問を作成します。
# タイムゾーンのサポートはデフォルト設定ファイルで有効になっているため、
# Djangoはpub_dateにtzinfoを使用した日時を想定しています。
# datetime.datetime.now()の代わりにtimezone.now()を使用すると正しい処理が実行されます。
>>> q = Question(question_text="What's new?", pub_date=timezone.now())

# オブジェクトをデータベースに保存します。save()を明示的に呼び出す必要があります。
>>> q.save()

# これでIDができました。
>>> q.id
1

# Python属性を介してモデルフィールド値にアクセスします。
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=)

# 属性を変更してからsave()を呼び出して値を変更します。
>>> q.question_text = "What's up?"
>>> q.save()

# objects.all()は、データベース内のすべての質問を表示します。
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

ああ、データベースをPythonでいじるってことね。遊ぶというには全く楽しくはないが・・・

とりあえず、polls/models.pyに定義したQuestionクラスとChoiceクラスをインポートね・・・

from polls.models import Choice, Question

それから・・・登録した質問の全てを表示するということか? 

Question.objects.all()

何か、エラーコードが返ってくるんだが・・・・

どうもテーブル自体が作られてないようだ。前回、モデルを適用した時にテーブルが自動で作られたはずなんだが・・・何か見落としがあるのか?

>>> Question.objects.all()
Traceback (most recent call last):
省略・・・・・・・・・・・・
・・・・・・・・
sqlite3.OperationalError: no such table: polls_question
省略・・・・・・・・・・・・・・・
・・・・・・・・・・・・
django.db.utils.OperationalError: no such table: polls_question

エラーの原因

調べた結果、前回、モデルを適用する手順を一つ飛ばしていることに気づく。

次のコマンドを実行していなかった。テーブルが作られてないはずだ。

python manage.py migrate

migrateコマンドを実行した後、再び、Question.objects.all()を実行してみる。

C:\django_project\mysite>python manage.py shell
Python 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet []>
>>>

QuerySetというリストが空になっている。何もデータを入れてないからな。

次は、Questionクラスに ” What’s new? ” という質問と、回答となる現在日時を引数に渡して、インスタンスを生成。それをセーブしている。

これが、最初に登録したデータ・・ということになるのだろうか?

>>> from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
>>> q.save()

登録したデータのIDナンバーと、テキストの内容、登録日時を確認してみる。

>>> q.id
1
>>> q.question_text
'What's new?'
>>> q.pub_date
datetime.datetime(2021, 8, 23, 15, 40, 5, 807064, tzinfo=)

ああ、チュートリアルと同じだ。合ってる。

次に質問内容を ” What’s new? ” を ” What’s up? ” に変更してセーブ。

それから、再び、QuerySetリストを確認すると、確かにデータが一つ格納されている。

ああ、確かにデータベースの操作は楽だな・・・

>>> q.question_text = "What's up?"
>>> q.save()
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
>>>

model.pyに__str__()を追加

ちょっと待ってください。<Question:Question object (1)> は、このオブジェクトの表現としてまったく役に立ちません。Questionモデルを編集してこれを修正しましょう。__str__()メソッドを Question と Choice の両方に追加します。

え、何ですか?「役に立たない」という意味が分からないが・・・ とりあえず言われたとおり追加してみる。

mysite/polls/models.py

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
    
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
    

それから、もう一つ、メソッドを追加しろとあるな。

was_published_recently()は、多分、日時を比較した結果を返すみたいだ。

mysite/polls/models.py

from django.db import models
from django.utils import timezone
import datetime

class Question(models.Model):

    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

     def __str__(self):
        return self.question_text

     def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

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

再びPythonでデータベースをいじる

データベースの構造を変更したわけじゃないから、migrateコマンドは実行しなくていいんだよね?

では、Pythonシェルをもう一度立ち上げて・・・あれ、エラーが出たぞ?

c:django_project\mysite>python manage.py shell
Traceback (most recent call last):
省略・・・・  
IndentationError: unexpected indent

インデントエラー? ハッ・・・もしかして、manage.py経由でmodels.pyにアクセスしてないか?

polls/models.pyを開いて各行のインデントを確認すると、確かに不必要なスペースが紛れ込んでいた。さっき、コピペした時に入ったんだな。

スペースを削除し、もう一度、コマンドを実行すると・・・あ、出た。やっぱり、manage.py から、一回、見に行ってるんだな。

C:\django_project\mysite>python manage.py shell
Python 3.9.6 (tags/v3.9.6:db3ff76, Jun 28 2021, 15:26:21) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

では、チュートリアルどおりにデータベースにアクセスしてみよう。QuerySetリストには、先ほど変更した ” What’s up? ” が入ってるな。

>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
>>>

次に、id=1 のデータを抽出。はいはい、出来てますね。

>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>>

では、文字列の先頭が ” What “ で始まるデータを抽出。

>>> Question.objects.filter(question_text_startswith='What')
<QuerySet [<Question: What's up?>]>
>>>

あら、エラーだ。’question_text_startswith’ なんてキーワードは無いと言っている。 

Traceback (most recent call last):
省略・・・・・・・・・・・・・・・・・
raise FieldError("Cannot resolve keyword '%s' into field. "
django.core.exceptions.FieldError: Cannot resolve keyword 'question_text_startswith' into field. Choices are: choice, id, pub_date, question_text

よく見ると、公式チュートリアルでは、’question_text__startswith’ となっている。

・・・お分かり頂けただろうか? text と startswith の間のアンダースコアが二つある・・・なんじゃそりゃ!

改めて書き直す。エラーは消えた。

>>> Question.objects.filter(question_text__startswith='What')
<QuerySet []>
>>>

次は、今年、登録されたデータを抽出。OK! 

” pub_date__year ” もアンダースコアが二つになってるな。こういうルールなのか?

さっきは、filter だったが、getを使っているな。何か違うのだろうか?

>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

次は、あえて存在しないデータにアクセス。チュートリアルと同じく ” DeseNotExist ” だ。

>>> Question.objects.get(id=2)
Traceback (most recent call last):
省略・・・・・・
polls.models.Question.DoesNotExist: Question matching query does not exist.
>>>

次が主キーによるデータ取得らしい。チュートリアルのコメントには主キーで指定する方が一般的とある。この場合、ID指定と同じだ。

” pk ” プライマリーキーの略だね。

>>> Question.objects.get(pk=1)
<Question: What's up?>

主キー指定で取得したデータから、先ほどmodels.pyに追加したメソッドを呼び出してるな。
最近のデータなら True。

>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

選択肢を追加する

さて・・・・ ここからが、ちょっとややこしいぞ。

主キーによってデータ(Questionオブジェクト)を取得する。

QuestionオブジェクトのフィールドQuerySetリストは、まだ空の状態だ。

>>> q = Question.objects.get(pk=1)
>>> q.choice_set.all()
<QuerySet []>

それから、三つの選択肢を追加する。

全て追加したら、Questionオブジェクトを変数cに格納。

なぜ、cに格納するのか分からないが・・・

>>> 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>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

質問の確認。

そして、QuerySetリストの確認。先ほど追加した三つの選択肢が入ってますね。

>>> c.question
<Question: What's up?>
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

この部分は、意味が分からない・・・ChoiceクラスメソッドからもQuerySetリストがのぞけますということだろうか?

>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

それから、フィルターキーワード ” question__pub_date__year=current_year ” だが、ダブルアンダースコアが二か所使われている。コメントには「関係を区切るにはダブルアンダースコアを使う」とあるので、区切りであることは分かるが、いまいち、どういうルールで命名されているのか分からない。

どこかで、また出てくるかもしれんな。

さて、最後に追加したばかりの選択肢の一つを削除してみる。

>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
(1, {'polls.Choice': 1})

さて、今回は、データベースをPythonシェルを使って操作してみたが、まだ、システムの全容がつかめない。

全く楽な回ではなかった。このまま、行けるのか不安だ。

この記事をシェア

Comments are closed, but trackbacks and pingbacks are open.