アラカン"BOKU"のITな日常

文系システムエンジニアの”BOKU”が勉強したこと、経験したこと、日々思うことを書いてます。

Djangoで入力フォームを作る最初のハマりどころ。CSRF検証エラー対応と静的ファイルへのアクセス

pythonでロジックを書くなら、WebアプリケーションもDjangopythonベースで手軽に作るのもアリだな・・などと軽く考えてはじめてみました。

 

でも、やっぱり、最初は色々つまづきますね。

 

特に、かんたんに考えていた以下の3つのことが、意外にわかりづらかったです。

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

 

ということで、備忘を兼ねてまとめておきます。

 

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

 

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

f:id:arakan_no_boku:20180202001917j:plain

 

CSSと画像にアクセスして、テキストボックスの入力+送信(POST)で画面更新させるという、基本の確認ですね。

 

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;
}

 

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>

 

プロジェクトフォルダ「hello」の下に、viewとstaticというフォルダを作ります。

f:id:arakan_no_boku:20180202002556j:plain

 

上記の役割はこうです。

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

 

先程の、hello_form.html は、hello/viewの直下に置きます。

 

styles.cssは、hello/static の下に、再度「hello」というプロジェクト名と同じフォルダを作り、その下に「css」フォルダを作って、その下に置きます。

 

つまり、hello/static/hello/css にスタイルシートを置くわけです。

 

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

 

これで静的ファイルの配置は終わりです。

 

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

 

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

 

Djangoに関して、ネットで調べても、だいたいこのように書いてあります。

{% load static %} 

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

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

 

書き方としては、これで正しいのですが、これだけでは全く認識してくれません。

 

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

 

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

setting.py

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, '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検証エラーの回避 

上記までで、静的ファイルを配置して、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ですね。