ベスパリブ

プログラミングを主とした日記・備忘録です。ベスパ持ってないです。

Django Girls Tutorialをする

はじめに

Djangoの最初のプロジェクトを作る方法、毎回ググってやるのがつらいので、備忘録として残したい。 Django Girls Tutorialを通してやることで、備忘録とします。

上記のページは情報が古くなってる可能性があるので、GitHubリポジトリを確認したほうが良いかもです。

tutorial/ja at master · DjangoGirls/tutorial · GitHubgithub.com

Anacondaはインストールされていること前提。Windows環境です。

Django Girls Tutorial用の仮想環境を作る

デフォルト環境を汚したくないので、専用の仮想環境を作っておきます。

# djangogirlsという名の仮想環境を作成する
(base) > conda create -n djangogirls
# djangogirls環境に移動する
(base) > activate djangogirls
# Djangoのインストール
(djangogirls) > conda install django

これ以降、(djangogirls)は省略します。

プロジェクトを作る

tutorial/ja/django_start_project at master · DjangoGirls/tutorial · GitHub

# Django Girls Tutorial用のフォルダを作り、移動する
> mkdir djangogirls
> cd djangodirls
# プロジェクトの作成
~/djangogirls> django-admin startproject mysite .

settings.pyの設定

mysite/settings.pyの中身を次のように変更する

# タイムゾーンの設定
TIME_ZONE = 'Asia/Tokyo'
# 言語の設定
LANGUAGE_CODE = 'ja'
# 静的ファイルのパスの設定
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
# PythonAnywhereにデプロイする場合
ALLOWED_HOSTS = ['127.0.0.1', '.pythonanywhere.com']

データベースの設定

sqlite3の設定はmysite/settings.pyの以下に既に設定されている(本番環境ではsqliteは使わないこと)。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

以下のコマンドでデータベースを作成し、サーバを起動します。

# データベースの作成
~/djangogirls> python manage.py migrate
# サーバの起動
~/djangogirls> python manage.py runserver
# ブラウザのアドレス欄に"http://127.0.0.1:8000/"を入力し、ページが表示されたら成功
(ctrl-Cでサーバの停止)

Djangoモデル

tutorial/ja/django_models at master · DjangoGirls/tutorial · GitHub

プロジェクト内にアプリケーションを作成します。

# blogという名前のアプリケーションを作成する
~/djangogirls> python manage.py startapp blog

アプリケーションを作ったら、Djangoにそれを使うように伝えます。 mysite/settings.pyファイルのINSTALLED_APPSに、'blog'という一行を追加します。

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
)

blog post modelの作成

blog/models.pyファイルでModelsと呼ばれる全てのオブジェクトを定義します。これがブログポストを定義する場所です。

blog/models.pyを以下のように書き換えます。

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

class Post(models.Model):
    author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    text = models.TextField()
    created_date = models.DateTimeField(
            default=timezone.now)
    published_date = models.DateTimeField(
            blank=True, null=True)

    def publish(self):
        self.published_date = timezone.now()
        self.save()

    def __str__(self):
        return self.title

models.ModelはポストがDjango Modelだという意味で、Djangoが、これはデータベースに保存すべきものだと分かるようにしています。

(クラス変数をselfでアクセスしてるけどいいのか?)

データベースにモデル用のテーブルを作る

新しいモデルをデータベースに追加します。まず、(今作った)モデルの中で少し変更があったことをDjangoに知らせる必要があります。

~/djangogirls> python manage.py makemigrations blog

Djangoがデータベースに入れる為の移行ファイルを作ってくれているので、migrateする。

~/djangogirls> python manage.py migrate blog

これでPostモデルがデータベースに入りました。

ログインページを作る

今作成したポストを追加、編集、削除するのにDjango adminを使います。

モデルをadminページで見れるようにするために、モデルをadmin.site.register(Post)で登録する必要があります。

blog/admin.pyファイルを書き換えます。

from django.contrib import admin
from .models import Post

admin.site.register(Post)

ログインして投稿する

# superuser (サイトの全てを管理するユーザー)を作る
~/djangogirls> python manage.py createsuperuser
# 忘れずにmigrateする
~/djangogirls> python manage.py migrate blog
# サーバ起動
~/djangogirls> python manage.py runserver
# http://127.0.0.1:8000/admin/ にアクセスしてログインすると、Django admin ダッシュボードへ行ける
# いくつか記事を投稿して、動作することを確認する

デプロイ

tutorial/ja/deploy at master · DjangoGirls/tutorial · GitHub

Heroku PythonAnywhereにデプロイするチュートリアル。飛ばします。

DjangoでURLはどのように機能する?

tutorial/ja/django_urls at master · DjangoGirls/tutorial · GitHub

mysite/urls.py内は次のようになっている。

from django.conf.urls import include, url
from django.contrib import admin

urlpatterns = [
    # Examples:
    # url(r'^$', 'mysite.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),
    # admin/で始まる全てのURLについて、Djangoが返すべきviewをこの行で指定しています。
    url(r'^admin/', include(admin.site.urls)),
]

最初のURLを作る

http://127.0.0.1:8000/ はブログの入口ページなので、投稿したブログポストのリストを表示するようにします。

mysite/urls.py ファイルは簡潔なままにしておきたいので、mysite/urls.pyではblogアプリからURLをインポートするだけにしましょう。

from django.urls import path, include
from django.contrib import admin

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('blog.urls')),
]

これでDjangoは 'http://127.0.0.1:8000/' に来たリクエストはblog.urlsへリダイレクトするようになり、それ以降はそちらを参照するようになります。

blogのURL

blog.urls.pyを作ります。

from django.urls import path
# blogアプリケーション内の全てのviewをインポートする
from . import views

# URLパターンの定義
# post_list という名前の ビュー をルートURLに割り当てる
urlpatterns = [
    path('', views.post_list, name='post_list'),
]

このURLパターンは空の文字列に一致し、Djangoはビューを見つけるとき、URLのフルパスの前半にくっつくドメイン名(つまり、http://127.0.0.1:8000/ の部分)を無視します。 このパターンは誰かがあなたのWebサイトの 'http://127.0.0.1:8000/' というアドレスにアクセスしてきたら views.post_list が正しい行き先だということをDjangoに伝えます。

最後の name='post_list' は、ビューを識別するために使われるURL の名前です。 これはビューと同じ名前にすることもできますが、全然別の名前にすることもできます。 プロジェクトでは名前づけされたURLを後で使うことになるので、アプリのそれぞれのURLに名前をつけておくのは重要です。また、URLの名前はユニークで覚えやすいものにしておきましょう。

ビューって何?

tutorial/ja/django_views at master · DjangoGirls/tutorial · GitHub

ビュー はアプリのロジックを書いていくところです。 ビューは、以前あなたが作った モデル に情報を要求し、それを テンプレート に渡します。 テンプレートは、次の章で作ります。 ビューはただのPythonの関数です。Python入門の章で書いたものよりもちょっと複雑なだけですよ。

ビューは、views.py に記述します。私たちの場合 blog/views.py に書くことになります。

ビューを作る

blog/views.pyを作ります。

from django.shortcuts import render


def post_list(request):
    return render(request, 'blog/post_list.html', {})

これは request を引数に取り、blog/post_list.htmlテンプレートを表示する (組み立てる) render 関数を return しています。

テンプレートって何?

tutorial/ja/html at master · DjangoGirls/tutorial · GitHub

テンプレートはhtmlのこと(たぶん)。

テンプレートは、blog/templates/blogディレクトリに保存されています。 それでは、最初に、自分のblogディレクトリの中にtemplatesという名前のディレクトリを作成してください。 次に、自分のtemplatesディレクトリの中にblogという名前のディレクトリを作ります。

blog
└───templates
    └───blog

(なぜ、両方ともblogという名前の付いたディレクトリを2つ作成する必要があるのか不思議に思う人もいるかもしれません。あとで分かると思いますが、簡単に言うと、これは、もっと複雑なことをやろうとした時に、それが楽にできるようにしてくれる便利な命名法なのです。)

テンプレートを作る

blog/templates/blog/post_list.htmlを作ります。

<html>
    <head>
        <title>Django Girls blog</title>
    </head>
    <body>
        <p>Hello!</p>
        <p>It works!</p>
    </body>
</html>

ここまで来たら、サーバを起動して動作確認します。

~/djangogirls> python manage.py runserver

http://127.0.0.1:8000/ にアクセスして、ページが表示されたら成功です。

クエリセット

データベースへの接続方法と、データストアについて。

tutorial/ja/django_orm at master · DjangoGirls/tutorial · GitHub

クエリセットが何かと言うと、モデルが提供しているオブジェクトのリストのことです。クエリセットは、データベースからデータを読み込んだり、抽出したり、言われた通りにやってくれます。

# djangoのコンソールを開く
$ python manage.py shell
>>> from blog.models import Post
# Post(投稿)データをすべて取り出す
>>> Post.objects.all() 
<QuerySet [<Post: Alice is god.>, <Post: Bob is good man.>, <Post: Carol is super man.>]>
# 新しい投稿を作成する
>>> from django.contrib.auth.models import User
>>> User.objects.all()  # どんなユーザが登録されているか確認する
<QuerySet [<User: USERNAME>]>
>>> me = User.objects.get(username='USERNAME')
>>> Post.objects.create(author = me, title = 'Sample title', text = 'Test')
# 新しい投稿が作られたか確認する
>>> Post.objects.all()
<QuerySet [<Post: Alice is god.>, <Post: Bob is good man.>, <Post: Carol is super man.>, <Post: Sample title>]>
# 投稿者でフィルタリングする
>>> Post.objects.filter(author=me)
# タイトルに"title"が含まれているもののフィルタリング
>>> Post.objects.filter(title__contains='title')
# 未公開の投稿を公開する
>>> post = Post.objects.get(id=1)  # ID1の投稿を取得
>>> post.publish()
# 公開済みの投稿のみを取りだす
>>> from django.utils import timezone
>>>Post.objects.filter(published_date__lte=timezone.now())
# created_date フィールドでソートする
>>> Post.objects.order_by('created_date')  # 昇順
>>> Post.objects.order_by('-created_date')  # 降順
# 公開済みの投稿をpublished_dateフィールドでソートする
>>> Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
# 終了
exit()

クエリセット2

tutorial/ja/dynamic_data_in_templates at master · DjangoGirls/tutorial · GitHub

・投稿内容を保存するためのPostモデルは、models.pyに定義した ・投稿の一覧を表示するpost_listはviews.pyにあり、そこにテンプレートも追加した。 ・投稿をどうやってHTMLファイルに出力すればよいか?

大まかなイメージとしては、データベースに保存された記事を取り出して、テンプレートのHTMLファイルの中に行儀よく並べるだけのことですけど。 正確には、 ビュー が モデルとテンプレートの橋渡しをしてくれます。私達が作業している post_list ビュー の場合、表示したいデータを取り出して、テンプレートファイルに渡すことになります。基本的に、どのモデルのデータを、どのテンプレートに表示させるかは、 ビューに 記述します。

blog/views.pyを開き、編集します。

from django.shortcuts import render
from django.utils import timezone
from .models import Post

def post_list(request):
    """ 投稿をリスト表示する """
    # 公開した投稿をpublished_dateでソートする
    _posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')

    # render()関数について
    # 第1引数:リクエスト(ユーザから受け取ったすべての情報が詰まっている)
    # 第2引数:テンプレートファイル
    # 第3引数:指定した情報を、テンプレートファイルに渡す
    return render(request, 'blog/post_list.html', {'posts': _posts})

クエリセットのAPIリファレンス:QuerySet API reference | Django ドキュメント | Django

テンプレートタグとは?

tutorial/ja/django_templates at master · DjangoGirls/tutorial · GitHub

テンプレートタグとは、HTMLにPyhtonのようなコードを埋め込むためのもの。

HTML内に{{ posts }}のように書くと、render()関数の第3引数で指定したJSONのpostsキーのデータを表示できる

blog/templates/blog/post_list.htmlを次のように編集します。

<html>
    <head>
        <title>Django Girls blog</title>
    </head>
    <body>
        <div>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        
        {% for post in posts %}
            <div>
                <p>published: {{ post.published_date }}</p>
                <h1><a href="">{{ post.title }}</a></h1>
                <p>{{ post.text|linebreaksbr }}</p>
            </div>
        {% endfor %}
    </body>
</html>

CSSでカワイくしよう

tutorial/ja/css at master · DjangoGirls/tutorial · GitHub

DjangoCSSファイルはどこに置けば(どう書けば)よいのか?

Bootstrapを使う

Bootstrap は美しいWebサイトを開発するためのHTMLとCSSフレームワーク

Bootstrapのインストール

blog/templates/blog/post_list.html<head> タグに以下を追加します。

<!-- 3.2.0のバージョン番号は好きに指定してね:) バージョンによって廃止になったアイコンあるので注意:( -->
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">

これは、あなたのプロジェクトにファイルを追加しているわけではありません。インターネット上にあるファイルを指しているだけです。

jQueryと使い方は同じですね。

Djangoの静的ファイル

静的ファイルとは、CSSファイルや画像ファイルといった、動的な変更が発生しないファイルのことです。

静的ファイルはプロジェクトのどこに置けばいいの?

知りたいのはこれだよこれ。

Djangoは、ビルトインの "admin" アプリにより、静的ファイルをどこで探せばいいのかわかっています。私たちがやることは、blog アプリのための静的ファイルを追加することだけです。

そのために、blogアプリの中に static というフォルダを作ります。

djangogirls
├── blog
│   ├── migrations
│   ├── static
│   └── templates
└── mysite

Djangoは、全てのアプリのフォルダ内の "static" と名づけられた全てのフォルダを自動的に探して、その中身を静的ファイルとして使えるようにします。

すご。

この"static"と名付けられたすべてのフォルダを自動的に探す機能は、mysite/setting.pyの以下の箇所でしているようです。

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/

STATIC_URL = '/static/'
# 後述のCSSがうまく反映されなかったら、以下を追加する
STATIC_ROOT = os.path.join(BASE_DIR, '/')
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
)

最初のCSSファイル!

以下のようにファイルを作成します。

djangogirls
└─── blog
     └─── static
          └─── css
               └─── blog.css

blog/static/css/blog.cssは適当に次のようにします。

h1 a {
    color: #FCA205;
}

・HTMLファイルの先頭に{% load static %}と書くことで、テンプレートに静的ファイルを読み込むことができる。

CSSファイルの読み込みは、<link rel="stylesheet" href="{% static 'css/blog.css' %}">のように書く

blog/templates/blog/post_list.htmlは次のようにします。

{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>
(以下略)

フォント

<link href="//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">

・ Lobster というフォントをGoogle Fonts (https://www.google.com/fonts) から読み込める

font-family: 'Lobster';CSSに追加することでフォントが適用できる

最終的に次のようになります。

/blog/static/css/blog.css

.page-header {
    background-color: #ff9400;
    margin-top: 0;
    padding: 20px 20px 20px 40px;
}

.page-header h1, .page-header h1 a, .page-header h1 a:visited, .page-header h1 a:active {
    color: #ffffff;
    font-size: 36pt;
    text-decoration: none;
}

.content {
    margin-left: 40px;
}

h1, h2, h3, h4 {
    font-family: 'Lobster', cursive;
}

.date {
    color: #828282;
}

.save {
    float: right;
}

.post-form textarea, .post-form input {
    width: 100%;
}

.top-menu, .top-menu:hover, .top-menu:visited {
    color: #ffffff;
    float: right;
    font-size: 26pt;
    margin-right: 20px;
}

.post {
    margin-bottom: 70px;
}

.post h1 a, .post h1 a:visited {
    color: #000000;
}

/blog/templates/blog/post_list.html

{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.3.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.3.0/css/bootstrap-theme.min.css">
        <link href="http://fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>
    <body>
        <div class="page-header">
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        
        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                    {% for post in posts %}
                        <div class="post">
                            <div class="date">
                                <p>published: {{ post.published_date }}</p>
                            </div>
                            <h1><a href="">{{ post.title }}</a></h1>
                            <p>{{ post.text|linebreaksbr }}</p>
                        </div>
                    {% endfor %}
                </div>
            </div>
        </div>
    </body>
</html>

テンプレートを拡張する

テンプレートを拡張しよう · Django Girls Tutorial

Djangoのまた別の素敵なところはテンプレート拡張です。これは何を意味するのでしょうか?それはHTMLの共通部分をウェブサイトの異なるページで使えるということです。

基本テンプレートを作成する

・基本テンプレートは各ページを拡張するための最も基本的なテンプレート。

blog/templates/blog/以下にbase.htmlファイルを作成します。

blog
└───templates
    └───blog
            base.html
            post_list.html

base.htmlを基本的なテンプレート(HTML)

post_list.htmlを「投稿を一覧表示する部分」の拡張テンプレート

として作成し直します。

まず、post_list.htmlの内容を丸々コピペしてbase.htmlに貼り付け、次のように「投稿を一覧表示する部分」を削除して、代わりにblock タグで囲みます。

{% load static %}
<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.3.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.3.0/css/bootstrap-theme.min.css">
        <link href="http://fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext" rel="stylesheet" type="text/css">
        <link rel="stylesheet" href="{% static 'css/blog.css' %}">
    </head>

    <body>
        <div class="page-header">
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                {% block content %}
                {% endblock %}
                </div>
            </div>
        </div>
    </body>
</html>

最後に、post_list.htmlを以下のように書き換えます。

{% extends 'blog/base.html' %}  <!-- blog/base.htmlを拡張する -->

<!-- block content タグの中身を定義する -->
{% block content %}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                <p>published: {{ post.published_date }}</p>
            </div>
            <h1><a href="">{{ post.title }}</a></h1>
            <p>{{ post.text|linebreaksbr }}</p>
        </div>
    {% endfor %}
{% endblock %}

アプリケーションを拡張する

アプリケーションを拡張しよう · Django Girls Tutorial

もう、ウェブサイトを作るのに必要な全ての章は終わりました。どのようにモデル、URL、ビュー、テンプレートを書いたら良いかわかっています(中略) さあ練習しましょう!

投稿の詳細へのテンプレートリンクを作成する

投稿リストの投稿のタイトルから投稿の詳細ページへのリンクを作るように、post_list.htmlにテンプレートリンクを追加します。

・変更前

<h1><a href="">{{ post.title }}</a></h1>

・変更後

<h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>

・{% %}という表記はDjangoのテンプレートタグを使用していることを意味している

・post_detailの部分は、Djangoblog/urls.pyに書かれた name=post_detail のURLを待ち受ける

・pk=post.pkについては、pkはプライマリキーの略で、データベースの各レコードのユニークな名前

・post.pkと書くことによって、Postインスタンスのプライマリキーにアクセスする

投稿の詳細へのURLを作成する

post_detail ビュー用にurls.pyにURLを作成します。

最初の投稿の詳細がこのURLで表示されるようにします:http://127.0.0.1:8000/post/1/

blog/urls.pyのURLパターンを次のようにします。

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
]
  • post/ はURLが post に続けて / で始まることを意味する
  • <int:pk>は整数の値を期待し、その値がpkという名前の変数でビューに渡されることを意味する
  • / – それからURLの最後に再び / が必要

つまり、ブラウザにhttp://127.0.0.1:8000/post/5/を入力すると、Djangoはpost_detailというビューを探していると理解します。そしてpkが5という情報をそのビューに転送します。

投稿の詳細ビューを追加する

ビューに追加のパラメータpkが与えられるようにしたので、ビューにそれを受け取る処理を追加します。

その処理する関数をdef post_detail(request, pk):として定義します。 urls.pyで指定した名前(pk)と同名にする必要があることに注意します。

blog/views.pyに次を追加します。

def post_detail(request, pk):
    from django.shortcuts import render, get_object_or_404
    post = get_object_or_404(Post, pk=pk)
    return render(request, 'blog/post_detail.html', {'post': post})

投稿の詳細へのテンプレートリンクを作成する

blog/templates/blog/post_detail.htmlを作成します。

{% extends 'blog/base.html' %}

{% block content %}
    <div class="post">
        {% if post.published_date %}
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaksbr }}</p>
    </div>
{% endblock %}
  • {% if ... %} ... {%endif%}では、公開日(published_date)が空でないかを確認している

Djangoフォーム

Djangoフォーム · Django Girls Tutorial

Django adminを使わず、記事を追加したり編集したりできるようにします。Djangoフォームを使えば、考え得る大抵の入力フォームは作れるらしいです。

Djangoフォームは、フォームをゼロから定義できたり、フォームの結果をモデルに保存できるModelFormを作れたりします。

そのためのblog/forms.pyを作成します。

blog
   └── forms.py
from django import forms
from .models import Post

class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = ('title', 'text',)

フォームにおけるページへのリンク

・blog/templates/blog/base.htmlをpage-headerと名付けたdiv中に次のリンク<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>を追加します

        <div class="page-header">
            <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>

URL

blog/urls.pyに、URLパターンを追加します。

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
    path('post/new/', views.post_new, name='post_new'),
]

post_new ビュー

blog/views.pyにpost_newビューを追加します。

from .forms import PostForm

def post_new(request):
    form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})

テンプレート

blog/templates/blog/post_edit.htmlファイルを作ります。フォームを動かすにはいくつかやることがあります。

  • フォームを表示する必要があります。 私たちは(例えば){{ form.as_p }} でこれを行うことができます。
  • 上記の行は HTMLのformタグでラップする必要があります:
    ...
  • Save ボタンが必要です。これをHTMLのbuttonタグで行います:
  • 最後に
    タグの開始直後に、 {% csrf_token %}を追加する必要があります。 フォームをセキュアにするためこれは非常に重要です! これを忘れると、Djangoはフォームを保存しようとすると文句を言うでしょう

フォームを保存する

blog/views.pyは今以下のようになっている。

def post_new(request):
    form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})
  • フォームを送信したとき、request.POST にデータが追加されている(のすべてのフィールドは今 request.POST にある)

このビュー では、扱わなくてはならない2つの別々のシチュエーションがあります。

  • 1つ目は、最初にページにアクセスしてきた時で空白のフォームが必要な場合
  • 2つ目はすべてのフォームデータが入力された状態でビューに戻ってくる場合です。 したがって条件分岐を追加する必要があります(そのためにifを使います)
if request.method == "POST":
    [...]
else:
    form = PostForm()

CSRF verification failed. Request aborted.エラーが表示されるようなら、settings.pyに以下を追加します。

MIDDLEWARE_CLASSES = (
    'django.middleware.csrf.CsrfViewMiddleware',
)

フォームのバリデーション(検証)

ブログのポストは title と text のフィールドが必要です。 Post モデルではこれらのフィールドがなくてもよいとは書いておらず (published_date とは対照的に)、Djangoはその場合、それらのフィールドには何らかの値が設定されることを期待します。title と text を入力せずに保存してみましょう。何が起こるでしょうか?

「このフィールドを入力してください」と表示されます。

フォームの編集

既存のデータを編集するためのページを作成する

編集ボタンを追加するため、blog/templates/blog/post_detail.html<div class="post">部分を次のようにします。

    <div class="post">
        {% if post.published_date %}
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}">編集<span class="glyphicon glyphicon-pencil"></span></a>
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaksbr }}</p>
    </div>

URLパターンを登録するため、blog/urls.pyurlpatternsに次を追加します。

path('post/<int:pk>/edit/', views.post_edit, name='post_edit'),

テンプレートは blog/templates/blog/post_edit.html を再利用するのでそのままです。

ビューは、blog/views.pyに次を追加します。

def post_edit(request, pk):
    """ 投稿を編集する """
    # 編集したいPostモデルを get_object_or_404(Post, pk=pk) で取得する
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        # フォームを作るときは既存の投稿をインスタンスとして渡す
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('post_detail', pk=post.pk)
    else:
        form = PostForm(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form})

これでOK。

セキュリティ

誰でも新しい投稿を作成することができてしまう状態なので、管理者でログインしているユーザのみ新しい投稿ボタンを表示させるようにします。

blog/templates/blog/base.htmlのボタン部分を、次のように編集します。

{% if user.is_authenticated %}
    <a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
{% endif %}

この{% if %}は、ページをリクエストしているユーザーがログインしている場合にのみ、リンクがブラウザに送信されるようにします。 これは新しい投稿の作成を完全に保護するものではありませんが、それは良い第一歩です。 私たちは拡張レッスンでより多くのセキュリティをカバーします。

(完全に保護するものではないのか……セキュリティ的に何が問題あるんだろう。)

編集ボタンも同様にします。

blog/templates/blog/post_detail.htmlの編集ボタン部分を、次のように編集します。

{% if user.is_authenticated %}
     <a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>
{% endif %}

これでOK。

シークレットウィンドウでページを読み込むと、リンクが表示されず、アイコンも表示されなくなります。

チュートリアルは以上です。

次のステップは?

次のステップは? · Django Girls Tutorial

感想

Djangoのモデル、ビュー、テンプレート、フォームの概念の説明がわかりやすかった。