Udemy 【3日でできる】はじめての Django 入門 ( Python 3 でウェブアプリを作って AWS EC2 で公開!)
https://www.udemy.com/django-beginner/
の内容の復習がてら。8割がた自分のためですが、お役に立ったらすごくうれしいです。
また、誤りがあればご指摘いただけると幸いです。
2018/11/26 現在、上記のUdemy講座のレクチャー23までを復習した内容になっていると思います。
同じような内容で、より良い記事があります(笑)https://qiita.com/gragragrao/items/373057783ba8856124f3
@gragragrao さんによるもので、非常にわかりやすいです。
(2つの記事を合わせて読むことで、より理解が深まる、ということにしておきます笑)
後半部分は、マジのメモになっております。自分用のノートで、公開しているほうが自分が見やすいからです笑
順次、整理していきます。なんか苦しんでるな、と見守っていただければと思います笑
前提:Python初級、HTML初級、Djangoがインストール済み
※まだまだ学習中なので、理解が進んだ or 誤りに気づいた、タイミングで追加・修正していきます。
注意事項: Djangoのバージョンは1.11を前提としています。2.0については公式チュートリアルなどをご参照ください。
(2.0だとurlの記述がいくらか簡単になっているようです。)
ゴール:自分で作成したHTMLをWebサイトに表示できるようにする。
「プロジェクト」・「アプリケーション」について
Djangoは最終的に作りたいWebサイト全体のことをプロジェクトと呼んでおり、そのプロジェクトの中に、トップページやら投稿画面やら管理者用ページやらのアプリケーションが入っている。
それぞれのアプリケーションの機能(ブラウザにどう表示するか?なんらかのデータベースから情報を持ってくるのか?など)を編集し、そのアプリケーションをプロジェクトに紐づけたりしてWebサイト全体を作っていく。
例:「Qiitaというプロジェクトは、記事の表示、記事の投稿・管理、タイムラインの表示、ランキングなどのアプリケーションが入っている。」
##流れ:
####1. Djangoのプロジェクトファイルを作る
Djangoのプロジェクトフォルダを作りたいディレクトリで、django-admin startproject <プロジェクト名>
####2. プロジェクトに新しいアプリケーションを作成する
python manage.py startapp <アプリケーション名>
####3. 作成したアプリケーションをプロジェクトで使用するものとして追加登録する。
ついでに言語を日本語にして、時刻を日本時間にする。
setting.py
を編集
####4. アプリケーション内のviews.py
を編集する
HttpResponse
を用いて'Hello World!'の表示
####5. ルーティング設定
views.py
とアプリケーションの関連付け
プロジェクトとアプリケーションの関連付け
の2作業で、リクエストされたURLでアプリケーションのviewsが呼び出されるようにルーティングする
####6. 実際にサーバーを動かして確認
python manage.py runserver
####7. 'Hello World'じゃなくて、HTMLファイルを貼る
####8. 更新予定、、、
※順番はこれでなくても全然大丈夫です
ひとつひとつやっていきます。
##1.Djangoのプロジェクトファイルを作る
今回は mywebsite というプロジェクト名とします。
コマンドラインから、Djangoのプロジェクトフォルダを作りたいディレクトリで、
django-admin startproject mywebsite
を入力
Microsoft Windows [Version 10.0.17134.345]
(c) 2018 Microsoft Corporation. All rights reserved.
C:\Users\norimichi>django-admin startproject mywebsite
ちなみに django-admin の admin は administrator '管理者' の意味。
指定したプロジェクト名で、中に様々なファイルとディレクトリができている。
##2.プロジェクトに新しいアプリケーションを作成する
今回は posts というアプリケーション名とします。
コマンドラインから cd mywebsite
でプロジェクトフォルダに移動し、
python manage.py startapp <アプリケーション名>
(アプリケーション名は英単語の複数形が望ましいとのこと)
C:\Users\norimichi>cd mywebsite
C:\Users\norimichi\mywebsite>python manage.py startapp posts
##3. 新しいアプリを、プロジェクトで使用するアプリとして追加登録する。
ついでに言語を日本語にして、時刻を日本時間にする。
posts のapps.py を見ると
from django.apps import AppConfig
class PostsConfig(AppConfig):
name = 'posts'
とあり、先ほど新しく作ったアプリケーション名をもとにクラスが作られている。
クラスの名前を確認。
続いて、プロジェクトフォルダ mywebsite
内のmywebsite/setting.py
を開く。
※今後、プロジェクト内の様々なPythonファイルを編集するので、
Pychramなどでプロジェクトフォルダごと開いておくと便利。
33行目から、INSTALLED_APPS にデフォルトのアプリが登録されているが、ここに新しいアプリを追加記述する。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'posts.apps.PostsConfig' #自分で作ったアプリケーションを追加「postsフォルダのapps.pyにあるPostConfigを追加」
]
ついでに、108行目らへんの言語と時刻についても、それぞれ日本語、東京の時間に設定する。
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
##4. 新しいアプリ内のviews.pyを編集する
ここでは、HttpResponseを用いて'Hello World!'の表示をします。
アプリケーション posts のフォルダに作成されているviews.py
を開き、以下のように追加記述。
「なぜこう書くのか」、や「django.http ってなんなの」という疑問はおそらく使っていくうちに解決されるのではないかと思っています・・・
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
return HttpResponse('Hello World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
これで、views.py
に、'Hello World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
という文字列を返す
関数index
が準備できました。
##5. ルーティング設定
「こういうURLのリクエストが来たら、ここに連れてってあげて」という設定のこと。
(1)views.py
とアプリケーションの関連付け
→アプリケーションposts
のフォルダ内にurls.py
を新規作成して行う。
(2)プロジェクトとアプリケーションの関連付け
→もともとmywebsite
フォルダにあるurls.py
に追記して行う
mywebsite/mywebsite/urls.py
とmywebsite/posts/urls.py
と、2種類のurls.pyを使います。
(「ルーティングをつかさどる」という意味では共通しているので同じurl.py
ですが、それぞれ紐づけする場所が違います。)
(1)、(2)の作業で、最終的に、リクエストされたURLでpostsアプリのviewsが呼び出されるようにルーティングします。
###(1)views.py
とアプリケーションの関連付け
アプリケーションposts
のフォルダ内にurls.py
を新規作成する。
空のurls.py
に、以下を記述。
from django.conf.urls import url
from . import views #同じディレクトリにあるviews.pyを呼び出し
urlpatterns = [url(r'^$', views.index, name='index')] #views.pyのindex関数を呼び出すよう設定
r'^$' のところは、正規表現でこういうURLにパターンマッチしたら、という意味ですが
今回は特に指定なし、なのでこのような書き方になっています。
###(2)プロジェクトとアプリの関連付け
もともとmywebsite
フォルダにあるurls.py
を開く。
以下のように追加記述。
#16行目から抜粋
from django.conf.urls import include, url #includeを追加でインポート
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^posts/', include('posts.urls')) #「postsが含まれるURL、はpostsフォルダのurls.pyを参照してね」という意味
#正確には、ベースとなるアドレス(ホスト名ともいう。Djangoの内蔵サーバーでWebサイトを立ち上げた場合は、`http://127.0.0.1:8000/`のこと。)の後の、先頭にposts/がある場合を指します!
]
これで
(1)views.py
で定義したindex関数とアプリケーションの関連付け
(2)プロジェクトとアプリケーションの関連付け
ができました。
###6. 実際にサーバーを動かして確認
コマンドラインでプロジェクトフォルダに入り、 python manage.py runserver
を実行。
C:\Users\norimichi\mywebsite>python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
November 26, 2018 - 17:24:10
Django version 1.11, using settings 'mywebsite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
migrationしないとダメじゃないか、と怒られていますが、とりあえず今は大丈夫。
これでDjangoの内蔵サーバーにてとりあえずWebサイトの動きを確認できます。
特にオプションを入れなければ、この段階では、自分のPCでしかページにアクセスできないようです。
あと、本番にはこの内蔵サーバーは使っちゃダメとのこと。
さっそくhttp://127.0.0.1:8000/
に、ブラウザでアクセスしてみましょう。するとエラーが出ます。当然です。
setting.py
では
#16行目から抜粋
from django.conf.urls import include, url #includeを追加でインポート
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^posts/', include('posts.urls')) #「postsが含まれるURL、はpostsフォルダのurls.pyを参照してね」という意味
]
としており、今のところは先ほど作ったhttp://127.0.0.1:8000/posts
か、
デフォルトである管理画面http://127.0.0.1:8000/admin
にしかルーティングされていないためです。
http://127.0.0.1:8000/posts
にアクセスしてみると、
ちゃんと表示されました。表示されずにエラーが出る場合、自分は大体コードミスが原因でした。
閲覧を終了するときは、いったんCtrl + C でサーバーを停止します。
ちなみに、サーバーを動かした状態でファイルに変更を加えても、表示されるWebページはちゃんと反映してくれます。
(例外となる操作もあるようです。)
###7. 'Hello World'じゃなくて、HTMLファイルを貼る
アプリケーション(ここではposts)のフォルダにtemplatesフォルダを作る。
さらに、その中に(アプリケーション名と同じ)postsフォルダを作る。
こういう風に作る決まりらしいです。
HTMLファイルはとりあえず適当に書きます。
<!DOCTYPE html>
<html lang="ja-jp">
<head>
<meta charset="UTF-8">
<title>投稿一覧</title>
</head>
<body>
<h1>おめでとう!Djangoでの初めてのHTMLファイルです</h1>
</body>
</html>
さて、とりあえずHTMLファイルはできたけど、views.py
がそのままではこのファイルはスルーされる。
views.py
に「このファイルを見に行きますよー」、と記述する必要がある。
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def index(request):
#return HttpResponse('Hello World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
#こちらは一旦コメントアウト
return render(request, 'posts/index.html') #新たにrender関数を追加
確認のため、再びpython manage.py runserver
でサーバーを立ち上げて、http://127.0.0.1:8000/posts
にアクセスしてみます。
(サーバー起動しっぱなしの場合はそのままページの再読み込みなどで大丈夫です。)
指定したHTMLファイルをWebページに表示することができました。
------------------------------------こっからは自分用メモ-------------------------------------------------------
データベースの利用
models.pyにクラス宣言
データベースで使用するメソッドを定義
python manage.py makemigrations
python manage.py migrate
新しくできたファイルを検知して更新
を実行
管理者アカウント作成python manage.py createsuperuser
admin.py
を編集して、管理者メニューを追加
from django.contrib import admin
# Register your models here.
from .models import Post
admin.site.register(Post)
####index.htmlをに投稿したタイトルを最新順にならべる、ついでに投稿日も入れる
<!DOCTYPE html>
<html lang="ja-jp">
<head>
<meta charset="UTF-8">
<title>投稿一覧</title>
<link rel="stylesheet" type="text/css" href="stylesheet.css">
</head>
<body>
<header>
<a href=#>Profile<a/>
<a href=#>News<a/>
<a href=#>Works</a>
</ul>
</header>
<div class="top-wrapper">
<h1>kounorimichのブログへようこそ</h1>
<h2></h2>
</div>
<div class="container">
<h2>最新の投稿</h2>
{% for post in posts.all %} <!--#allつけなくても行けます-->
#{{post.id}} :
{{ post.title }} published: {{ post.published }}
<br /><br />
<img src="{{post.image.url}}">
{{ post.summary }}
<br /><br />
{% endfor %}
</div>
</body>
</html>
####管理画面で、投稿のタイトルを表示できるようにする方法
from django.db import models
# Create your models here.
class Post(models.Model):
title = models.CharField(max_length=100)
published = models.DateTimeField()
image = models.ImageField(upload_to='media/')
body = models.TextField()
def __str__(self):
return self.title
__str__(self)
は特殊メソッドいうもので、クラスを扱うときに、よく使う関数が、Pythonにデフォルトで実装されている便利なもの。
__init__(self)
とか、、、、えー、__init__
とかのことですね。
この__str__(self)
は、文字列として呼び出されたときに、どういう文字列を返すか設定できます。
Djangoの管理サイトは、各々のPostオブジェクトを管理画面で表示する際、指定がなければPost.objectとしちゃうんですね。
それを避けるために、呼ぶときは、私のタイトルで呼んでね、といっとくわけですね。
htmlのテンプレートを動的にする。(データベースと連動させる)
views.py にクラスPostをインポート。
あとでHTMLに埋め込んで使う変数postにpublishedで降順に並んだPostオブジェクトのリスト(?)を代入しておく。
さらに、render関数の引数として、HTMLで使う'posts'には、ここで定義したpostが入りますよ、ということを、辞書型で知らせる。
from django.shortcuts import render
from django.http import HttpResponse
from .models import Post
# Create your views here.
def index(request):
#return HttpResponse('Hello World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
posts = Post.objects.order_by('-published')
return render(request, 'posts/index.html', {'posts': posts})
あとは、HTMLファイルのほうに、'{{posts}}'を埋め込む。
###staticなファイルの読み込み(ここがDjangoで一番ややこしい!!とのこと!!!)
HTML ファイルで、<img src="{{post.image.url}}">
とやれば表示できると思いきやエラー。
データベーなどの動的なファイルは読み込まるようになっているが、動画や写真などのメディアつまり
静的なファイルを読み込めるようになっていないから。
呼び出すURLを設定+フォルダの指定が必要
プロジェクト名のフォルダに(mywebsite/mywebsite/)あるurls.pyとsetting.pyを編集する。
from django.conf.urls import include, url
from django.contrib import admin
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
url(r'^posts/', include('posts.urls')),
url(r'^admin/', admin.site.urls),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
#staticなファイルについては、setting.pyのMEDIA_URLと、MEDIA_ROOTを見てねー
続いてアプリケーションフォルダのsetting.pyに以下の二行を足す。
MEDIA_URL = '/pics/'
MEDIA_ROOT = BASE_DIR #これで、ルートフォルダ直下にあるmediaフォルダからファイルを参照できるようになる
'''BASE_DIRとは、setting.pyでもともと宣言されている変数で、
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) を指している。
ルートフォルダのこと。一番上のmywebsiteのこと。 '''
てっきりsetting.py
で宣言しているように、pics
というフォルダを作ってそこに写真を移動するのかな、なんて思ってたら、なんとこれだけで写真が表示される。あれ。なんでだろう。と思ったら仮想のURLを作っていくれているということのみたいです。うーん、なるほど。
picsなんてフォルダは作成されていないのですが、動的に生成しているそうです。
####投稿をクリックしたときに、各投稿の詳細に飛べるようにする。
アプリケーションpostsには、Modeのインスタンス(各投稿のこと)を作った時点、内部的にIDが割り当てられている。
まず、各投稿のタイトルの前に、idを表示するように編集する。
index.html
に以下のように追記。
<h2>最新の投稿</h2>
{% for post in posts.all %}
#{{post.id}} : <!--追記分-->
{{ post.title }} published: {{ post.published }}
<br /><br />
<img src="{{post.image.url}}">
{{ post.summary }}
<br /><br />
Django、なかなか親切ですね。この勢いで(?)、http://127.0.0.1:8000/posts/1
とかやると、もう勝手に詳細画面に行ってくれるのかな、とか思ったのです。
「ページが見つからないじゃないか!」と怒られてしまいました。
まあ、当然ですね。そもそも詳細ページなんて作成もしてないし。。
####URLからIDを取得する
urlpatterns = [
url(r'^posts/', include('posts.urls')),
url(r'^admin/', admin.site.urls),
url(r'^posts/(?P<post_id>[0-9]+)/$', posts.view.post_detail, name='post_detail')
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
#staticなファイルについては、setting.pyのMEDIA_URLと、MEDIA_ROOTを見てねー
####http://127.0.0.1:8000/posts/1
で1番目の投稿の詳細画面を表示する
from django.conf.urls import include, url
from django.contrib import admin
from django.conf.urls.static import static
from django.conf import settings
from posts import views #追記分
urlpatterns = [
#url(r'^posts/', include('posts.urls')),
url(r'^posts/$', posts.views.index, name='index'),
url(r'^admin/', admin.site.urls),
url(r'^posts/(?P<post_id>[0-9]+)/$', views.post_detail, name='post_detail') #追記分
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
from posts import views
別のディレクトリにあるviews.pyを参照するため、from postsとつけております。
url(r'^posts/(?P<post_id>[0-9]+)/$', views.post_detail, name='post_detail')
?P<post_id>
というのはDjango特有の書き方です。
単に「ホスト名の後にpost/
が来て、その後に数字1文字以上がきて、終わる」という指定はr'^posts/[0-9]+/$)
でOKなのですが、この後に書くpost_detail関数では、その数字でどの投稿を呼び出すか判断するわけなので、引数としてpost_id、つまりこの数字を渡してやる必要があります。Djangoでは (?P<参照先の関数の引数名>正規表現)
としてやることで 通常の正規表現に加えて、「あてはまった文字列って、参照先で使いますよね?じゃあこのまま参照先の関数にこの値を引数として渡すよー」ってな感じでやってくれるみたいです。
次に`views.pyに、その関数を追加します。
from django.shortcuts import render
from django.http import HttpResponse
from .models import Post
# Create your views here.
def index(request):
#return HttpResponse('Hello World!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
posts = Post.objects.order_by('-published')
return render(request, 'posts/index.html', {'posts': posts})
def post_detail(request, post_id): #追加した関数。仮引数にpost_idをしているのは、偶然ではありません!
post = Post.objects.get(pk=post_id)
return render(request, 'posts/post_detail.html', {'post': post})
さっき(?P)という記述をしたので、当然この関数の仮引数としてpost_idを指定します。ここがそろっていないと、関数のどの仮引数にいれていいかわからないからです。
もう一度(自分に)解説してみます。
urls.pyで正規表現がマッチした箇所を、参照先の関数で使いたいので、関数の仮引数と名前を同じにする必要があるのです!
もしdef post_detail(request, post_id, hoge)
と、post_id以外の引数があった場合、どこに正規表現でマッチした数字を入れればいいか判断できないからです。
ちなみに、requestという仮引数がなんなのかは、まだわかっておりませんが、たぶんクラスにおけるselfみたいなもので、単にDjangoの動作に必要な記述、くらいな理解です。全然違ったらごめんなさい。
そして、index関数では投稿の一覧が欲しかったので、あとでfor文で回す用に複数のPostオブジェクトを取得してます。(posts = Post.objects.order_by('-published')
の部分ですね。HTMLのテンプレートで、{% for post in post %} として一覧を表示しました。)
しかし、今回は一つの投稿を取り出したいのです。しかもhttp://127.0.0.1:8000/posts/1
とリクエストが来たならpost_idが1の投稿をです。
なので、post = Post.objects.get(pk=post_id)
としています。Djangoのオブジェクトがもつgetというメソッドは、pk (primal keyのことらしいです)を引数として渡してやると、内部的にDjangoがつけてくれているindex番号が合致するオブジェクトを取得できるものです。
あとはpost_idだけじゃなくてPostのtitleやpublishedやら、詳細画面にはいろいろ入れたいので、htmlファイルを参照する際に、{'post':post}
としています。これにより、参照先のhtmlで、「postっていえば通じるよ。post.titleっていえばtitle持ってくるよ。」状態になります。)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>タイトル: {{post.title}}</title>
</head>
<body>
<h1>タイトル: {{post.title}}</h1>
<p>投稿日: {{post.published}}</p>
<br /><br />
<img src="{{post.image.url}}">
<p>本文: {{post.body}}</p>
</body>
</html>
ちゃんと一つの投稿、ひとつのPostオブジェクトだけ取り出してhtmlに組み込んで表示ができましたね!
ちょっとブログっぽくなってきてる、、、!
ただ、フライングで同じフォルダにcssファイル入れてhtmlにも呼び出すコード書いてるんですが、いまのとこガン無視です笑。
たぶんあとで出てくるでしょう。