本稿は続きものです
おさらい的な
前回はmodels.Modelを使ってモデルを作成したところで終わりました。
前回はよくわかってなかったのですっ飛ばしましたがsqlmigrateというコマンドでmigrateで実行したSQLの結果を確認できるようです。
(Django) C:\User\mysite>python manage.py sqlmigrate polls 0001
BEGIN;
--
-- Create model Question
--
CREATE TABLE "polls_question" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "question_text" varchar(200) NOT NULL, "pub_date" datetime NOT NULL);
--
-- Create model Choice
--
CREATE TABLE "polls_choice" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "choice_text" varchar(200) NOT NULL, "votes" integer NOT NULL, "question_id" integer NOT NULL REFERENCES "polls_question" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "polls_choice_question_id_c5b4b260" ON "polls_choice" ("question_id");
COMMIT;
ということでSQLで実行された内容の確認ができました。
見るにこんな感じでしょうか。
<polls_question>
| id | question_text | pub_date |
|---|---|---|
| : | : | : |
<polls_choice>
| id | choice_text | votes | quetion_id |
|---|---|---|---|
| : | : | : | : |
PythonのData Frameに似てる感じのデータベース(多分これはSQL発だから順序が逆)にデータを格納していく感じですかねー?
APIをつかう
ここからAPIを扱っていきます。
APIってなーに?
APIはApplication Programing Interfaceの略です。そんでApplication Programing Interfaceとは何かというと、特定のアプリ(Application)をコマンドラインなどの外部(Programing)から操作することを可能にする入口(Interface)を提供するのがAPIというのが私の理解です。
その昔、私がTwitterのAPIを使ってTweetの内容を分析用に取得したときはTwitterAPIを有効にしてサポートパッケージを入れたらプログラムの中で特定のTweetデータを取得することができたりしましたんで、外部から様々な形で機能の呼び出しができるAPIというは多様な可能性を感じさせますね。
(WebページについてたりするGoogleMapなんかもAPIを用いた技術です。)
もっといい解説⇒「API」ってつまりどんな技術?用語の意味をおさらいしよう(APIblog)
⇒公式のデータベースAPIについての解説
今回はDjango Shellを使って諸々やっていきます。
このPythonのShellは対話シェルと呼ばれておりPythonが提供するAPIの一つです。
(Django) C:\User\mysite>python manage.py shell
このコマンドでDjangoのShellを呼び出します。
呼び出したらこの対話シェルをつかってデータベースにデータを入れていきます。
# クラスの呼び出し
>>> from polls.models import Choice, Question
# timezoneをdjangoのパッケージから使うので呼び出し
>>> from django.utils import timezone
# インスタンス作成
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
これでQuestionクラスのインスタンスの作成をしました。データベースに以下の内容で入れたということになります。
<polls_question>
| id | question_text | pub_date |
|---|---|---|
| 1 | "What's next?" | datetime.datetime(2020, 8, 8, 4, 8, 56, 186975, tzinfo=) |
以上の内容はShell上で確認できます。
>>> q.id
1
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2020, 8, 8, 4, 8, 56, 186975, tzinfo=<UTC>)
とにかくこれで、まずはQuestionに一つデータをセットすることができました。
ここでチュートリアルではQuestionとChoiceのモデルに__str__()メソッドの追加を行っています。
__str__()メソッドはオブジェクトを表す様々な場面で表示してくれる文字列を決めることができます。
このDjangoのチュートリアルでは
__str__()メソッドの公式のドキュメントはこちら
ではチュートリアルに沿ってやっていきます。
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
# 以下の2行を追加
def __str__(self) -> str:
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)
# 以下の2行を追加
def __str__(self) -> str:
return self.choice_text
これで何が変わったのかを確認します。
>>> from polls.models import Choice, Question
>>> Question.objects.all()
<QuerySet [<Question: What's new?>]>
ここで先ほど設定したとおりにquestion_textが表示されています。(対話シェルを開きっぱなしでmodels.pyを編集していた場合は一度対話シェルをquit()して再度開いてからでないと変更が反映されません。)
ちなみに設定していないと
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>
このような表示になります。__str__()を設定しておくと見やすくなるのでいいですね。
Django adminの紹介
※チュートリアルでは__str__()メソッドの後にさらに対話シェルを用いていろいろとデータベースAPIの動作を試していますが、解説できないので割愛させていただきます。
Webサイトを作る上ではサイトの管理者というのは明確にしておく必要があります。
ということで管理者の作成をしていきます。
(Django) C:\User\mysite>python manage.py createsuperuser
Username: <管理者ユーザー名>
Email address: <管理者のメールアドレス>
Password: <管理者サイトにログインするときに使用するパスワード>
Password (again): <パスワードの確認>
Superuser created successfully.
これで管理者ユーザーの作成が終わったので、管理者サイトにはいってみましょう。(サーバーがpython manage.py runserverで立ち上がっている必要があります。)
127.0.0.1:8000/adminをブラウザに打ってみると以下のサイトが表示されます。

ここのUsernameとPasswordに先ほど設定したものを入れると以下の画面に入ります。

何にも設定を入れていなくてもすでにこれだけのGUI設定画面をDjango は提供してくれます。
このサイト上でpollsアプリの存在を反映させるためにmysite/admin.pyをいじっていきます。
from django.contrib import admin
from .models import Question
# Register your models here.
admin.site.register(Question)
こんな感じで書いたら再度管理者サイトにログインしてみましょう。

すると先ほどはなかったこの項目が現れました。
この項目のAddをクリックしてみると

こんな感じで、さっき対話シェルを使って設定したQuestionの追加がなんとブラウザ上でできるようになります。サイコー!!
にしても大した設定もしてないのにここまでのブラウザ場面を出してくれるなんて本当にDjangoは手厚いですね。
ビューを書く
Adminについては一度終わり、ここからWebページの肝であるページビューの作成に入ります。
DjangoはMTVという考え方があります。MTVはModel、Template、Viewの頭文字をとったものです。
Djangoは
データベースと連携をとるModel、
htmlファイルのTemplate、
ModelとTemplateを組み合わせて画面を作るView
の以上3つが中心でで成り立っているというものです。
ここまではModelを作成してきました。
ここからはViewを作っていきます。
まずはviews.pyに以下の部分を追加します。
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
ここでチュートリアル通りにやってるとあることに気づきます。
以下の文がすでに書かれているのです。
def index(request):
return HttpResponse("こんにちは!")
懐かしい!これは前回の最初のほうに書いたやつ!
ということは最初と同じように、この後urls.pyにルーティングを書いてなんやかんやするんだろうなーと予想がつきます。そしてそれは正解です。
urls.pyにviews.pyで作成したdetail, results, voteへのルーティングを足します。
urlpatterns = [
path('', views.index, name='index'),
# 以下、追加分
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
これらのルーティングはpolls/urls.pyに書いているため、開発サーバーでいえば127.0.0.1:8000/polls以下のpathになります。
pathの第一引数は.../polls/~としてつながるpathを示しています。
<int:question_id>は適当な数字を入れてよいですが、views.pyのdetailなどに渡されます。
ということで127.0.0.1:8000/polls/1とすると以下のようになります。

このように...polls/1/の1がしっかりdetailに渡され反映されています。resultsとvoteも似たような感じになるので割愛。
Templateづくり
続いてTemplateを作ってindexを改造していきます。
pollsファイルの下にtemplatesフォルダを作成します。そしてその下にさらにpollsフォルダを作ります。
ここでさらにpollsフォルダを作ります。このpollsフォルダは今後別のアプリを作ったときにDjangoがTemplateを区別できるようにするためです。
ファイルの階層構造としては

こんな感じです。そしてこの深いほうのpollsフォルダの下にhtmlを書きます。これを間違えるとのちの作業がすべて反映されないので注意!
それどころかエラーになります。(私はtemplatesの下にpollsフォルダ作り忘れて大変なことになりました。)
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
このhtmlファイル、一般的なhtmlとちょっと違くないですか?
{%...%}👈こいつです。こいつは「テンプレートタグ」と呼ばれるもので、if文・繰り返し文や変数のデータをhtmlで表現することができます。
あれ?なんかJavaScriptに似てね?
それではこのindex.htmlを.../polls/で表示してくれるように``views.pyのindex`にも設定を入れます。
# 追加
from .models import Question
# 以下差し替え
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
models.pyからQuestionクラスをインポートする文の追加と既に書いてあるindex関数を以上の内容に書き換えます。
ここで登場するrenderはtemplateと使うデータを指定して画面を作ってくれます。めっちゃ有能ですこれ。
まだ使いこなすには時間がかかりそうですが、今後長い付き合いになりそうです。公式の解説はこちら
以上が差分ですindexは本当にこのチュートリアルの中で何度も書き直されるので、うまくいかないときはここがおかしい時が結構ありました。
エラー送出
チュートリアルでは一度Http404を使ったエラー検知の方法を活用していますが、結果get_object_or_404を使うことになるのでここでは後者のほうのみ書きます。
# 以下追加文
from django.shortcuts import get_object_or_404
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
この差分を見るとreturnのところの返し方がHttpResponseからrenderに変わっています。ということでdetail.htmlが新たに書かれていますが、まだこのファイルを作っていないのでこれから作っていきます。
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
htmlタグとDjangoのテンプレートタグ盛り盛りのコードになっていますね。
<あまりにも雑なタグ説明>
h1:タイトルタグ
ul:順序なしリスト
li:リストの項目
{{ 変数 }}:変数の展開
{% for ... %}:繰り返し文
ハードコードの解消
ここもチュートリアルは寄り道しながら行っているので、ここでは最終的な差分のみ考えていこうと思います。
ここで問題となっているのはindex.htmlの以下のリンクを作っている部分
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
このhrefのところで直接pathを打ってしまっているためコードを書き直したりするときに崩れやすくなってしまうなどのデメリットがあります。
ということで、これを{% urls 'polls:detail' question.id %}で置き換えて、urls.pyにapp_name = 'polls'を追加してうまいことやります。
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
index.htmlがこのようになっていれば、大丈夫です。
127.0.0.1:8000/polls/

127.0.0.1:8000/polls/1/

まーた長くなったのでここでまた切ります。
ありがとうございます。