似非プログラマのうんちく

「似非プログラマの覚え書き」出張版

巳年じゃないけど Python やろうぜ(その 22・最終回)

すっかり忘れていましたが、Python ネタの締めにこれの話をすると約束していたので書いておきます。

WSGI による Apache HTTPD との連携

Python では WSGI(Web Server Gateway Interface) という、Web サーバーと Web アプリケーションを接続する標準化されたインターフェースを利用して Web アプリケーションを Web サーバー上で公開することができます。

Django には WSGI を利用してアプリケーションを公開するための仕組みがあらかじめ用意されているので、それほど特別なことをする必要はありません。

まずは Python 側で mod_wsgi をインストールします。Windows 機の場合はこちらで公開されているものを使うのが良いでしょう。お使いの Apache HTTPD のバージョンに合わせたものをインストールしましょう。

次に、httpd.conf に以下の部分を追加します。(project_forder) の部分は django_test プロジェクトを作ったフォルダに読み替えます。

LoadModule wsgi_module "C:/Python/3.6/Lib/site-packages/mod_wsgi/server/mod_wsgi.cp36-win_amd64.pyd"

WSGIScriptAlias /wsgi-scripts "(project_forder)/django_test/django_test/wsgi.py"
WSGIPythonPath "(project_forder)/django_test"
Alias /static/ "(project_forder)/django_test/static/"

<Directory "(project_forder)/django_test/django_test">
   <Files wsgi.py>
      Require all granted
   </Files>
</Directory>

<Directory "(project_forder)/django_test/static">
  Require all granted
</Directory>

基本的に WSGI による Apache HTTPD との連携はこれで良いのですが、Django においてはこのままだと CSSJavaScript、各種画像ファイルなどのいわゆる「静的ファイル」が上手く読み込めません。そこで

> python manage.py collectstatic

を実行して静的ファイルを Apache が認識できるフォルダにまとめておきます。

Apache HTTPD のサーバーにアクセスして、Django アプリケーションが使えることを確認してみてください。

Django 2.0 における変更点

昨年の 12 月に正式に Django 2.0 がリリースされました。大きな変更点としては Python 2 系のサポートを打ち切ったことでしょう。今後 DjangoPython 3 系のみをサポートしていくことになります。

細かな変更点として、URL ルーティングが従前の正規表現を用いた書き方に加えて、よりシンプルな表記が可能になった(django.urls.path)ようです。

from django.urls import path
from django.views.generic import ListView, DetailView
from .models import City

app_name = 'major_city'
urlpatterns = [
    path(
        '',
        ListView.as_view(
            queryset=City.objects.select_related().all(),
            context_object_name='cities',
            template_name='major_city/list.html'
        ),
        name='index'
    ),
    path(
        '<int:pk>/',
        DetailView.as_view(
            model=City,
            context_object_name='city',
            template_name='major_city/detail.html'
        ),
        name='show'
    ),
]

それでは皆様、良き Python & Django ライフを !

JUnit 5 でテストコードがだいぶ変わったという話

JUnit 5 がとっくの昔にリリースされてたのを今頃になって確認してみたらいろいろ変わってた話。

アプリケーションコード

すごーい(CV : 尾崎由香)簡単な例でスマソ。

package jp.mydns.akanekodou;

/**
 * <p>文字列から整数への変換メソッド提供クラス</p>
 *
 * @version 1.1.1
 * @author akaneko3
 */
public class ParseNum {
    /**
     * <p>数値文字列を整数に変換する。</p>
     *
     * @param str 整数を表す文字列
     * @return 変換された整数値
     * @see Integer#parseInt(String)
     */
    public int parseNum(String str) {
        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException nfe) {
            // NumberFormatException を catch したら
            // メッセージつきの NumberFormatException を throw する
            throw new NumberFormatException("入力された値が整数値を表していません。");
        }
    }
}
package jp.mydns.akanekodou;

import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

/**
 * <p>
 * メインクラス<br />
 * 標準入力から取り込んだ文字列を整数値に変換する。
 * </p>
 *
 * @version 1.1
 * @author akaneko3
 */
public final class App {
    /**
     * <p>
     * private コンストラクタ<br />
     * (このクラスはインスタンスを生成しない)
     * </p>
     */
	private App() {}

    /**
     * <p>メインメソッド</p>
     *
     * @param args コマンドライン引数
     */
	public static void main(String[] args) {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        try {
            System.out.print("整数を入力してください: ");
            String str = br.readLine();
            ParseNum cn = new ParseNum();
            int value = cn.parseNum(str);
            System.out.println(value);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
続きを読む

巳年じゃないけど Python やろうぜ(その 21)

PostgreSQL の利用

Django はデフォルトで SQLite3 を利用しますが、他のデータベースを活用することもできます。今回はその中から PostgreSQL を利用してみようと思います。

PostgreSQL のインストールと設定

Windows の場合は GUI インストーラもあって、比較的楽にインストール・設定ができると思います。

Linux についてはいろいろと設定を変更しないといけないっぽくて、とりあえず Fedora に関するものについては Qiita にまとめました。
Fedora に PostgreSQL をインストールして使えるようにするまで - Qiita

Django から利用するためのデータベースを作成しておきます。Linux の場合は postgres というユーザーが作成されているはずですので、以下のコマンドを実行します。

su - postgres
createdb django

Django の設定

PostgreSQLDjango から利用する場合、あらかじめ psycopg2 をインストールする必要があります。

pip install psycopg2

settings.py の中のデータベースに関する部分を以下のように書き換えます。

# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'django',
        'USER': 'postgres',
        'PASSWORD': '(snip)',
        'HOST': 'localhost',
        'PORT': 5432,
    }
}

マイグレーションと初期データの投入を再実行します。

./manage.py migrate
./manage.py loaddata initial_data

管理者ユーザーの情報もデータベースに格納されていますので、切り替えに伴って再度作成しておく必要があります。

django-admin createsuperuser

次回以降、WSGI による Apache HTTPD サーバとの連携についてお話をして、今回のシリーズはひとまず終了としたいと思います。

Memcached をインストールする

Python シリーズ番外編で Memcached のインストール方法を紹介します。

Windows の場合

こちらが参考になります。

Windows で Memcached を使う - make world

memcached-win64-1.4.4-14.zip をダウンロードしてきて適当なフォルダ(例えば C:\memcached) に解凍します。

あとはコマンドプロンプト

C:\memcached\memcached -d install
C:\memcached\memcached -d start

いささか古いバージョンになってしまいますが、最新のものを Windows でビルドする方法が良く分からないのでパス。

これを利用するとビルドできる*1っぽい ?
GitHub - LukeCarrier/windows-memcached: Abandoned attempt at getting memcached built on Windows

Fedora の場合

sudo dnf install memcached
sudo systemctl enable memcached
sudo systemctl start memcached

こちらも若干古いバージョンですが、最新版より少し遅れているだけなので大きな問題はなさそうです。あとは適宜 sudo dnf upgrade しておけば、更新が来た段階でアップデートされますし。

一応自前でビルドも試しましたがいまいちでした。他のディストリビューション(CentOS, Ubuntu, etc..)ではまた少し事情が変わってくるかもしれませんが。

*1:しかし作者は「Windows はひどいサーバープラットフォームだからこれは使うな」と言っている

巳年じゃないけど Python やろうぜ(その 20)

Ajax の活用(続き)

バリデーションエラーの出力

フォームのバリデーションエラーに関する情報は form.errors に辞書形式で格納されているので、それを利用すれば行けることがわかりました。

views.py

from django.shortcuts import render
from django.http.response import JsonResponse
from django.utils.translation import ugettext as _
from .forms import CustomerForm
from .models import Customer

# Create your views here.


def index(request):
    form = CustomerForm(label_suffix='')
    return render(request, 'customer_search/search.html', {
        'form': form,
    })


def search(request):
    form = CustomerForm(request.POST)
    if form.is_valid():
        customer_id = request.POST['customer_id']
        try:
            customer = Customer.objects.get(pk=customer_id)
            customer_name = customer.name
            customer_phone = customer.phone
        except Customer.DoesNotExist:
            customer_name = _('No data!')
            customer_phone = _('No data!')
    else:
        customer_name = _('Error!')
        customer_phone = _('Error!')
    return JsonResponse({
        'customer_name': customer_name,
        'customer_phone': customer_phone,
        'errors': form.errors,
    })

メソッド名が微妙に変わってるので、対応して urls.py も修正しておきます。

from django.conf.urls import url
from . import views

app_name = 'customer_search'
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^search/$', views.search, name='search'),
]

templates/customer_search/search.html

{% load static i18n %}
{% trans 'customer search' as page_title %}
<!doctype html>
<html>
  <head>
    <title>{{ page_title | title }}</title>
    <link rel="stylesheet" href="{% static 'customer_search/css/search.css' %}">
    <script src="//code.jquery.com/jquery-3.2.0.min.js"></script>
    <script src="{% static 'customer_search/js/js.cookie.js' %}"></script>
    <script src="{% static 'customer_search/js/csrf_token.js' %}"></script>
  </head>
  <body>
    <h1>{{ page_title | title }}</h1>
    <form method="post" action="{% url 'customer_search:search' %}">
      {% csrf_token %}
      <table>
        {% for field in form %}
          <tr>
            <th>{{ field.label_tag }}</th>
            <td>
              {{ field }}
              <span class="error" id="{{ field.html_name }}_errors"></span>
            </td>
          </tr>
        {% endfor %}
      </table>
      <input type="submit" value="{% trans 'Search' %}">
    </form>
    <script src="{% static 'customer_search/js/search.js' %}"></script>
  </body>
</html>

span 要素は DOM で追加する方がスマートかなと思ったけど、やってみると意外に苦戦した*1ので、とりあえずこうしておきます。

static/customer_search/js/search.js

$('form').submit(function (event) {
  event.preventDefault();
  $.post(
    $('form').attr('action'),
    { 'customer_id': $('#id_customer_id').val() },
    function (res) {
      $('#id_customer_name').val(res.customer_name);
      $('#id_customer_phone').val(res.customer_phone);
      var errors = res.errors;
      var messages = [];
      if(!$.isEmptyObject(errors)) {
        messages = errors.customer_id;
      }
      $('#customer_id_errors').text(messages.join(''));
    }
  );
});

エラーメッセージはフォームのフィールド名ごとに配列で格納されているので、join で連結*2します。

画面遷移が発生していないことが確認できます。

*1:自分が下手な書き方をしたせいでエラーメッセージが消えずにどんどん増えていくという間抜けなことにw

*2:今回のケースでは複数のエラーメッセージが格納されるケースは確認できていないので、必要ないと言えば必要ないのですがw

巳年じゃないけど Python やろうぜ(その 19)

Ajax の活用

以前作成した customer_search アプリケーションを Ajax を活用して作り変えてみましょう。

Ajax 活用のための準備

Ajax 活用のために、いくつかの準備が必要です。

1. js.cookie.js をダウンロードする

GitHub - js-cookie/js-cookie: A simple, lightweight JavaScript API for handling browser cookies

上記サイトから js.cookie.js をダウンロードします。間違っても script 要素の src 属性に URL を直接打ち込まないように(GitHub は CDN ではありません !)。

持って来た js.cookie.js は django_test/customer_search/static/customer_search/js/ に保存します。

2. csrf_token.js の作成

以下の内容で django_test/customer_search/static/customer_search/js/ 内に csrf_token.js を作成します。ほぼ公式ドキュメントからのコピペです。

var csrftoken = Cookies.get('csrftoken');

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function(xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader('X-CSRFToken', csrftoken);
    }
  }
});
続きを読む

巳年じゃないけど Python やろうぜ(その 18)

キャッシュサーバーの活用

これまではセッションの情報をデータベース(SQLite3)で管理してきましたが、別途キャッシュサーバーを用意して、そちらで管理することもできます。

キャッシュサーバーとしてよく使われるのが Memcached です。Memcachedインストールは済んでいて、サービスが起動しているものと仮定します。*1

python3-memcached をインストールします。

$ pip install python3-memcached

django_test/settings.py に以下を追記します。

# Session engine
# https://docs.djangoproject.com/en/1.10/ref/settings/#session-engine
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'


# Cache
# https://docs.djangoproject.com/en/1.10/ref/settings/#caches
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '127.0.0.1:11211',
    }
}

コメントは参照すべきドキュメントの URL を明示するためにつけただけなので、実際には無くても大丈夫です。

Memcached が正しくインストールされていて、かつサービスが開始されていれば、他は何も変更する必要はありません。

Memcached の各 OS ごとのインストール方法については、記事を改めて解説させていただきます。

*1:WindowsLinux 系 OS とで手順が異なります。