"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

Django2.0で入力フォームを作る時の、CSRF検証エラー対応と静的ファイルへのアクセスなど

今回は、かんたんに考えていたら、意外にわかりづらかった以下の3つについてです。

  • CSSや画像ファイルなどの静的ファイルへのアクセス
  • テキストボックスなどのタイトルラベルを任意の名前にする
  • 送信ボタンを押してPOSTで画面遷移する時のCSRF検証エラーの回避 

f:id:arakan_no_boku:20190320212948j:plain

 

まず、どういうことをしようとしたかの説明

 

とりあえず、練習でこんな画面を作ろうとしたわけです。

f:id:arakan_no_boku:20180202001917j:plain

テキストボックスの入力+送信(POST)で画面更新させる基本の確認です。

 

CSSや画像ファイルなどの静的ファイルへのアクセス

 

画像とCSSファイルは静的ファイルとして保存します。

CSSファイル(styles.css)は、ごくシンプルにこんな感じです。

@charset "utf-8"
html {
    font-size:16px
}

body {
    font-family: 'メイリオ','Hiragino Kaku Gothic Pro',sans-serif;
    font-size: 16px;
    background-color:white;
}

.box {
    width:100%;
}

p{
    word-break: break-all;
    margin:1em;
}

span{
    margin:1em;
}

img{
    margin:1em;
}

h1{
    font-size:150%;
    color:blue;
    margin:1em;
}

input[type=text] { 
	text-align:left;
	vertical-align: middle;
	width:30%;
	height:3em;
}

プロジェクトフォルダ「hello」の下に、HTMLと静的ファイル(CSS、画像、JavaScript)の置き場所のフォルダを作ります。

今回は、viewとstaticという名前にしました。

f:id:arakan_no_boku:20180202002556j:plain

上記の役割はこうです。

  • hello/hello: pythonプログラムの置き場
  • hello/static: CSSやimagesなどの静的ファイルの置き場
  • hello/view: テンプレート(つまりHTMLファイル)の置き場

 

「static」の下には、静的ファイルを置くフォルダを作ります。

例えば、「css」「images」「js」とかですかね。

当然、styles.cssは、hello/static/cssの下に置きます。 

同じように、画像ファイル(今回はjinsei.jpg)も、/hello/static/の下の「iamges」フォルダの下に置きます。 

 

HTMLファイルでの対応

 

表示用のHTMLファイルを書いて、hello_form.html という名前で、hello/viewの直下に置きます。 

HTMLファイル(hello_form.html)は、こうです。

 {% load static %}
<html>
    <head>
         <link rel="stylesheet" type="text/css" href="{% static "hello/css/styles.css" %}">
    </head>
    <body>
        <div class="box">
            <h1>Hello Form!!</h1>
            <img src="{% static "hello/images/jinsei.jpg" %}" />
            <form action="" method="post">
                {% if name %}
                      <p>{{ name }}</p>
                {% else %}
                      {{form.as_p}}
                      <span><input type="submit" value="送信"></span>
                {% endif %}
            </form>
        </div>
    </body> 
</html>

 上記のように配置した静的ファイルにアクセスするには、HTMLの中で以下のように書きます。 

{% load static %} 

  <link rel="stylesheet" type="text/css" href="{% static "hello/css/styles.css" %}"> 

 <img src="{% static "hello/images/jinsei.jpg" %}" />

 書き方としては、これで正しいのです。

これだけでは全く認識してくれません。 

 

setting.pyにStaticフォルダの物理パスを書く

 

実は・・「setting.py」に書いてある静的ファイルの記述

STATIC_URL = '/static/'

だけだと、djangoのインストールフォルダのadminの下にある「static」フォルダを指しているだけなので、上記のプロジェクトごとのstaticフォルダは見えないのです。

ここが第一のハマりどころでした。 

なので、hello/helloの下の「setting.py」に以下の記述を追加しないといけません。

 

setting.py

 

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

これで、さきほど作成したプロジェクトファイル以下のstaticファイルが見えるようになります。 

正しく認識されていることを、以下のコマンドで確認します。

python hello/manage.py findstatic .

最後に「.」があるのに気をつけてください。 

これを実行して、「static」フォルダを作成したパスが表示されたらOKです。 

 

補足

 

viewフォルダにHTMLをおいていることを認識させるのに、setting.pyに以下のようなことを書く必要があったのと同じです。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
        	os.path.join(BASE_DIR, 'view'),
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

 

 テキストボックスなどのタイトルラベルを任意の名前にする

 

入力用のテキストボックスは、Formを使います。 

今回はテキストボックスを一つだけ作るので、「CharField」を使ってます。 

他にも色々あるみたいですので、それはおいおい。 

「forms.py」を、hello/helloの下に作成して、以下のように、テキストボックスをひとつだけ返すクラスを作ります。 

その時に、「name = CharField()」とデフォルトでやると、自動的に変数名のnameから「Name:」というラベルが生成されて、テキストボックスの前につきます。 

ただ、この自動生成はあまりうれしくありません。 

英語圏の人なら、ラベルの名前と変数名を一致させておけるので良いかもしれませんが、日本人ですからね。 

このラベルを任意の名前に変える方法が、意外と情報がなくて手こずりました。 

といっても、引数のlabelに表示させたい文字列を指定しておくだけです。

from django import forms

class HelloForm(forms.Form):
     name = forms.CharField(label='何か入力')

 

POSTで画面遷移する時のCSRF検証エラーの回避 

 

上記までで、HTMLを書き、静的ファイルを配置して、setting.pyを設定して、forms.pyでフォームクラスを定義しました。 

あとは、view.pyと urls.pyを設定すればOKです・・が、まだ落とし穴がまってます。 

まずは、普通にしてみます。

views.pyです

from django.http.response import HttpResponse
from django.shortcuts import render,render_to_response
from datetime import datetime as dt
from . import forms

def hello_form(request):
    if request.method == 'POST':
         c = {
             'name':request.POST["name"]
         }    
    else:
         f = forms.HelloForm(label_suffix=':')
         c = {'form': f}
    return render_to_response('hello_form.html', c)

 

 

urls.pyです。 

以下を追加します。

 url(r'^hform/', views.hello_form,name='hello_form'),

 

これで、画面表示はできますが・・・。 

送信ボタンを押すと、こんな画面がでます。

f:id:arakan_no_boku:20180202090621j:plain

 

なるほど、CSFR(クロスサイトリクエストフォージェリ攻撃)対策をしてくれているのか・・とわかるのですが、まだ、なんか足らないということです。 

これを回避するためには、HTMLとview.pyの変更が必要です。 

HTMLの変更は以下のように、<form>の内側に「 {% csrf_token %}」を置きます。

            <form action="" method="post">

               {% csrf_token %}
                {% if name %}
                      <p>{{ name }}</p>
                {% else %}
                      {{form.as_p}}
                      <span><input type="submit" value="送信"></span>
                {% endif %}
            </form>

 

わりとネットの情報だと、これだけで良い・・みたいに書いてあるのですが、実際にやってみたところ、うまくいきませんでした。 

view.py を以下のように変更する必要があります。

from django.http.response import HttpResponse
from django.shortcuts import render,render_to_response
from datetime import datetime as dt
from . import forms
from django.template.context_processors import csrf

def hello_form(request):
    if request.method == 'POST':
         c = {
             'name':request.POST["name"]
         }    
    else:
         f = forms.HelloForm(label_suffix=':')
         c = {'form': f}
    c.update(csrf(request))
    return render_to_response('hello_form.html', c)

 

以下をインポートします。

from django.template.context_processors import csrf

 

そして、requestをラップしてやります。

c.update(csrf(request))

 

ちょっと注意点

 

上記の対応で、Djangoのドキュメントのサンプル(例えば、ここ。

クロスサイトリクエストフォージェリ (CSRF) 対策 — Django 1.4 documentation

には、インポートが「from django.core.context_processors import csrf」って書いてありますが、これだとエラーになります。 

なんか、最近 csrfモジュールが移動されたみたいなんですね。 

まだ、情報が少ないので、けっこうハマりますので、補足しておきます。 

これでOK。

 

実行確認です

 

サーバーを動かして。

python hello/manage.py runserver

 

やってみます。

f:id:arakan_no_boku:20180202094854j:plain

 

入力して、送信ボタンを押します。

f:id:arakan_no_boku:20180202094924j:plain

 

OKですね。