Django
Anaconda

Djangoでブログをつくるまで①

https://qiita.com/TsubasaTakagi/items/62c2d43a61e87ea1626fの続き

テンプレートの追加

DjangoではMTVモデルが採用されており、今回はTのtemplate部分を書いて行く。
MTVモデルについてはhttp://farewell-work.hatenablog.com/entry/2017/05/07/160503がわかりやすかったので貼っておく。

まず、postsディレクトリ内にtemplatesディレクトリを追加してあげる。
さらに、そのtemplatesディレクトリの中にアプリケーションと同じ名前のディレクトリをつくる。今回の場合はpostsディレクトリをつくる。
最後に、先ほど作ったpostsディレクトリ内に作りたいtemplatesのファイル、ここではindex.htmlを作成する。

すると、
スクリーンショット 2018-01-01 9.55.19.png

ディレクトリ全体として、いまはこんな感じになっているはずだ。

views.py
def index(request):
    return HttpResponse("Hello World! このページは投稿のインデックスです。")

から直接文字列を返すのではなく、いま作成したindex.htmlに埋め込んで返してあげる、という作業をする。

それではまず、index.htmlに最低限のhtmlの記述をしてあげる。

index.html
<!DOCTYPE html>
<html lang = "ja-jp">
<head>
    <title>投稿一覧</title>
</head>
<body>
    <h1>これは投稿一覧のページです!</h1>
</body>
</html>

次に、このhtmlファイルを返すような記述をviews.pyにしてあげる。

views.py
  return HttpResponse("Hello World! このページは投稿のインデックスです。")

という1行を消して、

views.py
def index(request):
  return render(request, 'posts/index.html')

と、書き換える。
renderにrequest関数を渡し、templateの参照先を記す。
ここでは、postsディレクトリ内のindex.htmlなので'posts/index.html'と書いてある。

これでもう一度http://127.0.0.1:8000/posts/にアクセスして見ると、

スクリーンショット 2018-01-02 11.50.05.png

同じように文字列を返してくるのがわかる。

ブログの記事をデータベースに格納する

データベースをあたかも変数かのように扱えるようにする役割をもつclassをmodels.pyに定義してあげる。

models.py
class Post(models.Model):
     title = models.CharField(max_length=100)
     published = models.DateTimeField()
     image = models.ImageField(upload_to = 'media/')
     body = models.TextField()

それぞれ右辺の変数にmodelsの中で用意されている雛形を利用する。()の中身はその型の制限や参照先を記述している。
たとえば、CharField(max_length=100)であれば、CharFieldは文字列を呼び出し、(max_length=100)は文字の上限を100文字と制限している。
ImageField()は画像を呼び出す役割をしており、その参照先としては'media'に保存した画像つかうと指定してある。

そして、データベースのテーブルをつくるための命令をするためのファイルを作成するコマンドをターミナルに打ち込む。

python manage.py makemigrations

すると、postsディレクトリのmigrationsディレクトリ内にmigrationファイルが作成されているのが確認できる。

スクリーンショット 2018-01-02 12.59.24.png

migrationのファイルができたので、次はデータベースにそれを反映させるコマンドをターミナルで実行する。

python manage.py migrate

migrateはデータを投稿するという理解でよい。
これでデータベースにテーブルを作成する作業、ブログに記事を格納するスペースをつくる工程は終わった。

記事を投稿してみる

スクリーンショット 2018-01-02 13.10.00.png

myblogappのurl定義を見てみると、adminというのがあらかじめ定義されていることがわかる。

このadminにアクセスしてみよう。http://127.0.0.1:8000/admin/

スクリーンショット 2018-01-02 13.11.50.png

このような画面が表示される。
Djangoではあらかじめ、管理画面が用意されているのである。

しかし、管理画面はあるがログインIDは無い。
なので、ログインIDを作成する作業を行う。

python manage.py createsuperuser

とターミナルに打つと、User名、メールアドレス、およびパスワードの作成をもとめられるのでその通り打ち込んで行く。
Superuser created successfully.と表示されれば成功である。

ここで作成したログインIDで、http://127.0.0.1:8000/admin/にアクセスしてログインすると、

スクリーンショット 2018-01-02 13.17.14.png

と管理画面に入れるようになる。

グループの追加やユーザーの追加メニューがあることが確認できる。
けれども、これだけでは記事の投稿ができないので、今度は記事投稿のメニューを作成する。

よって次は、postsディレクトリ内のadmin.pyファイルで、さきほど定義したPostクラスを読み込む。

admin.py
from .models import Post

.は同じ階層のmodelファイルを読み込むことを意味する。したがって.modelとは同階層のmodels.pyからPostというクラスを読み込む記述だとわかるだろう。

そして、importしたPostクラスを読み込む。

admin.py
admin.site.register(Post)

これでもう一度、http://127.0.0.1:8000/admin/にアクセスすると

スクリーンショット 2018-01-02 14.21.26.png

Postsメニューが表示されているのが確認できた。

 それではPostsメニューをクリックし、3つほど投稿をしてみよう。
投稿一覧の画面http://127.0.0.1:8000/admin/posts/post/を見てみると、

スクリーンショット 2018-01-02 14.38.45.png

どの投稿がなんなのかがわからない。
そのため、つぎは付けたtitleを一覧に表示するコードを書いて行く。

models.py
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

といったように、Postクラスに文字列を返すstrを定義してあげて、self.titleで自身のtitleを返すようにする。
 
そしてもう一度投稿一覧画面を見てみると

スクリーンショット 2018-01-02 14.43.59.png

titleが返されているのがわかる。

投稿タイトルを表示する

データベースから取得した値を変数に代入し、htmlのなかで渡された変数の内容を埋め込むテンプレートエンジンを利用することで、静的なページだけでなく動的なページを作って行く。

Postのクラス定義を参照して、テンプレートに引き渡すということをまず行う。

views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Post

def index(request):
    posts = Post.objects.order_by('-published')
    return render(request, 'posts/index.html', {'posts': posts})

全体としてのview.pyのコードはこのようになる。

from .models import Post

の部分は、Postクラスの変数をviewで利用する宣言。
そして、indexクラス内でpostsという変数を定義し、さきほどimportしたPostクラスの各変数を.objectという形で呼び出し、.order_by('-published')で投稿された期日が新しい順にならべかえるように代入する。
さらに、htmlに変数を渡すために、render()内に{'posts': posts}と追記する。

今度は、viewに渡された変数を実際にtemplateで利用する。書き換えた後のコードは次のようになる。

posts/templates/posts/index.html

index.html
<!DOCTYPE html>
<html lang = "ja-jp">
<head>
    <title>投稿一覧</title>
</head>
<body>
    <h1>ようこそ、Tsubasaのブログへ!</h1>

    <h2>最新の投稿</h2>

    {% for post in posts.all %}
        {{ post.title }}
        <br /><br />
        {{ post.published }}
        <br /><br />
        {{ post.body }}
        <br /><br />
    {% endfor %}
</body>
</html>

djangoでは、{% %}と囲んであげることでpythonの文法がつかえる。
for post in posts.all は先ほどviewで定義したpostsという変数のすべての値から、postという値に代入することを意味している。pythonのfor文の場合はendforで最後くくってあげる必要がある。
そして、変数の値を表示する際には{{ }}と囲んであげる。post.titleはpostの中のtitleを呼び出している。

これを書いたら、http://127.0.0.1:8000/posts/にアクセスしてみると

スクリーンショット 2018-01-02 16.34.50.png

登録しておいた記事のタイトルと投稿時間、本文がすべて表示されていることがわかる。

しかしこれでは、本文が長い場合は見づらいということがある。
たとえば3番目の投稿を次のように変える。

スクリーンショット 2018-01-02 16.38.08.png

よって今度は、文字数が非常に多い場合は最初の一部のみ表示するコードを足して行く。

posts/models.py

models.py
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

     def summary(self):
         return self.body[:30]

Postクラスの中にsummaryというbodyを30文字のみ返す関数を定義した。
そして、

posts/templates/posts/index.htmlのpost.bodyをpost.summaryに変える

index.html
<!DOCTYPE html>
<html lang = "ja-jp">
<head>
    <title>投稿一覧</title>
</head>
<body>
    <h1>ようこそ、Tsubasaのブログへ!</h1>

    <h2>最新の投稿</h2>

    {% for post in posts.all %}
        {{ post.title }}
        <br /><br />
        {{ post.published }}
        <br /><br />
        {{ post.summary }}
        <br /><br />
    {% endfor %}
</body>
</html>

これでどうなるかを見てみると、

スクリーンショット 2018-01-02 16.45.03.png

文字数の一部しか表示されていないことが確認できる。

画像を表示する

投稿一覧に画像を表示するようコードを書き加えてみる。

まず、

posts/templates/posts/index.htmlのpost.bodyをpost.summaryに変える

index.html
<!DOCTYPE html>
<html lang = "ja-jp">
<head>
    <title>投稿一覧</title>
</head>
<body>
    <h1>ようこそ、Tsubasaのブログへ!</h1>

    <h2>最新の投稿</h2>

    {% for post in posts.all %}
        {{ post.title }}
        <br /><br />
        {{ post.published }}
                <img src="{{ post.image.url }} />
        <br /><br />
        {{ post.summary }}
        <br /><br />
    {% endfor %}
</body>
</html>

しかしこれだけでは、urlの設定をしていないので画像は表示されない。
よってurlの表示を追記する。

myblogapp/urls.py

urls.py
from django.conf.urls.static import static
from django.conf import settings

とまずは加える。
1行目のimportは画像などの静的ファイルを読み込むために必要なパッケージを入れている。
2行目ではmyblogapp/settings.pyというファイルを読み込むためのコードである。

urls.py
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^posts/', include('posts.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

urlpatternsの後ろにstatic()で表示するデータのurlの設定と、そのデータをどこに表示しているかをしていする。

そして、myblogapp/settings.pyに

setting.py
MEDIA_URL = '/pics/'
MEDIA_ROOT = BASE_DIR

と書く。

これを行いもう一度投稿一覧のページを読み込んで見ると

スクリーンショット 2018-01-02 17.02.30.png

画像が表示される。

投稿詳細ページをつくる

記事タイトルをクリックしたら詳細ページに飛ぶようにしていく。
それにはまずurlの指定、具体的には、http://127.0.0.1:8000/posts/1/に飛んだら1番最初の投稿、
http://127.0.0.1:8000/posts/2/に飛んだら2番目の投稿の詳細ページが表示されるようにする。

urlのしていなので、まずプロジェクトのurls.pyにアプリケーションの呼び出し先を記述する。

myblogapp/urls.pyに書かれたurlpatternsに

urls.py
from posts import views

url(r'^posts/(?P<post_id>[0-9]+)/$', views.post_detail, name = "post_detail")

を追記。

r''''内に正規表現を書くという宣言。
^postは先頭がpostsであるということ。
[0-9]+というのはP0〜9の数字が複数並んでいるものであるということ。これを(?)でくくってあげることで変数名としてのurl利用を可能にしている。
そして最後の/$は文字列の一番最後が/で終わっていることを意味している。

その後ろのviews.post_detailはどこのディレクトリ内のファイルのクラスを参照するかを記述していることは大丈夫だろう。

ここでpost_detailという関数をviewでつくったので、posts/views.pyにかいていく。

views.py
def post_detail(request, post_id):
    post = Post.objects.get(pk=post_id)
    return render(request, 'posts/post_detail.html', {'post_id': post_id})

get(pk=post_id)というのは、primary_keyがpost_idに一致する投稿を取ってくる役割を果たしている。

ここで呼び出している、post_detail.htmlがないので、posts/templates/posts/post_detail.htmlを作成する。

中身はとりあえず

post_detail.html
{{ post.title }}
<br /><br />
{{ post.published }}
<br /><br />
<img src="{{ post.image.url }}" />
{{ post.body }}
<br /><br />

としておいて、http://127.0.0.1:8000/posts/1/に表示されるか確認してみよう。

すると
スクリーンショット 2018-01-02 18.43.58.png
詳細ページの表示に成功していることが確認できるだろう。

ここで、http://127.0.0.1:8000/posts/50/にアクセスして見て欲しい。

スクリーンショット 2018-01-02 18.47.47.png

というように表示されるはずだ。これでは見栄えがよくないので、投稿詳細ページが無い場合はPage not foundを返すようにしていく。

Djangoではあらかじめ、そのようなメソッドが用意されているので、viewsに書いて行く。

views.py
from django.shortcuts import render

def post_detail(request, post_id):
    post = Post.objects.get(pk=post_id)
    return render(request, 'posts/post_detail.html', {'post': post})

views.py
from django.shortcuts import render, get_object_or_404

def post_detail(request, post_id):
    post = get_object_or_404(Post, pk=post_id)
    return render(request, 'posts/post_detail.html', {'post': post})

と書き換える。

最後に、投稿一覧のタイトルをクリックしたら詳細ページに飛ぶように書き換える。

 index.html
<a href = "{% url 'post_detail' post.id %}" >{{ post.title }}'</a>

と、{{ post.title }}をタグで囲い、リンク先は'post_detail'とする。

これで、タイトルをクリックすることで記事詳細に飛べるようになった。

Djangoのブログとしてはこれで完成だが、見た目がかっこわるいので、次回はBootstrapで整形していく。