GIG

赴くままに技術を。

Djangoアプリケーションのデプロイ

開発サーバでなく、製品版ではどうするかというと2通りの方法があるみたい。

  1. Apache Httpサーバ + mod_wsgi
  2. Nginx + Gunicorn

2.の方がパフォーマンスが優れているという話も見かけたけど、今回は1.を試してみる。 wsgiは"ウィスギィ"と読むのか。

検証環境として、Virtualbox上に立てたCentOS 7を使う。

 cat /etc/centos-release
CentOS Linux release 7.2.1511 (Core)

webブラウザから確認するため、VagrantfileにIPアドレスを固定するよう設定する。

  • Vagrantfile
...
  config.vm.network "private_network", ip: "192.168.33.10"
...
Python 3.5.1のインストール

デフォルトでインストールされているPythonのバージョンが2.7.5なので、3.5.1をインストールする。

# mkdir tmp
# cd tmp
# wget https://www.python.org/ftp/python/3.5.1/Python-3.5.1.tgz
# tar -xzf Python-3.5.1.tgz
# cd Python-3.5.1
# ./configure --enable-shared
# make
# make altinstall

インストールの確認をしたが、関連するライブラリのパスが通っていない...。 パスを通す。

# /usr/bin/python3.5 -V/usr/bin/python3.5: error while loading shared libraries: libpython3.5m.so.1.0: cannot open shared object file: No such file or directory
# echo "/usr/local/lib/python3.5" > /etc/ld.so.conf.d/python33.conf
# echo "/usr/local/lib" >> /etc/ld.so.conf.d/python33.conf
# ldconfig
Apache Httpサーバ およびmod_wsgiのインストール

tmpディレクトリのままでApache Httpサーバとmod_wsgiをインストールする。

# yum install httpd
# wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.2.tar.gz
# tar -xzf 4.5.2.tar.gz
# cd mod_wsgi-4.5.2/
# ./configure --with-python=/usr/local/bin/python3.5
# make
# make install
Djangoのインストール

pipのバージョンが古いかったので、アップグレードも実施。

# pip3.5 freeze
You are using pip version 7.1.2, however version 8.1.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
# /usr/local/bin/pip3.5 install --upgrade pip
# /usr/local/bin/pip3.5 install django
確認用プロジェクトで確認する

helloプロジェクトを作成。

# su - vagrant
$ cd ~
$ mkdir -p dev/python/django
$ cd dev/python/django/
$ django-admin startproject hello

マイグレーション

$ /usr/local/bin/python3.5 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, sessions, contenttypes
Running migrations:
  Rendering model states... DONE
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying sessions.0001_initial... OK

管理者アカウントを作っておいて、管理画面の表示も確認しよう。

$ /usr/local/bin/python3.5 manage.py createsuperuser
Username (leave blank to use 'vagrant'): admin
Email address: admin@email.com
Password:
Password (again):
Superuser created successfully.

前回はstaticフォルダにJavascriptやらCSSを入れて参照していたけど、管理画面のものはどこにあるのかなと思ったらそれをstaticフォルダに集めれとのこと。これをしないとあとで管理画面のスタイルが崩れて表示される。

collectstaticコマンド を実行するとSTATIC_ROOTで指定したディレクトリに格納される。

  • hello/settings.py
...
STATIC_ROOT = os.path.join(BASE_DIR, "static/")
...

それでもって

$ /usr/local/bin/python3.5 manage.py collectstatic
mod_wsgiの設定
oadModule wsgi_module modules/mod_wsgi.so

WSGIDaemonProcess hello python-path=/home/vagrant/dev/python/django/hello
WSGIProcessGroup hello
WSGIScriptAlias / /home/vagrant/dev/python/django/hello/hello/wsgi.py process-group=hello

<Directory /home/vagrant/dev/python/django/hello/hello>
    <Files wsgi.py>
        Require all granted
    </Files>
</Directory>

Alias /static /home/vagrant/dev/python/django/hello/static
<Directory /home/vagrant/dev/python/django/hello/static>
    Require all granted
</Directory>

apacheユーザがhelloプロジェクトにアクセスできるようにする。

$ sudo usermod -a -G vagrant apache
$ sudo chmod 775 -R ~/dev/python/django/hello/
Apache Httpサーバの起動

あとはApache Httpサーバを起動して、http://192.168.33.10/http://192.168.33.10/adminで開発用サーバと同じ画面が見れるはず。

# systemctl start httpd
# systemctl enable httpd

ただ1点気になったのが、Apache Httpサーバのエラーログに/はないと言われるのはなんでだろう。。

[wsgi:error] [pid 4116] Not Found: /

Django1.9+Bootstrap3でログイン表示を作る

認証・認可は、Djangoに限らず、Webフレームワークを使い出してまず外せない機能。

Django公式マニュアルを見てみると、機能としてデフォルトで持っていて、それを用途に応じて拡張していく方針とのこと。 Users, Groupモデル、パスワードをハッシュ化して保持して保持するといった提供されている。

1回やり方を確認しておけば何にでも応用できそうなのででBootstrap3を利用し、ログイン画面を表示してみる。

環境

環境としては、こちら。

項目 バージョン
Python 3.5.1
Django 1.9.5
jQuery v2.2.2
Bootstrap v3.3.6

Djangoの認証

Djangoで認証・認可の機能は、django.contrib.authで使用できる。これはプロジェクトを生成するときに元から含まれている。プロジェクト作成後、マイグレーション(python manage.py migrate)とすると認証・認可用のテーブルが作成される。

つくってみる

はじめにプロジェクトから作成する。 radishはただのプロジェクトの例なので、適宜変更してください。

$ django-admin startproject radish
$ python manage.py migrate

次にaccountsdashboardアプリを作成する。 accountsには認証・認可機能を含めで、dashboardには認証された後のリダイレクト先のアプリを含める想定。

$ python manage.py startapp accounts
$ python manage.py startapp dashboard

アプリを作ったら、忘れずsettings.pyINSTALLED_APPSに追記しておく。これを忘れると、後で出てくるテンンプレートが存在しないというエラーが発生するので、忘れずに。

  • radish/radish/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # local
    'accounts.apps.AccountsConfig',
    'dashboard.apps.DashboardConfig',
]
Bootstrapを配置する

JavascriptCSSのライブラリは、radish/staticフォルダ内に入れておく。するとsettings.pyの下記の記載に沿って、参照可能となる。

  • radish/radish/settings.py
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/

STATIC_URL = '/static/'

STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
)

jQueryやらBootstrapやらをインストールする。それぞれダウンロードするのが面倒であったので、Bowerで入れる。

  • radish/static/
$ bower init
...
(対話型でbower.jsonを作成)
...
$ bower install jquery -S
$ bower install bootstrap -S
トップ画面、ログイン画面、ログイン後画面(ダッシュボード画面)を作る

各画面共通で使うNavbarなどはbase.htmlに記述し、他の画面はそれを継承する。またそういう共通のHTMLはradish/templates配下に入れておく。しかし、このパスはDjangoが見つけてくれないので、またsettigs.pyに追記する。

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 追記箇所
        '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',
            ],
        },
    },
]

それではbase.html{% load staticfiles %}と書くと、下のように{% static xxx%}でjsやcssを利用できる。{% if user.is_authenticated %}~{% else %}で条件分岐しているところは、認証されている場合のみ有効にするメニュー表示である。また {% block container %}~{% endblock %}は、base.htmlを継承した方のHTMLが埋め込まれる箇所となっている。

{% load staticfiles %}

<!DOCTYPE html>
  <html lang="ja">
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">

      <tile>{% block title %} Radish | {{ site_name }}{% endblock %}</tile>

      <!-- CSS -->
      <link href="{% static 'bootstrap/dist/css/bootstrap.min.css' %}" rel="stylesheet" />
      <link href="{% static 'bootstrap/dist/css/bootstrap-theme.min.css' %}" rel="stylesheet" />
      <link href="{% static 'font-awesome/css/font-awesome.css' %}" rel="stylesheet" />
  </head>
  <body>
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
          <div class="navbar-header">
            <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
              <span class="sr-only">Toggle navigation</span>
              <span class="icon-bar"></span>
              <sapn class="icon-bar"></sapn>
              <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="#">Radish</a>
          </div><!-- /.navbar-header -->

          <div id="navbar" class="navbar-collapse collapse">
            {% if user.is_authenticated %}
              <ul class="nav navbar-nav navbar-right">
                  <li>
                      <a href="#">Dashboard</a>
                  </li>
                  <li>
                      <a href="#">Settings</a>
                  </li>
              </ul>
            {% else %}
              <form class="navbar-form navbar-right">
                  <button type="button" class="btn btn-primary"
                    onclick="location.href=&quot;{% url 'login' %}&quot;;">
                    Login</button>
              </form>
            {% endif %}
          </div><!-- /.navbar-collapse -->
        </div><!-- /.container -->
    </nav>

    {% block container %}
    {% endblock %}

    <hr/>
    <footer>
      <p>&copy; hermesian</p>
    </footer>

    <!-- JavaScript -->
    <script src="{% static 'jquery/dist/jquery.min.js' %}"></script>
    <script src="{% static 'bootstrap/dist/js/bootstrap.min.js' %}"></script>
  </body>
</html>

次にトップ画面home.htmlは下記。 これはBootstrapのjumbotronの例をそのまま利用。

{% extends 'base.html' %}

{% load staticfiles %}

{% block container %}
<!-- Main jumbotron for a primary marketing message or call to action -->
<div class="jumbotron">
    <div class="container">
      <h1>Radish</h1>
      <p>
          "Radish is a dashboard for collecting visualizing report."
      </p>
    </div>
</div>
<div class="container">
      <!-- Example row of columns -->
      <div class="row">
        <div class="col-md-4">
          <h2>Heading</h2>
          <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
          <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
        </div>
        <div class="col-md-4">
          <h2>Heading</h2>
          <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
          <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
       </div>
        <div class="col-md-4">
          <h2>Heading</h2>
          <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
          <p><a class="btn btn-default" href="#" role="button">View details »</a></p>
        </div>
      </div>
  </div>
{% endblock %}

home.htmlのログインボタンで遷移する先は、accounts/tempalte/accounts/login.html<form>タグの下に{% csrf_token %}とあるのは、DjangoCSRF対策機能である。

{% extends 'base.html' %}

{% load staticfiles %}

{% block container %}
<div class="container">
  <div class="page-header">
      <h3>
        Login
      </h3>
  </div>
  <div class="row">
    <div class="col-md-4 col-md-offset-4">
        <div class="panel panel-default">
            <div class="panel-body">
                <form accept-charset="utf-8" role="form" action="{% url 'login' %}" method="post">
                    {% csrf_token %}
                    <fieldset>
                        <div class="form-group">
                            <input type="text" id="username" name="username" class="form-control"
                                   placeholder="Username" required autofocus>
                        </div>
                        <div class="form-group">
                            <input type="password" id="password" name="password" class="form-control"
                                   placeholder="Password" required>
                        </div>
                        {% if login_failed %}
                          <p class="text-danger">Sorry, that login was invalid.  Please try again.</p>
                        {% endif %}
                        <input class="btn btn-lg btn-success btn-block" type="submit" value="Log in">
                    </fieldset>
                </form>
            </div>
        </div>
    </div>
</div>
{% endblock %}

最後に認証後のdashboard画面dashboard/template/dashboard.html。といっても今回は空っぽ。

{% extends 'base.html' %}
ビューとルーティングを作る

accountsアプリ

  • radish/accounts/views.py

何かしらで認証されっぱなしの状態にならないように、logoutを最初に呼んで初期化してる。

import logging
from django.shortcuts import render_to_response, HttpResponseRedirect
from django.template import RequestContext
from django.contrib.auth import authenticate, login, logout

logger = logging.getLogger(__name__)


def login_view(request):

    #強制的にログアウト
    logout(request)
    username = password = ''

    login_failed = False

    if request.POST:
        username = request.POST['username'].replace(' ', '').lower()
        password = request.POST['password']
        user = authenticate(username=username, password=password)
        if user is not None:
            if user.is_active:
                login(request, user)
                return HttpResponseRedirect('/dashboard')
        else:
            login_failed = True

    return render_to_response('accounts/login.html',
                              {'login_failed': login_failed},
                              context_instance=RequestContext(request))

上で作ったビューをwebブラウザから/accounts/loginでアクセスできるよう設定する。

  • radish/accounts/urls.py
from django.conf.urls import url
from .views import login_view

urlpatterns = [
    url(r'^login/$', login_view, name='login'),
]

dashboardアプリ

  • radish/dashboard/views.py

@login_requiredをつけて、認証後のみアクセス可能なようにしている。

import logging

from django.shortcuts import render
from django.contrib.auth.decorators import login_required


@login_required
def index(request):
    return render(request, 'dashboard/index.html')
  • radish/dashboard/urls.py
from django.conf.urls import url
from .views import index

urlpatterns = [
    url(r'^$', index, name='dashboard'),
]
動作確認

ユーザを作成する。今回はスーパーユーザを造り、そのままそのアカウントでログインしてみる。

$ python manage.py createsuperuser
...
(対話的に作成)
...

動作確認として、開発サーバを起動$ python manage.py runserver、webブラウザからhttp://localhost:8000/にアクセスして、先ほど登録したスーパーユーザでログインし、NavbarにDashboard, Settingsというメニューが出ていることを確認する。

Djangoのフロー

DjangoフルスタックなWebフレームワークで、Ruby on RailsのようなDBマイグレーションができたり、管理GUIを簡単に作る機能があるなど、使えたら便利そう。

1) プロジェクトを作成する

$ django-admin startproject (プロジェクト名)

2) アプリケーションを作成する

$ python manage.py startpapp (アプリケーション名)

3) ビューを作成する

3)-1. (アプリケーション名)/views.pyを改修する
3)-2. (アプリケーション名)/urls.pyを改修する
3)-3. テンプレート(*.html)を作成する (js, cssは(プロジェクト名)/staticディレクトリ配下に入れる)

*テンプレート単体で確認できないのかな?

4) モデルを作成する

4)-1. (プロジェクト名)/(プロジェクト名)/settings.pyでデータベースを設定する
4)-2. (アプリケーション名)/models.pyを改修する
4)-3. (プロジェクト名)/(プロジェクト名)/settings.pyのINSTALLED_APPSを編集し、モデルを有効にする
4)-4. マイグレーションを実施する

$ python manage.py makemigrations (アプリケーション名)
$ python manage.py sqlmigrate (アプリケーション名) 0001  <= データベーススキーマを確認
$ python manage.py migrate

*ER図->DB--(リバース)-->モデル--(モデル修正)-->モデル'--(マイグレーション x n回)-->DB'の方がいきなりコードを書けないので、馴染める。

JacksonでLombokを使ったBeanにマッピングできない

同じ轍を踏まないようにメモ。

事象

JSONファイルから設定を読み込むことをしたく、Jacksonを使い、アクセッサーメソッドの記述を省力化しようとLombokを使った。そのとき下記のように記載するとアクセッサーメソッドがスネークケースになって(実際スネークケースになる設定というわけではなく、頭文字を大文字にして繋げる仕様で、"_"だからそれに対応できなかっただけのような気がする気がする...)、アンマーシャルすることができないエラーが発生する。

f:id:hermesian:20160207222514p:plain

import lombok.Getter;
import lombok.Setter;

public class Bean {

    @Getter
    @Setter
    private Parent1 _parent1;

    public class Parent1 {

        @Getter
        @Setter
        private String _child1;

        @Getter
        @Setter
        private String _child2;

        @Getter
        @Setter
        private String _child3;
    }
}
対策

属性の接頭辞には、"_"を使用しないこと。

f:id:hermesian:20160207222521p:plain

import lombok.Getter;
import lombok.Setter;

public class Bean {

    @Getter
    @Setter
    private Parent1 parent1;

    public class Parent1 {

        @Getter
        @Setter
        private String child1;

        @Getter
        @Setter
        private String child2;

        @Getter
        @Setter
        private String child3;
    }
}
バージョン

pom.xmlの内容は下記。

...以上、省略....
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.6</version>
        </dependency>
...以下、省略....

初段になりました

初めて審査なるものを受けました。 あいにくの雨。そして傘を忘れた...

審査は、術科試験と学科試験の2種。 ウェイトは不明だが、まれに学科で落ちることがあるとのことなので、術科にウェイトがあるような印象。

術科試験

一手座射が内容です。体配(執弓の姿勢、歩き方、坐しての回り方)が何よりも重要。 明らかに練習不足...。揖してからのモタつき、極め付けは射位を行き過ぎてしまいました。

学科試験

今年から学科の問題が公表されました(http://osaka-kyudo.jp/mondai_chiren_rengou.pdf)。 「A群、B群からそれぞれ1問、計2問を出題する。」を読んでいなかったため、「45分でこの量を書くのか...」と思い、キーワードを抑えることに集中していました。 終わった後、周りの方はほぼ15行びっしり記載されており、短すぎたかと焦りました(緊張すると震えてスラスラ書けない...)が、結果的には通ったようです。次は小論文などある程度の文量は書くトレーニングをし、そして硬筆の練習して臨みたいところ。

初心者弓道教室に参加して

初心者教室について

流山に越してから腰を据えて弓道を始めようと思い、まずは地域の弓道協会が主催する初心者教室に参加してみた。 計8回(5/30, 5/31, 6/6, 6/13, 6/14, 6/20, 6/21, 6/27)で先週がちょうど最終回だった。

  • 対象者は?

初心者でなくとも段を持っていない場合、様子をうかがえることから参加する方が良いと思う。 各地の道場における独自ルールもあると思うので。

課題

内省の意味を込めて、ここに記録しておく。

  • 足踏み
    • 左足の角度が開きすぎ
    • 幅が狭い
    • 胴が曲がっている(自分が正面であると思ったところから5cm後ろであった)
  • 弓構え
    • 手の内では指の「腹」でつかむ
  • 大三
    • 妻手は引かない
    • 肘を張る
  • 引分け
    • 左右に均等に引く(斜め後ろではなく)
  • 離れ
    • 射る瞬間に目をつぶることがある

その他

  • 弦を掛ける際は、順手
  • 矢番えの際は、気持ち前傾姿勢(仰け反って見えるときがある)

APIで取得したjsonが文字化けする

備忘録としてメモ。 やりたいことは、REST API提供されているデータを取得して、保存することです。

また使用しているpythonの環境は、以下。

 python --version
Python 3.4.3 :: Anaconda 2.2.0 (x86_64)

使用するデータは、e-StatのAPIから取得します。 APIを使用するに歳しては、まずユーザ登録をして、アプリケーションIDを発行しなくてはなりません。

www.e-stat.go.jp

提供されている機能としては5種類あります。 主に使いそうなものとしては、下記の2点になるかと思います。

  1. 使いたいデータの政府統計コードを使って、「統計表情報取得」機能から利用したい統計表IDを取得
  2. 「統計データ取得」機能からデータを取得

今回は、2点目の機能を利用。 平成22年国税調査 速報集計(0003033021)を例として、取得してみました。

#! /usr/bin/env python
#-*- coding: utf-8 -*-

import urllib.request, urllib.parse
import json, sys

def getEStatData():
    jsonUrl = "http://api.e-stat.go.jp/rest/2.0/app/json"
    apiKey = "(YOUR API KEY)"
    statId = "0003033021" # 平成22年国税調査 速報集計

    statsDataUrl = "%s/getStatsData?appId=%s&statsDataId=%s"
    url = (statsDataUrl % (jsonUrl, apiKey, statId))
    print(url)
    f = urllib.request.urlopen(url)
    jsonData = json.loads(f.read())
    print (jsonData)

    file = open('StatsData.json', 'w')
    json.dump(jsonData, file, sort_keys=True, indent=4)
    file.close()

if __name__ == '__main__':
    sys.exit(getEStatData())

urlib.request.urlopenがbyte型を返すことに注意(参考; http://docs.python.jp/3/library/urllib.request.html)

TypeError: the JSON object must be str, not 'bytes'

下記のようにutf-8にデコードすると日本語のままデータが取得できますが、このようにデコードした場合でもERROR_MSG":"\u6B63\u5E38\u306B\u7D42\u4E86\u3057\u307E\u3057\u305F\u3002"Unicodeエスケープされてしまう場合があったのですが、そんなときにはunicode-escapeを指定することで、回避していました。(正直、違いがわからない)。

    jsonData = json.loads(f.read().decode('utf-8'))
    print (jsonData)

一方で、出力したjsonファイルの方は、Unicodeエスケープされてしまいます。 これはjson.dumpsの引数であるensure_asciiがデフォルトでTrueになっており、入ってくるデータをエスケープする仕様になっていることに起因します。

従って、それをFalseにすることで、ファイルに書き出したときも日本語が保つことができます。

    file = open('StatsData.json', 'w')
    json.dump(jsonData, file, sort_keys=True, indent=4, ensure_ascii=False)
    file.close()

if __name__ == '__main__':
    sys.exit(getEStatData())