akiyoko blog

akiyoko の IT技術系ブログです

PayPal 決済の最新事情 〜 Django と PayPal と私 〜

この投稿は 「Django Advent Calendar 2016 - Qiita」 の 22日目の記事です。



この記事では、「Django と PayPal REST API で In-Context Window による PayPal 決済フロー」を実装・検証します。

はじめに謝罪しておきますが、「Django Advent Calendar」にも関わらず、結果として Django はオマケ程度にしか扱っていません。。


 

はじめに

オンライン決済の仕組み

ECサイトの決済処理の仕組みを簡単に図解すると、以下のようになります。

f:id:akiyoko:20160515172525p:plain
10 Safe and Popular Gateways for Online Payment Processing | InstantShift を参考に作成)


決済業者と直接契約するのはいろいろと面倒なので、代わりに決済代行業者と契約することで、Visa や Master Card などの各種クレジットカード(決済代行サービスによっては銀行振込やコンビニ決済も)が ECサイトで利用できるようになります。



少し細かい話になりますが、決済代行サービスごとに、決済処理システムとのやり取りの方式が異なります。

決済処理システムとの接続方式には、大きく分けて次の二種類があります。

接続方式 説明
リンク(画面遷移)方式 決済代行サービスのサイトに一旦遷移してクレジットカード情報(あるいは決済代行サービスのアカウント)などを入力させる方式
直接決済方式 画面遷移をおこなわず、バックエンドで直接決済処理をおこなう方式。実現方式や実装方法によってさらに細分化される。 *1


現在のところ、PayPal の接続方式は「リンク方式」のみ となっています。PayPal には「Direct Credit Card Payments」という直接決済方式の決済サービスもあるのですが、残念ながら(利用できるのは UKのみで)日本では利用できません。 *2

なお、GMOペイメントゲートウェイゼウス などの決済代行業者ではそれぞれの方式の決済代行サービスが各種取り揃えられています。また、最近本番サービスインとなった話題の Stripe は「直接決済方式」となっています。


なぜ PayPal?

私が必要としている決済サービスとしては、

  • 海外からの決済が可能(JPY以外の通貨が扱える) *3

というのを最低条件としていました。

そのほか、

  • PayPal に慣れている
  • PayPal のビジネスアカウントを既に持っている

という理由から、PayPal を第一候補に考えています。


PayPal の ECサイト用オンライン決済にもいろいろと実装方式(使用する API の種類など)があるのですが、

  • Braintree v.zero がプロダクション利用できるのは 2017年以降(2018年?)
  • Classic API よりも REST API を使いたい
  • In-Context Window を使ったフローの方が離脱が少ない

という事情を勘案して、「In-Context Window による決済フローを PayPal REST API で実装」するのがベストな選択肢であるという結論に達しました。


 

In-Context Window とは?

今回検証した PayPal のオンライン決済パターンは、下図のようなものになります。

f:id:akiyoko:20161222132204p:plain
NVP/SOAP Integration - PayPal Developer の図を元に作成。緑枠:自サイト、青枠:PayPal サイト)

① ショッピングカート画面(「PayPal で支払う」ボタン)
    ↓
② (In-Context Window 内)ログイン画面(「ログイン」ボタン)
    ↓
③ (In-Context Window 内)支払承認画面(「支払いに同意」ボタン)
    ↓
④ 決済完了画面


In-Context Window は、過去記事 の「5. ポップアップウィンドウ型(In-Context Window)」(小さなポップアップを立ち上げてその内部で PayPal サイトを表示させる新しいタイプの画面遷移パターン)に該当する決済フローです。

これまでの Express Checkout と違って、全画面が PayPal 決済ページにリダイレクトされることなく、小さなポップアップが立ち上がってその中で PayPal 決済ページを表示するというのが最大の特徴です。


<過去記事>
akiyoko.hatenablog.jp



これが、PayPal の数あるオンライン決済パターンの中で一番シンプルで最も新しいパターンになるかと思います。


また PayPal の公式ページでも、

PayPal no longer recommends full-page redirects to PayPal for Express Checkout. Instead, Express Checkout only requires a payment ID generated by the REST Payments API, or a payment token generated by the legacy NVP/SOAP APIs, to initiate and finalize a payment.


https://developer.paypal.com/docs/integration/direct/express-checkout/integration-jsv4/other-integrations/

ということで、全画面をリダイレクトするような画面遷移は今後はオススメしないということなので、今後の PayPal 決済では In-Context Window 型のフローが増えてくると思われます。



In-Context Window 型の通常決済(Checkout)を利用するには、フロント側に「checkout.js」という PayPal 謹製の JSライブラリを読み込ませて(*4)、あとは決まったやり方に則って実装するだけで OK です。

checkout.js は少し前まで V 3.5.0 だったのですが、現在の最新バージョンは V.4.0.0 となって、実装方法が若干変更されています。 *5


今回、V 4.0.0 で検証する前に V 3.5.0 でも実装・検証してみたのですが、より簡単に、よりセキュアに実装できるようになったという印象です。

具体的には、V 4.0.0 になって、

  • PayPal ボタン生成のときに、PAYPAL_CLIENT_ID を画面に晒さなくてよくなった
  • Create Payment のときに、生成した Payment から「redirect_url」を取り出してリダイレクトしなくてよくなった *6

などのうれしい変更点がありました。


 

PayPal REST API とは?

PayPal REST API は、PayPal が提供する様々な決済サービスを利用することができる RESTful API です。現時点で、PayPal が実現できるほぼ全ての決済サービスを網羅しているようです。 *7


なお、PayPal REST API は OAuth 2.0 プロトコルによる認可システムを採用しており、PayPal Developer サイトで作成した売り手アカウントの Credential(Client ID および Secret)を使用して各 API 呼び出しに必要なトークンを払い出します。 *8


Credential の作成方法については、ここでは説明を省略します。 *9



先に述べたように、PayPal REST API には様々な API の種類がありますが、オンライン決済処理ではその中から「Payments API」を使用すれば事足りるでしょう。 *10


PayPal REST API を便利に利用するためのライブラリとして、Python であれば PayPal Python SDK が 本家 PayPal から提供されていますので(Python のほかにも Java, PHP, .NET, Ruby, Node.js など各種言語向けの SDK が揃っています)、 pip でインストールして使います。


 

Django パッケージは使わないの?

Django Packages : Payment ProcessingDjango Packages : django SHOP plugins などで、PayPal に対応している決済パッケージをチェックしてみたのですが、

  • Django 1.10
  • PayPal REST API
  • In-Context Window(checkout.js V 4.0.0)

に対応しているものは今のところ見当たりません。


スターの多い順に確認してみると、django-merchant は「PayPal Website Payments Pro」のみ対応ということで日本では利用不可、django-lfs は「PayPal Payments Standard」のみ対応ということで API が古くて NG、django-paypal も「PayPal Payments Standard」または「PayPal Website Payments Pro」のみ対応ということで先の二つと同じでした。


そもそも「PayPal REST API で In-Context Window 決済」は自前で実装してもそんなに大変ではなくて、Django パッケージをわざわざ使うまでもないといった印象です。




ということで、前置きがずいぶん長くなってしまいましたが、「Django と PayPal REST API で In-Context Window による PayPal 決済フロー」を実際に試していきます。



実装前には、以下のドキュメントをざっと読んでおくことをお勧めします。


 

実装

購入する商品と合計金額が表示されたカート画面から PayPal 決済をおこなうことを想定した、「shop」アプリケーションを作ってみます。

/opt/webapps/myproject/
├── config
│   ├── __init__.py
│   ├── local_settings.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── shop
│   ├── apps.py
│   ├── __init__.py
│   ├── urls.py
│   └── views.py
└── templates
    ├── base.html
    ├── error.html
    └── shop
        ├── base_shop.html
        ├── cart.html
        └── complete.html


動作確認をおこなった Python および Django のバージョンは以下の通りです。

  • Python 2.7.6
  • Django 1.10



ソースコードは GitHub に置きました。
github.com



 

Settings

conf/settings.py (抜粋)

# Application definition

INSTALLED_APPS = [
    ...
    'shop',
]
...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
        },
    },
]
...
# LOCAL SETTINGS
PROJECT_APP_PATH = os.path.dirname(os.path.abspath(__file__))
PROJECT_APP = os.path.basename(PROJECT_APP_PATH)
f = os.path.join(PROJECT_APP_PATH, 'local_settings.py')
if os.path.exists(f):
    import sys
    import imp
    module_name = '%s.local_settings' % PROJECT_APP
    module = imp.new_module(module_name)
    module.__file__ = f
    sys.modules[module_name] = module
    exec (open(f, 'rb').read())


conf/local_settings.py (抜粋)

DEBUG = True

# PayPal
PAYPAL_MODE = '<paypal-mode>'  # 'sandbox' or 'live'
PAYPAL_CLIENT_ID = '<paypal-client-id>'
PAYPAL_CLIENT_SECRET = '<paypal-client-secret>'

 

URLConfs

shop/urls.py

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^cart$', views.ShowCartView.as_view(), name='cart'),
    url(r'^create-payment$', views.CreatePaymentView.as_view(), name='create-payment'),
    url(r'^execute-payment$', views.ExecutePaymentView.as_view(), name='execute-payment'),
]

 

Views

shop/views.py

import logging

from django.conf import settings
from django.contrib import messages
from django.http import JsonResponse, Http404
from django.shortcuts import render, reverse
from django.views.generic import View
import paypalrestsdk

logger = logging.getLogger(__name__)


class ShowCartView(View):
    def get(self, request, *args, **kwargs):
        return render(request, 'shop/cart.html', {
            'paypal_mode': settings.PAYPAL_MODE,
        })


class CreatePaymentView(View):
    def post(self, request, *args, **kwargs):
        paypalrestsdk.configure({
            'mode': settings.PAYPAL_MODE,
            'client_id': settings.PAYPAL_CLIENT_ID,
            'client_secret': settings.PAYPAL_CLIENT_SECRET,
        })

        payment = paypalrestsdk.Payment({
            'intent': 'sale',

            # Payer
            'payer': {
                'payment_method': 'paypal',
            },

            # Redirect URLs
            'redirect_urls': {
                'return_url': request.build_absolute_uri(reverse('shop:execute-payment')),
                'cancel_url': request.build_absolute_uri(reverse('shop:cart')),
            },

            # Transaction
            # Note: This is dummy. If production, transaction should be created with reference to cart items.
            'transactions': [{
                # Item List
                'item_list': {
                    'items': [{
                        'name': 'item',
                        'sku': 'item',
                        'price': '5.00',
                        'currency': 'USD',
                        'quantity': 1,
                    }]
                },
                # Amount
                'amount': {
                    'total': '5.00',
                    'currency': 'USD',
                },
                'description': 'This is the payment transaction description.',
            }]
        })

        # Create Payment
        if payment.create():
            logger.info("Payment[{}] created successfully.".format(payment.id))
            return JsonResponse({'success': True, 'paymentId': payment.id})
        else:
            logger.error("Payment failed to create. {}".format(payment.error))
            return JsonResponse({'success': False, 'error': "Error occurred while creating your payment."}, status=500)


class ExecutePaymentView(View):
    def get(self, request, *args, **kwargs):
        # Query strings are always in request.GET
        payment_id = request.GET.get('paymentId', None)
        payer_id = request.GET.get('PayerID', None)

        try:
            payment = paypalrestsdk.Payment.find(payment_id)
        except paypalrestsdk.ResourceNotFound as err:
            logger.error("Payment[{}] was not found.".format(payment_id))
            return Http404

        # Execute Payment
        if payment.execute({'payer_id': payer_id}):
            logger.info("Payment[{}] executed successfully.".format(payment.id))
            messages.info(request, "Your payment has been completed successfully.")
            return render(request, 'shop/complete.html', {
                'payment': payment,
            })
        else:
            logger.error("Payment[{}] failed to execute.".format(payment.id))
            messages.error(request, "Error occurred while executing your payment.")
            return render(request, 'error.html')

Create Payment は
PayPal-Python-SDK/create_with_paypal.py at master · paypal/PayPal-Python-SDK · GitHub
を参考に、Execute Payment は
PayPal-Python-SDK/execute.py at master · paypal/PayPal-Python-SDK · GitHub
を参考にしました。


なお、例外処理は全然ケアしていないので、本番で使う場合には要注意です。


 

Templates

shop/cart.html

{% extends "./base_shop.html" %}

{% block title %}Cart{% endblock title %}

{% block content %}
{{ block.super }}
<div id="paypal-button"></div>

<script src="https://www.paypalobjects.com/api/checkout.js" data-version-4></script>
<script>
    paypal.Button.render({
        env: '{{ paypal_mode }}',
        payment: function (resolve, reject) {
            paypal.request.post('{% url "shop:create-payment" %}', {csrfmiddlewaretoken: '{{ csrf_token }}'})
                .then(function (data) {
                    console.log("data=", data);
                    if (data.success) {
                        resolve(data.paymentId);
                    } else {
                        reject(data.error);
                    }
                })
                .catch(function (err) {
                    console.log("err=", err);
                    reject(err);
                });
        },
        onAuthorize: function (data, actions) {
            return actions.redirect();
        },
        onCancel: function (data, actions) {
            return actions.redirect();
        },
        onError: function (err) {
            // Show an error page here, when an error occurs
        }
    }, '#paypal-button');
</script>
{% endblock content %}

フロントのスクリプトは、

などを参考にしていただければ。


また、POST送信時に「csrfmiddlewaretoken」というパラメータを付与しないと 403エラーになるのは、Django に詳しい皆さんにはお馴染みですよね。



shop/complete.html

{% extends "./base_shop.html" %}

{% block title %}Complete{% endblock title %}

{% block content %}
<span style="font-size: 0.7rem;">{{ payment }}</span>
{% endblock content %}

 

処理概要

ここで、In-Context Window の処理フローを少し解説すると以下のようになります。

  1. paypal.Button.render で PayPal ボタンを表示
  2. ユーザがボタンをクリックすると、paypal.requet.post() で '/shop/create-payment' にリクエストを POST する
  3. サーバ側で商品情報や合計金額、およびPayPal からリダイレクトさせる URL を含めた Payment を create して、payment.id を JSON で返す
  4. checkout.js が勝手に PayPalサイトにリダイレクトしてくれる
  5. In-Conetxt Window 内でユーザが決済を承認して同意すると、onAuthorize、キャンセルすると onCancel がコールバックされるので、それぞれのコールバック関数の中で適宜リダイレクトをする(ここでは勝手にリダイレクトされない)
  6. 「/shop/execute-payment?paymentId=xxx&token=yyy&PayerID=zzz」という URL でリクエストされるので、サーバ側で paymentId, PayerID を取得して Payment を execute して、決済完了画面を表示させる


実装を見ながら、処理の流れをチェックすると分かりやすいかと思います。


 

動作確認

最後に、実際に検証環境で画面を動かしながら、動作を確認してみます。


ブラウザで「http://localhost:8000/shop/cart」にアクセスし、PayPal のチェックアウトボタンをクリックします。
f:id:akiyoko:20161221135159p:plain

In-Context Window(モーダルウィンドウみたいなもの)が起動して、PayPal サイト(サンドボックス)のログイン画面が表示されます。
PayPal の買い手アカウント情報を入力してログインし、
f:id:akiyoko:20161221145000p:plain

「同意して続行」をクリックします。
f:id:akiyoko:20161221145025p:plain

onAuthorize がコールバックされ、return_url の「/shop/execute-payment」に redirect() され、shop/complete.html に payment オブジェクトの中身がダンプされました。
f:id:akiyoko:20161221145046p:plain



 

まとめ

Django で PayPal 決済を検討しているのであれば、今回紹介したように、フロントは「In-Context Window の checkout.js V 4.0.0」、バックエンドは「PayPal REST API 」という現時点での最新スタイルがオススメです。

ドキュメントや記事がまだ少ないのが欠点ですが、PayPal の日本チームもそこには今後力を入れていくそうなので期待しましょう。

PayPal の決済フローもドキュメントも、現在進行形でどんどん進化していますよ!!


最後にひと言。

PayPal REST API で In-Context Window 決済はいいぞ!


ご拝読ありがとうございました。「Django と Paypal と私」でした。




明日は、luizs81 さんの 23日目の記事です。よろしくお願いします。


オススメ Django 本

Django のベストプラクティス本です。
全編英語ですが絶対オススメです。

Two Scoops of Django: Best Practices for Django 1.8

Two Scoops of Django: Best Practices for Django 1.8


記事を書きましたので、是非ご参考に。


<過去記事>
akiyoko.hatenablog.jp

*1:決済代行業者によって呼び方も異なる。例えば、GMOペイメントゲートウェイ では「プロトコルタイプ」「モジュールタイプ」、ゼウス では「トークン(JavaScript)型」「データ伝送(API)型」などと呼ばれるが、いずれも画面遷移を伴わない直接決済方式である。

*2:https://developer.paypal.com/docs/classic/api/#website-payments-pro

*3:2017年4月末でのサービス終了が宣言された「WebPay」では、海外向けでの販売のみを前提とした利用はできないと規定されていました。 https://webpay.jp/faq#constraints

*4:常に最新版のものを利用するために、PayPal の CDN を利用することが推奨されています。

*5:V 3.5.0 から V 4.0.0 への移行方法については、「Upgrade checkout.js to V4.0.0 - PayPal Developer」を参照

*6:payment_id を JSON形式で返すだけで、後は checkout.js が勝手にリダイレクトしてくれるようになりました。

*7:利用できる API の種類については「REST API reference - PayPal Developer」を参照

*8:詳しい仕組みについては「How PayPal uses OAuth 2.0 - PayPal Developer」を参照

*9:https://www.paypal-knowledge.com/infocenter/index?page=content&id=FAQ1949 が新しくて参考になりそうです。

*10:本番では「Payment Experience API」を組み合わせて使うこともありますが今回は利用しません。

「Python 3 エンジニア認定基礎試験」に合格しました!

この投稿は 「Python Advent Calendar 2016 - Qiita」 の 21日目の記事です。


このたび、「Python 3 エンジニア認定基礎試験」に合格することができましたー! パチパチパチ〜!!


f:id:akiyoko:20161221021155p:plain:w450


と、Python Advent Calendar で報告しようと思ったのですが、残念ながら12月21日現在、まだ合格通知が届いていません。試験(ベータ版)を受けたのが 11月7日で、結果通知が約一ヶ月後ということだったのですが、まだ何の音沙汰もありません。。



仕方ないのでここはひとつ、合格したという体で書かせていただきます(!!)。


 

はじめに

まずはじめに「Python 3 エンジニア認定基礎試験」は、「一般社団法人Pythonエンジニア育成推進協会」主催・監修により 2017年春から本試験開始を予定している Pythonエンジニアのための認定試験で、Python 3系の基礎文法が問われます。


Python 3 エンジニア認定基礎試験
概要:文法基礎を問う試験
受験料金:1万円(外税) 学割5千円(外税)
問題数:40問(すべて選択問題)
合格ライン:正答率70%


Python試験 | 一般社団法人Pythonエンジニア育成推進協会」より



Pythonの普及推進、Pythonicの理解促進:Pythonエンジニア育成推進協会 発起人会が発足、2017年春にPython試験を実施予定 - @IT」の記事に「これから Python を習得し始める人などを対象」と書かれているように、どちらかと言うと Python 初級者のための試験 であると考えられます。



認定教材は今のところこの一冊のみで、「基礎文法部分から出題」されます。

私も試験直前にひと通り目を通しましたが、Python 初学者にベストな参考書かどうかは議論の余地はあるかもしれませんが、内容は非常によくまとまっていると感じました。




一方、「一般社団法人Pythonエンジニア育成推進協会」は、その設立背景を

ビッグデータ、機械学習、Webサービス開発など、幅広い分野で注目が集まっているPythonは米国ではすでに求人件数がJavaに次ぐ2位となり4万件を超えています。日本でも2年間で求人数が2倍になっており、近い将来、人材不足になる可能性が高く、エンジニア育成を推進する業界団体を設立するに至りました。

と説明しており、ビッグデータ分析や機械学習などで今後ますます注目株となる Pythonエンジニアの認定資格は、データサイエンティストを求める企業やスキルを証明したい個人にとってそれなりにニーズがありそうです。


かくいう私も、早速履歴書に書いてアピールしていく予定です!!





なお、「基礎試験」以外に、データ分析に特化した上位の「Python 3 エンジニア認定データ解析試験」も開催される予定です。

Python 3 エンジニア認定データ解析試験
概要:Pythonを使ったデータ分析の基礎や方法を問う試験
受験料金:1万円(外税) 学割5千円(外税)
問題数:40問(すべて選択問題)
合格ライン:正答率70%



「Python 3 エンジニア認定データ解析試験」の認定教材は、次の一冊となっています。




 

感想

私はその昔、主に Java やら PHP を嗜んでいたサーバサイドエンジニアですが、5年ほど前から業務で Python を使うようになって以来ずっと Python(Python 2!)を使い続けています。なので現在、Python歴 5年(うち Django歴 3年)といったところです。

今回受験したのはいわゆる「ベータ試験」と言われる本番前のプレ試験なのですが、合格すれば本番試験と同等の資格が得られるとのことで、早速一般向けの第一回ベータ試験に参加してきたのでした。 *1

pythonic-exam.connpass.com



今回の試験に向けて、直前に参考書をひと通り読んでいたのですが、実際の試験は意外と難しかったです。参考書を読まなければ間違っていた問題もいくつかあったかと思います。 *2 「70%」という合格ラインをクリアするのはそんなに難しくないのですが、満点を取るにはちょっと基礎知識に抜けがあったかな?というのが正直な感想です。


試験が終わって、きちんと理解していなかった部分をすぐに参考書で復習できたのが、今回一番の成果だったように思います。


何はともあれ、一発合格できてよかったです。
これでめでたく私も Pythonic の一員になれたというわけですね。 *3


うぉぉぉぉ!! これが Pythonic ってやつか!
力がみなぎってくるぜぇぇ!

f:id:akiyoko:20161221075723p:plain:w350


というわけで、まだまだ認知度の低い「Python 3 エンジニア認定基礎試験」ですが、もっともっと盛り上がるといいな!!と思いました。




明日は、kaneshin さんの 22日目の記事です。
よろしくお願いします。

*1:本試験は、全国のオデッセイ コミュニケーションズ CBTテストセンターで行われる予定。

*2:どんな問題が出題されたかについては守秘義務があるため詳細を言えないので悪しからず。

*3:Pythonエンジニア育成推進協会の設立目的の一つが「プログラミングフィロソフィー「Pythonic」の普及推進を行う」となっています。

「PayPal API体験ハンズオンセミナー!世界のFintechを味見しませんか!」に参加してきました

主催

IoT ALGYAN(あるじゃん)


IoTの基本要素を網羅する自学自習可能コンテンツ「IoT Kit Hands-on Training Site


会場

Microsoft 品川本社
東京都港区港南 2-16-3 品川グランドセントラルタワー


Twitter

twitter.com




f:id:akiyoko:20161129191245j:plain


 

全体の感想など

先月末、この PayPal のハンズオンイベントに参加しました。


以前に少し PayPal の調査を個人的にしていたので、とても興味深かったです。

<過去記事>
akiyoko.hatenablog.jp


Braintree については、今年の 3月あたりにサンドボックスを Python で一度試してみたのですが、あれから 8ヶ月くらい経っているのにまだサンドボックスなのですね。。 Braintree API は ドキュメント が(英語ですが)一通り揃っていて実装もしやすいので、早く本番利用できるようになって欲しいところです。


今回、ハンズオン形式で Node.js の express アプリを作って Braintree API を試してみたのですが、express アプリ自体も簡単に作れて実装もシンプルなので、ハンズオンとしてピッタリだったかもしれません。


あと、懇親会で登壇者の岡村氏に直接質問もできて非常に有益でした。
貴重な情報もゲットできました。




 

PayPal / Braintree Hands-on

岡村 純一氏(PayPal Pte.Ltd.)


最近は、エンジニアSF漫画で有名とのこと。
【新連載】エンジニアSF漫画「GRAMMERS #1」 プロローグ~予知能力を持った青年 | geechs magazine



  • 昨年 eBay から独立して再上場
  • venmo は個人間送金、US だけ
  • PayPal は Braintree を API の基盤にする方針
    • v.zero(=Braintree's API) を充実させようとしている
  • Classic API はグローバルで最も使われているのでまだ無くならない
  • 「都度決済」(Checkout)、「定期支払」(Recurring Payment)、「従量課金」(Reference Transaction)、「マーケットプレイス用決済」、「送金」(Payout)が可能
    • カッコ内は API の名前
    • 従量課金は別途審査が必要
    • マーケットプレイスは複数受け取りの場合
    • 送金も別途審査が必要
  • 開発者向け日本語サイト も拡充中


 

v.zero Node.js SDK による PayPal 実装

Braintree でクレジットカードを扱うには Braintree アカウントが必要ですが、PayPal を使うだけであれば Braintree のアカウントは必要はないとのことです(PayPal アカウントがあればよい)。

なお、Braintree の本番アカウントはまだ日本では使えないとのこと(現在はサンドボックスアカウントの作成・利用が可能)。


 

1.PayPal のサンドボックスアカウントを作成

Developer サイト にログインし、ダッシュボードを表示します。

f:id:akiyoko:20161216000336p:plain

次に、[Sandbox]>[Accounts]から、パーソナル(買い手)アカウント および ビジネス(売り手)アカウントを作成します。
https://developer.paypal.com/developer/accounts/

f:id:akiyoko:20161212080822p:plain


 

2.v.zero SDK の Credential を作成

続けて、PayPal Developer サイトで[My Apps & Credentials]から「Generate Credential」ボタンをクリックして、ビジネスアカウントに対して Credential(認証情報)を発行します。

f:id:akiyoko:20161216000938p:plain

作成したビジネスアカウントを選択して「Generate Credential」ボタンをクリック。

f:id:akiyoko:20161216001212p:plain

[My Apps & Credentials]の「v.zero SDK」の「Sandbox Account」のリストから、Credential を作成したアカウントをクリックして、Access Token を確認します。

f:id:akiyoko:20161216001911p:plain


 

3.Node.js, npm をインストール

Homebrew を使って Mac に nodebrew をインストールし、Node.js および npm の最新版をインストールをします。


<過去記事>
akiyoko.hatenablog.jp



 

4.express アプリを作成してサーバを起動

express アプリを作成してサーバを起動します。

$ cd ~/dev

$ npm install express-generator -g
$ express myapp
$ cd myapp
$ npm install
$ npm start

(参考)express実践入門 · GitHub




Braintree Node.js SDK をインストールします。

$ npm install braintree
myapp@0.0.0 /Users/akiyoko/dev/myapp
└─┬ braintree@1.43.0
  ├── dateformat@1.0.1-1.2.3
  ├─┬ readable-stream@1.1.10
  │ ├── core-util-is@1.0.2
  │ ├── debuglog@0.0.2
  │ └── string_decoder@0.10.31
  ├── semver@5.1.0
  ├─┬ source-map-support@0.2.9
  │ └── source-map@0.1.32
  ├── underscore@1.8.3
  └─┬ xml2js@0.1.13
    └── sax@1.2.1

(参考)Set Up Your Server | Node.js - Braintree Developer Documentation



あとは、Set Up Your Server - PayPal Developer に沿って実装していきます。

ちなみに岡村氏の正解コードはこちら。
GitHub - benzookapi/VZeroNodeDemo: Braintree v.zero hands-on code



修正したのは以下の 2ファイルです。


routes/index.js

var express = require('express');
var router = express.Router();
var braintree = require('braintree')

/* GET home page. */
router.get('/', function(req, res, next) {
  var gateway = braintree.connect({
    accessToken: 'access_token$sandbox$xxxxxxxx$xxxxxxxxxxxxxxxxxxxxxxxx'
  });
  gateway.clientToken.generate({}, function (err, response) {
    res.render('index', { title: 'Express', clientToken: response.clientToken });
  });
});

router.post('/checkout', function (req, res) {
  var gateway = braintree.connect({
    accessToken: 'access_token$sandbox$xxxxxxxx$xxxxxxxxxxxxxxxxxxxxxxxx'
  });
  var saleRequest = {
    amount: req.body.amount,
    merchantAccountId: 'JPY',
    paymentMethodNonce: req.body.payment_method_nonce,
    options: {
      submitForSettlement: true
    }
  };
  gateway.transaction.sale(saleRequest, function (err, result) {
    if (err) {
      res.send('<h1>Error: ' + err + '</h1>');
    } else if (result.success) {
      res.send('<h1>Success! Transaction ID: ' + result.transaction.id + '</h1>');
    } else {
      res.send('<h1>Error: ' + result.message + '</h1>');
    }
  });
});

module.exports = router;


views/index.jade

div#paypal.
  <script src="https://js.braintreegateway.com/web/3.5.0/js/client.min.js"></script>
  <script src="https://js.braintreegateway.com/web/3.5.0/js/paypal.min.js"></script>

  <script src="https://www.paypalobjects.com/api/button.js?"
     data-merchant="braintree"
     data-id="paypal-button"
     data-button="checkout"
     data-color="gold"
     data-size="medium"
     data-shape="pill"
     data-button_type="submit"
     data-button_disabled="false"
     data-locale="ja_JP"
  ></script>


  <script>
  var paypalButton = document.getElementById('paypal-button');
  // Create a Client component
  braintree.client.create({
    authorization: '#{clientToken}'
  }, function (clientErr, clientInstance) {
    // Create PayPal component
    braintree.paypal.create({
      client: clientInstance
    }, function (err, paypalInstance) {
      paypalButton.addEventListener('click', function () {
      // Tokenize here!
      paypalInstance.tokenize({
        flow: 'checkout', // Required
        amount: 1000, // Required
        currency: 'JPY', // Required
        locale: 'ja_JP'
        }, function (err, tokenizationPayload) {
          // Tokenization complete
          // Send tokenizationPayload.nonce to server
          var form = document.createElement('form');
          document.body.appendChild(form);
          var inputNonce = document.createElement('input');
          inputNonce.setAttribute('type', 'hidden');
          inputNonce.setAttribute('name', 'payment_method_nonce');
          inputNonce.setAttribute('value', tokenizationPayload.nonce);
          form.appendChild(inputNonce);
          var inputAmount = document.createElement('input');
          inputAmount.setAttribute('type', 'hidden');
          inputAmount.setAttribute('name', 'amount');
          inputAmount.setAttribute('value', '1000');
          form.appendChild(inputAmount);

          form.setAttribute('action', '/checkout');
          form.setAttribute('method', 'post');
          form.submit();
        });
      });
    });
  });
  </script>

ここで、「Access Token」は知られてはいけないもの、「Client Token」はどうしても見えてしまうが見せても大丈夫なもの、「Nonce」はライブラリが Client Token を使って生成するワンタイムトークンみたいなもの、ということでした。(スライド p.28)


 
また、form を使っている部分は、jQuery を使ったりして今風にしてもらって構わないとのことです。


5.試してみる

ブラウザで http://localhost:3000/ にアクセスします。

f:id:akiyoko:20161216005405p:plain

「PayPal で支払う」ボタンをクリックすると、In-Context Window が立ち上がります。
f:id:akiyoko:20161216072235p:plain:w400

サンドボックスのパーソナルアカウント でログインして、「同意して続行」をクリックします。
f:id:akiyoko:20161216072249p:plain:w400

「/checkout」に post されて決済が実行され、画面に結果が表示されます。
f:id:akiyoko:20161216005435p:plain



最後に、決済が完了したかどうか、PayPal サイトのマイアカウントで確認をおこないます。


https://www.sandbox.paypal.com/
にアクセスし、サンドボックスのビジネスアカウント でログインします。

f:id:akiyoko:20161216072441p:plain
f:id:akiyoko:20161216072519p:plain

(仮想の)1,000円が入金されていることが確認できました。
f:id:akiyoko:20161216072538p:plain





あとは、本番で使いたい場合には、

  • Access Token を入れ替えるだけ
  • 実際にお金を引き落としするには、本人確認が必要

ということでした。

しかし残念ながら、Braintree v.zero の本番アカウントは日本ではまだ作成することができません。PayPal としても頑張っているけど、体制とサポートの問題で遅れているらしいです。。

いいニュースとしては来年、PayPal に大幅な機能改善があるそうです。ヒントとしては、US で使えていたものが順次入るとのこと。何だろうなぁ。。気になります。



オススメ本

PayPal 創業者のピーター・ティール(Peter Andreas Thiel)の本。
今年の前半に読みました。

ゼロ・トゥ・ワン―君はゼロから何を生み出せるか

ゼロ・トゥ・ワン―君はゼロから何を生み出せるか

「EducationTech Talks Tokyo #1」に参加してきました

会場

NHN テコラス株式会社
東京都新宿区新宿6-27-30 新宿イーストサイドスクエア EAST 13階


Twitter

togetter.com



 

全体の感想など

今回は EdTech 系のイベントでしたが、教育関係の勉強会は都内でも結構珍しいです。見かけたらなるべく参加するようにしていますが、それでも数ヶ月か半年に一回あるかないかでしょうか。。 特に今回のように、「足が長い」と言われている教育サービスの中でも急成長を遂げている三社のベンチャー企業の CTO が登壇してくれる機会もなかなかレアではなかったでしょうか。


全体的なテーマは「スケール」についてでした。突発的なスパイクや成長に合わせたインフラや技術的な対策など、エンジニアも少ない中いろいろと苦労されているようでした。


そういった教育関連サービスならではの事情やノウハウも非常に有益でしたが、個人的に技術的興味が湧いたのは、アオイゼミ の動画ストリーミング。ライブ配信は HLS / WebRTC とのことでしたが、遅延は 30秒くらいでしょうか。どのように遅延を少なくしているのか、いろいろとノウハウがありそうです。


あと、「PHPer」は「ぺちぱー」って読むんですね。初めて聞きました。




manabo CTO 山下氏


  • mana.bo アプリ
  • 高単価のサービスなのでスケールの規模感は小さい?
    • 1時間プランで 3,500円/月
  • 講師数7万人の株式会社トモノカイと提携、Z会グループと資本業務提携(有料ユーザの送客?)
  • スパイクが季節要因とキャンペーン要因
    • 可能な限りクラウドサービスを利用
  • イケてるサービスは TechCrunch(英語版)で発掘
    • CrunchBase(Wikipediaライクなスタートアップデータベース)
    • StackShare(サービスやツールがどのような技術で開発されているかをチェックできる)
    • デモデイ(Demo Day)
  • データ分析は、re:dashFirebase Analytics
  • 全エンジニアにデプロイ権限!


 

スタディプラス CTO 斉藤氏

  • 「学ぶ喜びをすべての人へ」
  • 従業員数 22名(エンジニア 9名、うちサーバサイド 3名?)
  • Studyplus(BtoB)
    • 勉強記録を可視化
    • 2016年9月現在で会員200万人以上(受験する高校3年生の3人に1人が利用)
    • 日本e-Learning大賞、2016 ベスト自己改善アプリ
  • Studyplus for School(BtoC)
    • 勉強状況をダッシュボードで見れるように
  • AWS導入事例:スタディプラス株式会社
    • 小難しい?属人化? ⇒ 誰でも構築できるシンプルな設計に!
  • サーバ側は Ruby on Rails
    • 特に、FatModel 問題、秘伝のタレ問題、クエリチューニングしずらい
  • RoR meets DDD でレイヤー分離
    • 責務がクリアになってコードの見通しがよくなった
    • ファイル数が増える。。



 

動画ストリーミングサービスのスケーリング事例

アオイゼミ CTO 青木氏


  • 中高生向けオンライン学習塾
    • リアルタイムの動画を観ながら、双方向のコミュニケーションが可能
    • 5,000円/月?
  • ライブストリーミング
    • オフィスにオンプレのサーバがある
    • (EC2で?)エンコードして CDN(CloudFront & Akamai の二段構え)で配信
    • CloudFormation Template で一式構築可能
  • ライブストリーミングが始まると急なバースト
    • APIアクセスの遅延対応として、Amazon Aurora 導入!
      • Aurora の Reader Endpoint を利用。オススメ
  • ログ保存は Kinesis を使ってストリームデータを保存
  • Firebase Analytics + BigQuery でデータ分析
    • re:dash も使ってる?
  • エンジニア5名?

今年の流行語大賞は「JPAP」(Jupyter / Python / Anaconda / Pyenv)〜 ゼロからはじめる Jupyter Notebook 〜

この投稿は 「jupyter notebook Advent Calendar 2016 - Qiita」 の 6日目の記事です。



今年の流行語大賞が 「JPAP(Jupyter / Python / Anaconda / Pyenv) に決まりましたね!



あれ? 違うの!?



といった冗談はさておき、ちょっとした統計解析をしようとしたときに、Jupyter Notebook(ジュピター ノートブック)+ Pandas(パンダス)を試してみたら、すごくいい感じだったのでご紹介。



Jupyter Notebook はブラウザ上でデータ分析関連の Python コードを実行したり実行結果を保存したりすることができる非常に便利なツールなのですが、Jupyter Notebook の他にも Python, NumPy, Pandas, Matplotlib 等々の環境を準備しておく必要があります。それをゼロから用意するのは大変だなぁと思うかもしれませんが、「Anaconda」というデータサイエンティスト向けのプラットフォームをインストールするだけで、Python 本体を含めたデータ分析関係の Python パッケージ群をいい感じに環境設定することができます。 *1

また Anaconda は、「Pyenv」という Python のバージョン管理ツールを使うことで、Mac や Linux などの環境に適当な Anaconda を簡単にインストールをすることができます。

f:id:akiyoko:20161206000608p:plain



ということで今回は、「JPAP(Jupyter / Python / Anaconda / Pyenv)」(Jupyter はじめるなら、データ分析関連の Python パッケージが全部入りの Anaconda を Pyenv でインストール!)と題して、Jupyter Notebook のセットアップ、および使ってみた感想を書いてみます。




まずは、Jupyter Notebook や Pandas の概要から。


 

Jupyter Notebook

Jupyter Notebook は、ブラウザ上でデータ分析関連の Python コードを実行したり実行結果を保存したりすることができる非常に便利なツールです。サーバを裏で起動しておくことで、ブラウザ上のいろいろな操作を受け付けることができます。


(参考)
A gallery of interesting IPython Notebooks · ipython/ipython Wiki · GitHub


ちなみに、Jupyter Notebook は少し前まで「IPython Notebook」と呼ばれていました。公式としては、2015年7月末から名前が変更になったということでしょうかね。

Jupyter Notebook was born (officially on July 30)


Jupyter Notebook - LIMSWiki



以下、Jupyter Notebook を使ってみた感想です。

Jupyter Notebook の良いところ
  • Anaconda さえあれば他にセットアップ不要
  • 画面でポチポチしてデバッグできる *2
  • 実行結果を含めて ipynb ファイルに保存できる *3
  • ipynb ファイルを GitHub にアップすれば誰でも実行結果を見れる

f:id:akiyoko:20161105114420p:plain
(例:bgstat/analytics_by_experience.ipynb at master · akiyoko/bgstat · GitHub

少し足りないところ
  • 変数の中身が全て出力されないところ(長いと「...」と省略されてしまう)
  • GitHub の画面でポチポチできない


 

Pandas

Pandas は、非常に便利に使える Python のデータ分析ライブラリです。DataFrame や read_excel、read_csv など、ありがたい機能が盛り沢山です。

pandas is an open source, BSD-licensed library providing high-performance, easy-to-use data structures and data analysis tools for the Python programming language.


http://pandas.pydata.org/

 

参考本

Pandas をガッツリ勉強するならこちら。



Pandas を手っ取り早く習得するなら、こちらの記事が一番かもしれません。
openbook4.me





 

JPAP(Jupyter / Python / Anaconda / Pyenv)の手順

Anaconda を Pyenv でインストールします。

以下、Mac 上でのインストール手順になります。


<過去記事>
akiyoko.hatenablog.jp

 

1.Pyenv のインストール

Homebrew で pyenv をインストールします。

$ brew install pyenv

$ pyenv --version
pyenv 20160303

$ cat << EOF >> ~/.bash_profile
export PYENV_ROOT=\${HOME}/.pyenv
export PATH=\${PYENV_ROOT}/bin:\$PATH
eval "\$(pyenv init -)"
EOF

$ source ~/.bash_profile

 

2.Anaconda のインストール

Anaconda 2 は Python 2.7系、Anaconda 3 は Python 3.5系のパッケージを同梱しています。 *4


私は(業務で使っているのが)Python 2系なので、最新の Anaconda 2 をインストールすることにしました。

ちなみに anaconda2-2.4.0 以降、 Anaconda 2 系のプレフィックスが「anaconda2-*」に統一されたようです。 *5

$ pyenv install -l
Available versions:
  2.1.3
  ・
  ・
  anaconda-2.4.0
  anaconda2-2.4.0
  anaconda2-2.4.1
  anaconda2-2.5.0
  anaconda3-2.0.0
  ・
  ・


Anaconda 2系で(現時点で)最新の「anaconda2-2.5.0」をインストールしました。

$ pyenv install anaconda2-2.5.0
$ pyenv global anaconda2-2.5.0
$ pyenv rehash

### バージョン確認
$ pyenv versions
  system
  anaconda-2.4.0
* anaconda2-2.5.0 (set by /Users/akiyoko/.pyenv/version)

$ python --version
Python 2.7.11 :: Anaconda 2.5.0 (x86_64)

$ pip list | grep -E "jupyter|notebook"
jupyter (1.0.0)
jupyter-client (4.1.1)
jupyter-console (4.1.0)
jupyter-core (4.0.6)
notebook (4.1.0)


ここで一瞬ハマってしまいました。。
conda update を実行して最新化しようとしたら、Python が 2.7.12 にアップグレードされてしまい、Jupyter Notebook 利用時に新規ファイルを作成しようとすると、「Python[conda root]」と「Python[default]」の 2バージョンが表示されてしまいました。

python 2.7.12 installed from anaconda and the default one came with Mac is 2.7.10.


http://stackoverflow.com/a/40051941

が原因かもしれません。
結局、conda update でアップデートするのはやめました。



 

3.Jupyter Notebook の起動方法

コマンドラインから以下を実行すれば OK です。

$ jupyter notebook


サーバ起動後にブラウザで「http://localhost:8888/」にアクセスすると、サーバを起動したディレクトリが階層表示されます。

f:id:akiyoko:20161105104659p:plain

あとはポチポチやるだけ。

f:id:akiyoko:20161105104738p:plain



 

実戦

ちょっとしたデータ解析をしてみました。

こんな感じで、D列から右方向にデータ列が並んでいる Excel シートがあるとします。

f:id:akiyoko:20161104235820p:plain


まずは、Pandas の read_excel() を使って Excel を読み込みます。
少し前処理をした後、DataFrame を加工して、偏差値などの代表値を加えたり、欠損値を除外したり、データをソートしたり、各代表値を計算したりします。

最後に、経験年数ごとにグループ化してグラフを描きました。

github.com




import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
%matplotlib inline

# header 無しで Excel を読み込む
excel_df = pd.read_excel('5th_backgammon_proficiency_test_score.xls', sheetname='Result', header=None)

「%matplotlib inline」は実行結果の画像をインラインで埋め込むためのおまじない。

# D列以降のデータのみを使用
df = excel_df.copy()
df = df.ix[:, 3:]

pandas.DataFrame.ix を使うと、DataFrame のスライスが簡単にできます。

# index と column を反転
df = df.T

今回試した Excel データが右方向にデータ列が並んでいるため、少しトリッキーなことをしました。

# column名を書き換え
columns = {
    0: 'Date',
    3: 'Nation',
    4: 'Sex',
    7: 'Experience',
    61: 'Total Score',
    64: 'Total Error',
    118: 'Early Game Score',
    119: 'Middle Game Score',
    120: 'End Game Score',
    121: 'Other Score',
    132: 'Early Game Error',
    133: 'Middle Game Error',
    134: 'End Game Error',
    135: 'Other Error',
}
df.rename(columns=columns, inplace=True)
# column の絞り込み
df = df[columns.values()]
# Date列が NaN のデータは除外
df = df.dropna(subset=['Date'])

pandas.DataFrame.dropna() で欠損データを除外することができます。

def get_deviation(s):
    """
    s: Series
    """
    return (s - s.mean()) / s.std(ddof=False) * 10 + 50

# 偏差値列を追加
df['Deviation of Total Score'] = get_deviation(df['Total Score'])
df['Deviation of Early Game Score'] = get_deviation(df['Early Game Score'])
df['Deviation of Middle Game Score'] = get_deviation(df['Middle Game Score'])
df['Deviation of End Game Score'] = get_deviation(df['End Game Score'])
df['Deviation of Other Score'] = get_deviation(df['Other Score'])
df['Deviation of Total Error'] = 100 - get_deviation(df['Total Error'])
df['Deviation of Early Game Error'] = 100 - get_deviation(df['Early Game Error'])
df['Deviation of Middle Game Error'] = 100 - get_deviation(df['Middle Game Error'])
df['Deviation of End Game Error'] = 100 - get_deviation(df['End Game Error'])
df['Deviation of Other Error'] = 100 - get_deviation(df['Other Error'])
# Total Score列の降順、Total Error列の昇順でソート
df = df.sort_values(by=['Total Score', 'Total Error'], ascending=[False, True])
df

pandas.DataFrame.sort_values() でソートができます。

# 各代表値を求める
sliced = df[['Total Score', 'Early Game Score', 'Middle Game Score', 'End Game Score', 'Other Score', 'Total Error', 'Early Game Error', 'Middle Game Error', 'End Game Error', 'Other Error']]
desc = pd.DataFrame(
    [sliced.max(), sliced.min(), sliced.mean(), sliced.median(), sliced.var(ddof=False), sliced.std(ddof=False)],
    index=['max', 'min', 'mean', 'median', 'var', 'std']
)
desc
# その他の統計量
df.describe()
# 前処理ここまで

# 経験年数ごとにグループ化
grouped = df.groupby('Experience')
# 出力用のデータフレームを作成
experience_df = pd.DataFrame()
experience_df['Size'] = grouped.size()
experience_df['Total Score'] = grouped['Total Score'].apply(np.mean)
experience_df['Early Game Score'] = grouped['Early Game Score'].apply(np.mean)
experience_df['Middle Game Score'] = grouped['Middle Game Score'].apply(np.mean)
experience_df['End Game Score'] = grouped['End Game Score'].apply(np.mean)
experience_df['Other Score'] = grouped['Other Score'].apply(np.mean)
experience_df['Total Error'] = grouped['Total Error'].apply(np.mean)
experience_df['Early Game Error'] = grouped['Early Game Error'].apply(np.mean)
experience_df['Middle Game Error'] = grouped['Middle Game Error'].apply(np.mean)
experience_df['End Game Error'] = grouped['End Game Error'].apply(np.mean)
experience_df['Other Error'] = grouped['Other Error'].apply(np.mean)
# indexの並び替え
experience_df = experience_df.reindex(['under 1 year', '1-2 years', '2-3 years', '3-4 years', '4-5 years', '5-10 years', 'over 10 years'])
experience_df

pandas.DataFrame.reindex() で index の並び替えができます。

# スコアのグラフ描画
experience_df.plot.bar(
    y=['Early Game Score', 'Middle Game Score', 'End Game Score', 'Other Score'],
    figsize=(10, 8),
    stacked=True,
    cmap='Blues')
plt.title(u'Score by Experience', size=16)
plt.plot()

pandas.DataFrame.plot.bar で棒グラフを描画することができます。

# エラーのグラフ描画
experience_df.plot.bar(
    y=['Early Game Error', 'Middle Game Error', 'End Game Error', 'Other Error'],
    figsize=(10, 8),
    stacked=True,
    cmap='Blues')
plt.title(u'Error by Experience', size=16)
plt.legend(loc='upper right')
plt.plot()


 

まとめ

冒頭でも書きましたが、ちょっとした統計解析をしようとしたときに Jupyter Notebook + Pandas を試してみたら、すごくいい感じでした。

Jupyter Notebook を使うための環境は、Anaconda をインストールすればゼロからでも一発で準備することができるので、非常に楽チンです。

Anaconda には、Python 本体や Jupyter Notebook のほか、Numpy や Pandas、Matplotlib までデータ分析に必要なライブラリが一通り揃っているので、あとはブラウザを使ってトライ&エラーでデータ分析を進めていくことができます。気軽にデータ分析を試すことができ、ipynb ファイルで実行結果を共有したりすることもできるので、非常に面白いツールだと感じました。



明日は、7of9 さんの 7日目の記事「Jupyter Notebook > NewボタンでPython2が選択できない時の対処方法 / Jupyterの気に入っているところ - Qiita」です。
よろしくお願いします。



 

参考本

「IPython」とタイトルに付いていますが、比較的新しい本です。



「IPython Notebook」は 2015年7月以降に「Jupyter Notebook」に改名されましたが、「IPython」自体は消滅したわけではなく、インタラクティブ Python シェルや Jupyter の Python 用のカーネルとして存在しています。

  • A powerful interactive Python shell
  • A Jupyter kernel to work with Python code in Jupyter notebooks and other interactive frontends.


IPython Documentation — IPython 6.2.1 documentation

*1:Anaconda に含まれるパッケージの一覧は、「Anaconda package lists | Anaconda: Documentation」から確認することができます。

*2:jupyter notebook コマンドでサーバを立ち上げる必要があります

*3:出力したグラフ(画像)もインライン表示で含めることができます

*4:Frequently asked questions | Anaconda: Documentation より

*5:AnacondaでPythonの分析環境をまとめてインストール - TASK NOTES より

ベスト・オブ・Django本!

この投稿は 「Django Advent Calendar 2016 - Qiita」 の 5日目の記事です。


「Django」を勉強するときに一番困るのは、やっぱり 「Django本がない」 問題ですよね?


実際、Django 初心者や初級者のエンジニアがいざ本格的に勉強しようと思っても、Django の専門書が圧倒的に少ないという問題が大きな壁として立ちはだかっているのではないでしょうか。


あっても古いとか。

開発のプロが教える標準Django完全解説―Webアプリケーションフレームワーク (デベロッパー・ツール・シリーズ)

開発のプロが教える標準Django完全解説―Webアプリケーションフレームワーク (デベロッパー・ツール・シリーズ)

2008年発売って、Django のバージョンは 1.0 ですよね。
さすがに情報が古すぎます。。


あと、Django に割かれているページ数が少ないとか。

Pythonプロフェッショナルプログラミング第2版

Pythonプロフェッショナルプログラミング第2版

Django について書かれた章がありますが、それでも 20ページ程度しかありません。。


「もっと新しくて、ちゃんと全体を網羅している Django の専門書はないの??」
私も常々、こんな疑問を抱いていました。



書籍の充実した Ruby や PHP と比べて、Python のフルスタック Webアプリケーションフレームワークに関する書籍が少ないという問題が、Django があまり流行ってない大きな原因になっているのでは??というのが、私の率直な印象です。




で、見つけましたー!!


私が自信を持ってオススメできる ベスト・オブ・ Django本が「Two Scoops of Django: Best Practices for Django 1.8」 です!

Two Scoops of Django: Best Practices for Django 1.8

Two Scoops of Django: Best Practices for Django 1.8


残念ながら全編英語の本ですが(*1)、設計のベストプラクティスから細かな Tips まで詳細に分かりやすく解説されている Django の専門書です。


対象者は、初心者というよりも、チュートリアル を読み終えたくらいの初級者から、現場で Django を何年か使っている中級者くらいを想定していると思われます。
専門書と言っても堅苦しさは全く無く、頻繁にアイスクリームショップの話が引き合いに出されたりと、ポップな世界観(!)を醸し出しています。


著者は Daniel Roy GreenfeldAudrey Roy Greenfeld の二人のエンジニアで(*2)、プロジェクトで Django を使い倒している超ベテランです。これまで、Django 1.5, 1.6 向けの Two Scoops of Djangoシリーズ を出版してきた経歴もあるので、本の内容の充実度ぶりは半端ないです。


本のタイトル「Two Scoops of Django(Django の 2つのスクープ)」の由来は、「二人がお届けする Django の最新情報(スクープ)」という意味と、Django をアイスクリームに見立てて「二すくい(スクープ)のアイスクリーム」という意味を掛けているんじゃないかと思われます。




ちなみに、現時点の Django の最新バージョンは 1.10 ですが、この本の対象バージョンは Django 1.8 です。しかしながら、1.8 は LTS(長期サポートバージョン)なので、少なくとも 2018年4月までは本家からセキュリティとバグフィックスが提供されることになるため、現場で使う場合も安心です。

f:id:akiyoko:20161122010842p:plain
Download Django | Django より引用)


著者は、次の「Two Scoops of Django」シリーズはバージョン 2.2 のリリース以降と言っているので、2019年まではこの「Two Scoops of Django: Best Practices for Django 1.8」にお世話になりそうです。なので、この本を買って 2019年まで読み倒すのがよいかと思います。


(2017/9/14 追記)

次の「Two Scoops of Django」シリーズはバージョン 2.2 のリリース以降、と言っていましたが、どういう心境の変化かバージョン 1.11 に対応した最新刊がつい先日発売になりました。バージョン 1.11 を導入する方にとっては朗報ですね!!

Two Scoops of Django 1.11: Best Practices for the Django Web Framework

Two Scoops of Django 1.11: Best Practices for the Django Web Framework


(2018/4/20 追記)

技術書典4」というイベントで、人生初の技術系同人誌「現場で使える 基礎 Django」を頒布することになりました。
動機はここにも書きましたが、「Django の日本語の書籍が少ない」と常々感じていたからです。「無いのなら 自分で書こう Django本」を実践してみたわけです。Django の日本語書籍が無くて困っている方、Django で一度挫折したことのある方に絶対オススメです。
あなたの「ベスト・オブ・Django本」になれますように。

techbookfest.org

akiyoko.hatenablog.jp



 

目次

ざっと内容を紹介しますが、詳細には触れていませんので興味がある内容が一つでもあれば実際に読んでみてもらえればと思います。

著者曰く、どの章から読み始めても問題ないそうです(実際、問題ありません)。


 

第1章:Coding Style(コーディングスタイル)
  • p.1〜
  • PEP8 に準拠すべし
  • OSSプロジェクトは1行79文字、その他のプロジェクトは1行99文字制限に
  • 明示的な相対インポートを使うべし
  • Django コーディングスタイル およびその他のガイドラインについて
第2章:The Optimal Django Environment Setup(最適なDjango環境設定)
  • p.13〜
  • 開発環境のセットアップについて
  • 開発環境と本番環境で別々のデータベースを使っている場合は要注意 *3
  • pip と virtualenv を使おう。virtualenvwrapper もオススメ
  • コードを Git や Mercurial でバージョン管理しよう
  • Vagrant + VirtualBox、Docker などを使って開発環境を仮想化する
第3章:How To Lay Out Django Projects(Djangoプロジェクトをどのようにレイアウトすべきか)
  • p.21〜
  • プロジェクトレイアウトのベストプラクティス(リポジトリルート/プロジェクトルート/コンフィグレーションルート)
  • virtualenv の格納場所はプロジェクト外に *4
  • startproject の代わりに、cookiecutter-django コマンドを使って究極の Djangoテンプレートプロジェクトを作成しよう
第4章:Fundamentals of Django App Design(Djangoアプリケーション設計の基本)
  • p.33〜
  • Django app(アプリケーション)を理解しよう。 *5
  • それぞれのアプリケーションは一つのタスクに集中すべき(一文で説明できるようにできるだけ小さく設計すべき)
  • アプリケーション名はメインとなるモデルの複数形にすべし *6
  • アプリケーションに含めるべきモジュール群(とネーミング)
第5章:Settings and Requirements Files(SettingsモジュールとRequirementsファイル)
  • p.41〜
  • 「SECRET_KEY」などのシークレットキー(*7)を除く全ての設定はバージョン管理下に置くべき
  • シークレットキーの設定には環境変数を使う(付録Eを参照)
  • アンチ local_settings.py パターンとして、各環境に合わせた Settingsモジュール(base.py/local.py/staging.py/test.py/production.py)を用意
  • Settingsモジュール内のファイルパスの書き方について
第6章:Model Best Practices(モデルのベストプラクティス)
  • p.63〜
  • django-model-utils の TimeStampedModel や django-extensions はどのプロジェクトでも便利に使える
  • モデルが5個までに収まるようにアプリケーションの大きさを設計する
  • 3種類のモデルの継承方法(abstract base classes, multi-table inheritance, proxy models)のうちどれを使うべきか *8, *9
  • データベースのマイグレーション方法について
  • テーブル設計で非正規化をする前にキャッシングを検討しよう
  • null=True, blank=True の使いどころ
  • models.field.GenericForeignKey はなるべく使わないようにしよう
  • モデルの _meta API について
  • モデルマネージャについて
  • ファットモデル志向(データ処理やビジネスロジックはモデルに集約)
  • モデルがファットになりすぎないように、Mixin やステートレスなヘルパー関数に分離しよう
第7章:Queries and the Database Layer(クエリとデータベース層)
  • p.81〜
  • ビューで単一のオブジェクトを取得する場合は get() の代わりに get_object_or_404() を使う *10
  • ObjectDoesNotExist はどのモデルでも共通で使える
  • get() で複数のオブジェクトが抽出されてしまう場合は MultipleObjectsReturned で判定
  • 遅延評価を理解しよう。長いクエリは読みやすいように分割しよう
  • Django 1.8 から UPPER, LOWER, SUBSTR などの関数が使えるように *11
  • 生の SQL は出来る限り書かないようにする *12
  • デフォルトのトランザクションはクエリごと *13
第8章:Function- and Class-Based Views(関数ベースおよびクラスベースのビュー)
  • p.95〜
  • 関数ベース、クラスベースのビューのどちらを使うべきか? *14
  • URLConf にロジックを書かない(ビューやモデルを使い回せるように分離)
  • アプリケーションレベルの namespace を使うと便利
  • URLConf のビューへの参照は文字列で書かない
第9章:Best Practices for Function-Based Views(関数ベースビューのベストプラクティス)
  • p.109〜
  • 再利用できない、クラスベースビューのように継承が使えない、のが難点
  • HttpRequest を引数にしたヘルパー関数やデコレータを使うことで、ビューをシンプルにできる
第10章:Best Practices for Class-Based Views(クラスベースビューのベストプラクティス)
  • p.117〜
  • 継承構造がややこしいのが難点
  • Mixin を利用しよう *15
  • 代表的な GCBV(generic class-based view)の紹介 *16
  • CBV + ModelForm, CBV + Form の例
  • django-braces の Mixin は GCBV の足りない部分を補完してくれるので便利
第11章:Form Fundamentals(フォームの基本)
  • p.137〜
  • ModelForm を使おう
  • 外部から入力された値はフォームでバリデーションすべし
  • データの更新を伴うリクエストには CSRF対策を施そう
  • Ajaxリクエストには、CSRF対策を施すか、HTTPヘッダに「X-CSRFToken」を設定する
  • django-braces の ModelForm Mixin は便利 *17
  • form.is_valid() の仕組み
第12章:Common Patterns for Forms(フォームのよくあるパターン)
  • p.149〜
  • フォーム関連の便利パッケージについて *18
  • clean() には複数フィールドにまたがる相関チェックを書く
  • clean(), clean_xxx() には、データベースに永続化されたデータを使ったバリデーションを書いても OK
  • Model とほぼ同じフィールドを Form に加えるときは、コピペせずに ModelForm の __init__ で self.fields を参照すればよい
第13章:Templates: Best Practices(テンプレート:ベストプラクティス)
  • p.167〜
  • テンプレートファイルは templates/ にひとまとめにしよう
  • 二層構造にする。それぞれのアプリケーションごとにベースのレイアウトを変えたい場合は(base.html を継承した)三層構造にする
  • "Flat is better than nested"
  • テンプレートレイヤでは必要以上にクエリや処理が実行されないように注意
  • テンプレートは「データを表示する」だけにする
  • ループ内で不必要なクエリが発行されないように select_related を使う *19
  • テンプレートの継承を理解しよう
  • テンプレートファイルやブロックなどの名前には「-」ではなく「_」を使う
  • 他のテンプレートファイルから include されるテンプレートファイル名は「_」で始める
  • CBV を利用した場合、「object_list」という暗黙の変数名が使える
  • テンプレート内では URL のパスはハードコードせずに url タグを使う
  • テンプレートのデバッグには TEMPLATES の「string_if_invalid」オプションの設定を使うと便利
  • 404.html と 500.html のエラーページは最低限用意しておこう
第14章:Template Tags and Filters(テンプレートタグとフィルタ)
  • p.191〜
  • フィルタは単なる関数(ただし引数は二つまでという制限あり)
  • テンプレートタグはデバッグしづらい
  • カスタムテンプレートタグのモジュール名は <app name>_tags.py とする
第15章:Django Templates and Jinja2(DjangoテンプレートとJinja2)
  • p.197〜
  • Django 1.8 から Django template language (DTL) 以外に Jinja2 をサポート
  • DTL と Jinja2 のどちらを使うべきか *20
  • ディレクトリごとに適用するテンプレートエンジンを替えることも可能
第16章:Building REST APIs(REST APIの構築)
  • p.207〜
  • 通常のアプリケーションの所々にAPI用のコードを入れるよりも、全ての serializer, renderer, view が入った REST API に特化したアプリケーションを1つ作ってしまうのがベター
    • アプリケーション名は「apiv4」などバージョンを意識した名前にすべし
    • 小さくてシンプルなプロジェクトでは、REST API のビューは単一の views.py または viewsets.py に集約できる
    • 多数の REST API が含まれる大きなプロジェクトでは、viewset パッケージを作ってその中に記述する
    • ビジネスロジックはできるだけAPIビューから分離すべし
  • 別アプローチとして、URLconf を core/api.py や core/apiv1.py に記述して、ルートURLconf から include する方法も
  • バージョンアップを考慮して、URLパターンは /api/v1/users, /api/v2/users などとするとよい
  • レートリミットは必ず入れるべし
    • Nginx や Apache でも導入することが可能(パフォーマンスもよいが、Pythonコード外で管理することになる)
第17章:Consuming REST APIs(REST APIの使用)
  • p.225〜
  • バックエンドに Django、フロントエンドに React.js、Angular.js、Backbone.js などのモダンな JSフレームワークを利用する
  • CSRF トークンの送信方法に注意
第18章:Tradeoffs of Replacing Core Components(コアコンポーネントを入れ替える場合のトレードオフ)
  • p.237〜
  • よっぽどのことがない限り Django のコアコンポーネントを入れ替えるのはやめよう
第19章:Working With the Django Admin(Django管理サイトを動かす)
  • p.243〜
  • Django 管理サイトはエンドユーザ向けのものではなく、管理者向けのもの
  • 一覧表示を見やすくするために、モデルに __str__() メソッドを用意しよう *21
  • 一覧に表示するフィールドは list_display で宣言する
  • 詳細表示をカスタマイズする場合は、ModelAdmin を継承したクラスを用意
  • どうしても管理サイトの見た目をカスタマイズしたい場合は https://github.com/sehmaschine/django-grappelli:titledjango-grappelli などのスキンを使う
第20章:Dealing with the User Model(Userモデルを扱う)
  • p.255〜
  • Django 1.5 以降でユーザクラスを設定で変更できるようになったため、ユーザクラスを取得するには get_user_model() を使う
  • ユーザモデルに対して一対多・一対一・多対多関連のフィールドを作成する場合は、settings.AUTH_USER_MODEL を使う
  • ユーザモデルをカスタマイズするには django-authtools が便利
  • ユーザモデルにカスタムフィールドを追加するための 3つのオプション *22
第21章:Django's Secret Sauce: Third-Party Packages(Djangoの秘伝のソース:サードパーティのパッケージ)
  • p.263〜
  • 本の中で言及したパッケージの一覧については付録Aを参照
  • 55,000 を超える Python パッケージが PyPI で管理されている *23
  • Django Packages は Django関連のプロダクトが集められている比較サイト
  • 依存パッケージはバージョン番号も指定しておこう
  • パッケージの完成度を見極める方法
  • Cookiecutter で Python/Django プロジェクトのテンプレートを作ろう
  • PyPI に登録する方法について
第22章:Testing Stinks and Is a Waste of Money!(疑わしいものをテストしよう!テストはお金の無駄!)
  • p.283〜
  • カバレッジを取るには coverage.py を使うとよい
  • テストモジュールにはファイル名に「test_」のプレフィックスを付けよう
  • 一つの単体テストメソッドでは一つのことをテストするようにしよう
  • request オブジェクトを生成するには RequestFactory を使おう。ただし、Middleware を通さないといけない場合は工夫が必要
  • データのメンテナンスが大変になるので、フィクスチャは使い過ぎないようにしよう *24
  • Mock ライブラリを使ってモックアウトする *25
  • unittest には便利なアサーションメソッドが多数用意されている *26
  • 統合テスト/CI について
  • unittest の代わりの単体テストツールとして、pytest-django, django-nose も便利 *27
第23章:Documentation: Be Obsessed(ドキュメンテーションせずにはいられない)
  • p.301〜
  • ドキュメントは reStructuredText(RST)形式 で書こう *28
  • .rstファイルは Sphinx で各種フォーマットに変換して出力する
  • どんなドキュメントを作るべきか?
第24章:Finding and Reducing Bottlenecks(ボトルネックの発見と解消)
第25章:Asynchronous Task Queues(非同期タスクキュー)
  • p.319〜
  • Celery, Redis Queue, django-background-tasks のどれを使うべきか?
  • タスクはビューのように小さくせよ
第26章:Security Best Practices(セキュリティのベストプラクティス)
  • p.327〜
  • XSS、CSRF、SQLインジェクション、クリックジャッキング、TLS/HTTPS サポート、HTMLタグの自動エスケープなどが標準装備
  • SECERT_KEY の取り扱いに注意すべし(バージョン管理ツールの管理下にしないように)
  • HTTPS/SSL 化するには django.middleware.security.SecurityMiddleware を使う(ただし、static, media は含まれないので注意)
  • 標準ライブラリの pickle には気をつけよう
  • cookieベースのセッションは使わない
  • クレジットカード番号やパスワードなどのセキュアなデータ入力フィールドを利用する際の注意点 *29
  • 強度の高いパスワードを生成するには django-passwords, django-autoadmin などを利用する
  • アップロードファイルは python-magic でファイルヘッダを確認する
  • Django管理サイトへのIPアドレス制限をする場合は Middleware で判別
第27章:Logging: What’s It For, Anyway?(ロギング:それって何のため?)
  • p.355〜
  • 各ログレベル(CRITICAL/ERROR/WARNING/INFO/DEBUG)の用途
  • ロガーは再利用せずにモジュールごとに用意する
  • Logger.exception() について
  • logutils パッケージが便利
第28章:Signals: Use Cases and Avoidance Techniques(シグナル:ユースケースと回避テクニック)
  • p.365〜
  • シグナルは同期・ブロッキングなので、パフォーマンスを考慮すべし
  • 複数モデルを操作する場合や save後にキャッシュを無効化したい場合などはシグナルを使ってよい
  • models.Manager のメソッドやフォームの cleanメソッドで置き換えられないか検討する
  • 単一モデルを操作している場合は saveメソッド内に書けないか検討する
第29章:What About Those Random Utilities?(ちょっとしたユーティリティはどうしたらいい?)
  • p.371〜
  • 汎用的な共通モジュールは coreアプリケーションに配置しよう
  • 各アプリケーション直下にヘルパーモジュール utils.py(helpers.py)を
  • Django に内蔵されているヘルパーを利用しよう *30, *31, *32
第30章:Deployment: Platforms as a Service(デプロイ:プラットフォーム・アズ・ア・サービス)
  • p.387〜
第31章:Deploying Django Projects(Djangoプロジェクトのデプロイ)
  • p.395〜
第32章:Continuous Integration(継続的インテグレーション)
  • p.411〜
  • テストをスピードアップするための Tips
  • 複数の異なる Python, Django バージョンで検証したい場合は tox を使う
  • Jenkins, Travis-CI, CircleCI などの CIツール/CIサービスを使おう
第33章:The Art of Debugging(デバッグの技術)
  • p.417〜
  • django-debug-toolbar を使おう
  • PDB を使おう *33
  • ファイルアップロードを扱う際のチェックポイント
第34章:Where and How to Ask Django Questions(Djangoに関する質問をする場所と方法)
  • p.427〜
  • 同じ問題を抱えている人がいないかググったり、MLStackOverflow をチェックしてみよう
  • django-users の MLや IRC で直接聞いてみよう
第35章:Closing Thoughts(最後に)
  • p.431〜
  • Django 2.2 がリリースされるまでは次のシリーズは書かない
  • Django 1.8 は LTE なので、しばらくはこの本が使えるでしょう


 

まとめ

「Two Scoops of Django」の著者は、Django を理解するには、フォーム(Form)、モデル(Model)、クラスベースビュー(CBV)について理解することが重要だと言っていますが、私は特に、モデルと Django ORM 周りについての理解がネックになると考えています。


「Two Scoops of Django」は全編英語で、英語が苦手な人には取っ付きにくいかもしれませんが、どの章から読み始めてもよいという性質と堅苦しくないポップな雰囲気から、Django 初級者にピッタリな本だと思います。ポップなタッチながらも、Django の実戦的なノウハウが一から十まできめ細やかに書かれていて、Django アプリのデベロッパーにとって非常に貴重な本に仕上がっています。


英語が苦手でない人には絶対オススメなので、是非読んでみてください!!



明日は、felyce さんの 6日目の記事「DjangoのForm(CreateView、UpdateViewなど)について - Qiita」です。よろしくお願いします。



 

英単語

最後に、「Two Scoops of Django」本に出てきた英単語をピックアップしました。
読むときのお役に立てば。


p.xxxv vet : 吟味する

p.xxxv distill : 抽出する

p.xxxv suffice : 十分である

p.xxxvi errata : 正誤表

p.1 abbreviate : 省略する

p.3 accommodate : 適応する、同意する

p.3 provision : 規定、供給

p.8 It goes without saying : 〜は言うまでもない

p.13 optimal : 最適な

p.13 pitfall : 落とし穴

p.13 identically : 完全に同じように

p.14 without a hitch : 問題なく

p.15 pre-populate : 自動入力する

p.15 familiarize : 習熟させる

p.26 intentional : 故意の

p.34 truncated : 省略された

p.34 in essence : 要するに

p.34 moderate : 適度な

p.35 envision : 心に思い描く

p.36 convention : 慣習、慣例

p.36 dull : つまらない

p.36 correspond with : 〜と一致する

p.36 discouraged : 推奨されない

p.37 when in doubt : 判別がつかないときは

p.37 a modicum of : 少量の

p.41 stay away from : 〜を避ける

p.42 purposefully : 意図的に

p.48 substantially : 実質的に

p.48 without hesitation : 気兼ねなく

p.63 think things through : じっくり物事を考える

p.63 down the road : やがて

p.63 sound : 堅固な、安定した

p.64 ramification : 予期せぬ問題

p.64 sloppy : ずさんな

p.64 derived : 生成された

p.65 concrete : 具象的な

p.65 overlap : 重複部分

p.65 inadvertent : うっかりした

p.65 substantial : 相当の

p.65 traverse : 横断する

p.67 nasty : 不快な

p.67 propagation : 伝播

p.67 aptly : 適切に

p.68 unwieldy : 手に負えない

p.68 bring someone to heel : 〜を従わせる

p.69 prematurely : 時期尚早に

p.69 panacea : 万能薬

p.73 unconstrained : 拘束されない

p.73 akin : 同種の

p.73 comforted : 安心した

p.78 infer : 推察する

p.79 judiciously : 思慮深く

p.81 consistently : 一貫して

p.81 quirk : 奇癖

p.81 cartwheel : 車輪

p.81 unconstrained : 制約されない、とらわれない

P.83 legible : 読みやすい

p.84 mitigate : 和らげる、軽減する

p.84 lean on : 〜に頼る

p.85 shudder : 身震いする

p.86 comparison : 比較

p.86 under the hood : 内部で

p.87 decent : 適切な

p.87 drastically : 抜本的に

p.88 approximate : 〜に近い

p.89 practitioner : 実行者

p.89 acronym : 頭字語

p.89 overhaul : 〜を徹底的に点検する

p.89 modernize : 近代化する

p.90 monumental : (記念碑のように)巨大な

p.90 embarrassing : 厄介な

p.90 crop up : 不意に起こる

P.92 downside : 否定的側面

p.96 err on the side of : 〜し過ぎて失敗する(傾向にある)

p.97 yell : 怒鳴る

p.98 argue : 異議を唱える、主張する

p.98 steer clear of : 〜を避ける

P.98 stick to : 〜に忠実である

p.101 hackery : ハッカー行為

p.101 elaborative : 入念な

p.101 automagically : 魔法のごとく自動的に

p.103 collide : 衝突する

p.104 prevalence : 普及、行き渡ること

p.104 tangible : 明らかな、具体的な

p.105 come into play : 動き始める

p.106 when it comes down to it : いざというときには、ここ一番というときには

p.106 algebra : 代数

p.107 one-off : 一回限りの、単発の

p.108 annoy : 困らせる、イライラさせる

p.108 exploit : 〜を利用する、十分に引き出す

p.108 adhere to : 〜を支持する、忠実に守る

p.109 come at the expense of : 〜を犠牲にして成り立つ、〜という代償で手に入る

p.110 cognitive overload : 認知的過負荷

p.110 arbitrary : 任意の

p.111 in the meantime : その間に

p.113 for once : 今回に限り

p.113 parlance : 専門用語

p.113 out of necessity : 必要に迫られて

p.113 to the point of : 〜するくらいまで

p.113 ubiquitous : 至る所にある

p.115 astute : 抜け目のない

p.117 advent : 出現、到来

p.117 out of the box : 難しい設定などは一切なしで

p.117 address : 対処する、取り組む

p.118 along the line of : 〜に従って

p.122 constrain : 制約する

p.128 catch : 落とし穴、わな

p.128 wire into : 〜に配線する

p.130 queue up : 列を作る

p.131 to recap : 要点をまとめると

p.131 intention : 意図

p.133 readily : すぐに、容易に

p.135 straight-forward : 簡単な、分かりやすい

p.135 in essence : 要するに

p.137 trivial : ささいな

p.137 anguish : 苦悩

p.137 albeit : 〜ではあるが

p.140 alteration : 変更

p.140 idempotent : 冪等の

p.141 capability : 能力、機能

p.141 exempt : 免除された

p.142 in conjunction with : 〜と連動して

p.144 iterate : 〜を反復する

p.144 coerce : 〜を強要する

p.146 streamline : 簡素化する

p.149 extensively : 広範囲に渡って

p.149 explicitly : 明確に

p.152 corruption : (データの)破壊

p.154 corruption : 〜を除いて

p.159 fancy : 手の込んだ

p.161 gross : 不作法な、気持ち悪い

P.167 constrain : 〜を制約する

p.168 tier : 層

p.169 consistent : 矛盾のない、一貫した

p.169 distinctive : 独特の

p.171 eloquently : 雄弁に

p.171 inefficiency : 効率が悪いこと

p.171 redeemable : 商品と交換できる

p.172 redeem : 商品と引き換える

p.173 breakdown : 分析結果

p.173 redemption : 回収

p.177 implied : 暗黙の

p.178 intensive : 集中的な

p.179 consumption : 消費

p.179 distract : 〜の気を散らす

p.179 bluntly : 単刀直入に

p.179 realign : 再編成する

p.185 mnemonic : (記憶を助ける)短い語句

p.186 intuitive : 直感的な

p.189 draw : 呼び物、引き付けるもの

p.189 carry away : 心を奪う

p.189 worse yet : さらに悪いことには

P.189 a blessing in disguise : 災い転じて福となす

p.190 bulky : 大きい

p.190 not to mention : 〜は言うまでもなく

p.191 trait : 特徴

p.191 prone : 〜しがちな

p.191 abuse : 〜を乱用する

p.193 unbearably : 我慢できないほど

p.195 fury : 激怒

p.196 supposedly : おそらく

p.196 chagrin : 残念さ

p.196 obscure : 目立たない

p.196 contention : 主張、論争

p.197 syntactical : 構文の

p.198 First off : 最初に

p.198 advent : 出現

p.198 harmoniously : 平和に

p.199 mitigate : 和らげる

p.200 incorporate : 〜を組み込む

p.207 by design : 計画的に、故意に

p.207 representational : 具象的な

p.207 craft : ~を作る

p.207 definitive : 決定的な

p.207 hindrance : 障害物

p.208 distribute : ~をばらまく、~を分配する

p.208 appropriate : 適切な

p.209 by definition : 定義上は

p.209 idempotent : 冪等の

p.209 in favor of : ~を支持して、~に賛成して

p.211 tailor : ~あつらえる、~を調整する

p.211 wire into : ~に配線する

p.213 suited for : ~向きの

p.213 neatly : きちんと

p.213 hunt down : 追跡して捕まえる

p.213 in contrast to : ~と対照的に、~とは異なり

p.213 it makes sense : 理に適う

p.214 when it comes down to it : いざとなれば

p.214 endorse : 推薦する、支持する

p.214 go for : 当てはまる

p.214 complete with : ~が全部そろった

p.214 myriad : 無数の

p.215 dedicated : 専用の、特化した

p.216 lean on : ~に頼る

p.217 abbreviate : ~を簡潔にする、~を短縮する

p.217 anger : ~を怒らせる

p.217 ample : 十分な

p.218 simultaneously : 同時に

p.218 loosely-coupled : 疎結合の

p.218 preferably : できれば

p.220 grumble : 不平を言う

p.220 analogy : 比喩

p.220 inventory : 在庫

p.221 unfettered : 拘束されない、自由な

p.221 graciously : 快く

p.221 desirous : 望んで

p.222 utter : 全くの

p.222 tier : 層

p.222 tie into : 勢いよくとりかかる

p.222 embrace : ~を利用する、~を採用する

p.223 vantage : 優越

p.225 twofold : 二つ折りの、二つの要素からなる

p.225 landscape : 状況

p.225 evolve : 進化する

p.225 advent : 出現

p.225 controversy : 議論

p.227 weed : 除外する

p.227 chaff : もみ殻、無用のもの

p.229 woe : 苦悩

p.229 latency : 待ち時間

p.229 circumference : 周囲

p.229 noticeable : 容易に気付く

p.229 alienate : 遠ざける

p.229 hiccup : 一時的な中断

p.229 distract : ~の気を散らす

p.230 feasible : 実現可能な

p.230 albeit : ~ではあるが

p.230 trickery : 策略

p.230 geographically : 地理的に

p.230 avenue : 手段

p.230 reliant : 依存している

p.230 mitigate : 軽減する

p.231 suffice : 十分である

p.231 certainly : 確かに

p.231 tempting : 誘惑的な

p.234 straight-forward : 分かりやすい、単純な

p.235 consumption : 消費

p.235 up to par : 基準に達して

p.235 capable : 有能な

p.235 ramp up : 成長させる

p.235 reap : ~を得る

p.235 assess : ~を評価する

p.235 a plethora of : 大量の

p.235 advocate : 推奨する

p.237 hype : 誇大広告、うそ

p.237 sacrifice : 〜を犠牲にする

p.237 prematurely : 時期尚早に

p.238 Franken : フランケンの、遺伝子組換えの

p.238 fad : 流行のもの

p.239 implication : 影響

p.239 compliant : 準拠した

p.240 non-issue : 取るに足りない問題

p.240 swallow : 鵜呑みにする

p.241 take it too far : 行き過ぎる、無理する

P.241 advocate : 支持する、推奨する

p.243 mess around with : 〜をいじり回す

p.243 revoke : 〜を無効にする、〜を取り消す

p.243 surreal : 非現実的な

p.250 oddly : 変に

p.251 venerable : 尊敬すべき、非常に古い

p.251 up-and-comer : 新人

p.252 arcane : 難解な

p.252 account for : 〜を構成する、〜の主要因である

p.252 idiosyncrasy : 特異性

p.252 invariably : いつも、常に

p.252 mind-numbing : 極めて退屈でつまらない

p.255 significantly : 著しく、かなり

p.255 radical : 抜本的な、急進的な

p.257 timid : 臆病な

p.257 intrusive : 煩わしい

p.257 in place : 適所に

p.259 bare-bones : 必要最低限の

p.259 slate : 候補者リスト

p.259 sane : 健全な

p.260 unobtrusive : 控えめな

p.261 dedicated : 専用の

p.263 incorporation : 組み込み、合体

p.265 competent : 有能な

p.265 tragic : 悲惨な

p.265 downfall : 転落、崩壊

p.267 formality : 手続き

p.268 pore over : 〜を熟読する

p.268 be willing to : 喜んで〜しようとする

p.269 submission : 提出

p.270 suffice : 十分である

p.270 bulletproof : 防弾の

p.270 in lieu of : 〜の代わりに

p.271 diverge : 分岐する

p.271 invasive : 侵略的な

p.273 forthcoming : 来たるべき

p.273 purist : 純粋主義者

p.274 be meant to : 〜であることを意図されている

p.274 harness : 利用する、役立てる

p.274 scare off : (怖がらせて)追い払う

p.274 obscenity : わいせつなもの

p.275 permissive : 寛大な

p.275 litigation : 訴訟

p.275 liable : 責任がある

p.275 disclaimer : 免責事項

p.275 liability : 法的責任、損害賠償

p.275 interoperability : 相互運用性

p.276 rewarding : やりがいのある

p.277 burnout : 燃え尽き症候群

p.278 irresponsible : 責任のない

p.278 fault : 〜を責める

p.280 in turn : 次に

p.280 exploit : 〜を悪用する

p.281 invariably : いつも、必ず

p.281 give away : 譲る

p.281 notable : 有名な

p.282 diligence : 不断の努力

p.283 stink : 疑わしいもの、悪臭

p.283 rigorous : 厳しい、厳密な

p.283 adequate : 適正な、適切な

p.285 hence : だから、したがって

p.286 therein : その中に、その点で

p.286 conundrum : 難しい問題

p.288 when possible : 可能であれば

p.290 subtly : 微妙に

p.290 wrestle with : 〜と格闘する

p.290 malignant : 悪意のある

p.290 intruder : 侵入者

p.290 at a distance : 少し離れて

p.290 grief : 厄介、面倒

p.292 undetected : 発見されていない

p.292 alas : ああ、悲しいかな

p.295 remedy : 改善する、是正する

p.296 fragile : 壊れやすい、不安定な

p.296 boredom : 退屈

p.297 error-prone : 間違いを起こしやすい

p.299 mandate : 命令する

p.299 gradual : 段階的な

p.299 bogus : 偽造の

p.299 thus far : ここまでは

p.299 comprehend : 理解する

p.301 That being said : そうは言っても

p.301 can't help but : 〜せずにはいられない

p.305 infrequently : まれに

p.305 tangential : 無関係の、脱線した

p.309 snappy : きびきびした

p.309 excessive : 必要以上の、過度の

p.310 invaluable : すこぶる有益な

p.312 noticeable : 目立つ

p.320 pitch in : 協力する

p.320 supervision : 監督、管理

p.320 Arguably : 議論の余地はあるが、ほぼ間違いなく

p.320 intensive : 集約的な

p.321 overkill : やり過ぎ

p.321 footprint : 足跡、専有面積

p.321 rules of thumb : 経験則

p.322 quirk : 奇癖

p.322 portability : 可搬性

p.323 underlying : 下部の、内在する

p.323 counter-productive : 非生産的な

p.328 malicious : 悪意のある

p.328 sniff : 盗聴する

p.328 up for grabs : 容易に手に入る

p.329 reputable : 信頼できる

p.332 expedite : 〜を早める、〜を迅速に処理する

p.332 malignant : 悪意のある

p.334 beware of : 〜に気をつける

p.335 falsify : 改ざんする

p.335 impersonate : 〜になりすます

p.336 transmit : 送信する

p.338 coerce : 〜を強要する

p.338 grave : 重大な

p.341 arbitrary : 任意の

p.341 be of concern : 心配事である

p.341 adequate : 適切な

p.342 incorporate : 〜を組み込む

p.343 paraphrased : 言い換える

p.343 trivial : ささいな

p.344 fine-grain : 微粒子の

p.344 in conjunction with : 〜と連動して

p.345 concealed : 隠れた

p.345 devastating : 壊滅的な

p.347 venerable : 尊敬すべき

p.347 punctuation : 句読点

p.347 pledge : 誓う

p.348 put up : 用意する、立ち上げる

p.348 overwhelm : 圧倒する、〜をひっくり返す

p.348 snap decision : 即断、見切り発車

p.348 ill-advised : 軽率な

p.349 bonded : 保証付きの

p.351 compromise : 譲歩

p.351 obfuscate : 分かりにくくする

p.351 cumbersome : 扱いにくい

p.352 nigh : ほとんど

p.352 corresponding : 同様の

p.353 diligence : 不断の努力

p.355 What's it for? : それは何に使うの?

p.355 once in a while : たまに

p.355 meticulously : 慎重に

p.356 go-to : 頼りになる

p.356 unpredictable : 予測できない

p.356 might as well : 〜してもいい

p.356 fine-tune : 微調整する

p.356 account for : 〜を構成する

p.356 introspection : 内省

p.357 catastrophic : 壊滅的な

p.357 sparingly : 控えめに

p.358 intruder : 侵入者

p.360 sprinkle : ちりばめる

p.360 debt : 負債

p.360 go overboard : やり過ぎる

p.360 clutter : 〜で溢れさせる

p.363 reduction : 減少

p.365 last resort : 最後の手段

p.365 knot : からまる

p.365 hairball : 毛玉

p.365 untangle : 〜のもつれを解く

p.365 dispatch : 〜を送り出す

p.365 obfuscation : 難読化

p.366 inversion : 逆転

p.370 delve into : 掘り下げる

p.371 general-purpose : 用途の広い

p.372 inevitably : 必然的に

p.372 Synonymous with : 〜と同じ意味の

p.373 brobdingnagian : 巨大な

p.374 indispensable : 不可欠の

p.385 a plethora of : 過多の

p.385 tangle : 混乱

p.411 originator : 創始者

p.411 cohesive : 団結した、まとまりのある

p.412 statically typed : 静的に型付けされた

p.413 oodles : たくさん

p.414 analogue : 類似品

p.415 outweigh : 上回る

p.417 annoying : うるさい

p.418 -naut : 〜の航行者

p.419 in regards to : 〜に関しては

p.419 disastrous : 破滅的な

p.419 essential : 必要不可欠なもの

p.419 frustrating : イライラする

p.422 immensely : 広大に

p.422 embrace : 受け入れる、採用する

p.422 to the fullest : 最大限に

p.423 replicate : 〜を複製する

p.424 nemesis : 悪の根源、因果応報

p.425 haywire : めちゃくちゃな

p.425 utter : ひどい

p.425 unfold : 姿を現す

p.425 aficionado : 熱狂的なファン

p.426 in exchange for : 〜と引き換えに

p.427 get stuck : 行き詰まる

p.427 descriptive : 記述的な

p.428 troll : 煽る

p.428 cranky : 気難しい

p.428 gratitude : 感謝、感謝の気持ち

p.428 go a long way : 大いに役立つ

p.428 make someone's day : 人を幸せな気分にさせる

p.428 fill up : 満たす

p.428 a tidbit of : ちょっとした

p.429 put in : 投資する

p.429 diversity : 多様性

p.429 underrepresent : 過小評価する

p.431 ground : 領域

p.431 starter : 初心者

p.431 as for : 〜に関しては

p.431 genuinely : 心から

p.431 omission : 省略、手抜かり

*1:英語は易しめだと思います

*2:二人は 2013年末に結婚しています。 https://www.pydanny.com/i-married-audrey-roy.html また、cookiecutter-django のコアコミッターとしても有名です。 https://github.com/audreyr/cookiecutter

*3:データベースごとにフィールドタイプの扱い方が違っていたりする

*4:例えば「~/projects/」あるいは「~/.envs/」など。virtualenvwrapper を使っているのであれば「~/.virtualenvs/」など

*5:「Django apps are small libraries designed to represent a single aspect of a project.」とのこと。

*6:推奨しないが、単語と単語の間にはアンダースコアを使ってもよい

*7:AWS・Stripe などの APIキー、OAuth トークンなども含まれる

*8:「abstract base classes」は Metaクラスに「abstract = True」を記述した抽象ベースクラスを継承する方式、「multi-table inheritance」は OneToOneField で複数のテーブルを一対一関連させる方式、「proxy models」は Metaクラスに「proxy = True」を記述した子クラスで継承する方式。 Models | Django documentation | Django

*9:multi-table inheritance 方式は使わないようにしよう!

*10:ヘルパー関数、フォーム、モデルのメソッドなど、ビュー(やビューに直接関連した部品)以外では使わないこと

*11:https://docs.djangoproject.com/en/1.8/ref/models/database-functions/

*12:書くなら raw() で。extra() は使わない

*13:リクエストごとにしたいのであれば、settings.py の「ATOMIC_REQUESTS」の設定を True にする。ビュー中の特定の範囲にのみトランザクションを適用する場合は「with transaction.non_atomic_requests()」で囲む

*14:複雑にカスタマイズしたい場合だけ関数ベースのビューを使えばよい

*15:Mixin を自作するときは Python object 型を継承し、複雑に継承させない設計にする

*16:View, RedirectView, TemplateView, ListView, DetailView, FormView, CreateView, UpdateView, DeleteView, Generic date views が紹介されている

*17:https://django-braces.readthedocs.io/en/latest/form.html#userformkwargsmixin:UserFormKwargsMixin, https://django-braces.readthedocs.io/en/latest/form.html#userkwargmodelformmixin:UserKwargModelFormMixin など

*18:django-floppyforms, django-crispy-forms, django-forms-bootstrap が紹介されている

*19:select_related の詳細については、過去記事「Django ORM の select_related, prefetch_related の挙動を詳しく調べてみた - akiyoko blog」を参照

*20:パフォーマンスの問題がある場合以外は DTL のままでよい

*21:Python 2.7 の場合は python_2_unicode_compatible デコレータを使うか、__unicode__() メソッドを用意する

*22:AbstractUser を継承する方法、AbstractBaseUser を継承する方法、ユーザモデルに対して一対多・一対一・多対多関連のフィールドを作成する方法

*23:「パイピーアイ」と発音する。pip はここからパッケージを検索する

*24:代わりに、factory boy などのツールを使おう

*25:過去記事「Python で MagicMock を使う - akiyoko blog」を参照

*26:https://docs.python.org/2/library/unittest.html#assert-methods

*27:過去記事「akiyoko.hatenablog.jp」を参照

*28:Python, Django 界隈ではあまり使われないが、Markdown 形式も人気。なお、Pandoc を使えばマークアップを自由に変換可能

*29:Djangoフォームで autocomplete を off にする、django.forms.PasswordInput を使うなど

*30:ただし、django.utils 内のモジュールは内部向けに作られたものでバージョンによって挙動が変わるので注意。詳しくは Django Utils | Django documentation | Django を参照

*31:django.core.exceptions のいくつかのビルトイン例外クラスが利用可能

*32:シリアライズ/デシリアライズには django.core.serializers が使える

*33:過去記事「PDB QUEST ~ pdb のショートカットはドラクエ風に覚えよう ~ - akiyoko blog」を参照

Mezzanine に Cartridge 0.12 を導入してみる

この前 4.2.2 にアップデートした Mezzanine サイトに、Cartridge 0.12 を導入してみました。


Mezzanine は、Python製の WordPress風フルスタックCMSフレームワークですが、一方の Cartridge は、Mezzanine 専用に作られた、Mezzanine に ECサイト機能を搭載するためのアプリケーションです。 *1


イメージとしてはこんな感じです。

f:id:akiyoko:20161120180920p:plain


具体的には、商品登録やセール設定、ディスカウント設定、注文管理などの機能を備えたバックオフィス(Django Admin を拡張)、ショッピングカート、PayPal および Stripe に対応した決済モジュール(プラガブルに変更可能)などの機能が付きます。


f:id:akiyoko:20161120182828p:plain
(ショッピングカート機能)


f:id:akiyoko:20161120184415p:plain
(バックオフィス機能)


 

1. 導入手順

Mezzanine に Cartridge 0.12 を導入する手順は以下の通りです。


Cartridge 0.12.0 をインストールします。

(akiyokoproject)$ pip install cartridge==0.12.0

Successfully installed cartridge-0.12.0 pyPdf2-1.26.0 reportlab-3.3.0 xhtml2pdf-0.0.6


config/settings.py に Cartridge 用の設定を追記します。

--- a/config/settings.py
+++ b/config/settings.py
@@ -7,6 +7,76 @@ from django.utils.translation import ugettext_lazy as _


 ######################
+# CARTRIDGE SETTINGS #
+######################
+
+# The following settings are already defined in cartridge.shop.defaults
+# with default values, but are common enough to be put here, commented
+# out, for conveniently overriding. Please consult the settings
+# documentation for a full list of settings Cartridge implements:
+# http://cartridge.jupo.org/configuration.html#default-settings
+
+# Sequence of available credit card types for payment.
+# SHOP_CARD_TYPES = ("Mastercard", "Visa", "Diners", "Amex")
+
+# Setting to turn on featured images for shop categories. Defaults to False.
+# SHOP_CATEGORY_USE_FEATURED_IMAGE = True
+
+# Set an alternative OrderForm class for the checkout process.
+# SHOP_CHECKOUT_FORM_CLASS = 'cartridge.shop.forms.OrderForm'
+
+# If True, the checkout process is split into separate
+# billing/shipping and payment steps.
+# SHOP_CHECKOUT_STEPS_SPLIT = True
+
+# If True, the checkout process has a final confirmation step before
+# completion.
+# SHOP_CHECKOUT_STEPS_CONFIRMATION = True
+
+# Controls the formatting of monetary values accord to the locale
+# module in the python standard library. If an empty string is
+# used, will fall back to the system's locale.
+# SHOP_CURRENCY_LOCALE = ""
+
+# Dotted package path and name of the function that
+# is called on submit of the billing/shipping checkout step. This
+# is where shipping calculation can be performed and set using the
+# function ``cartridge.shop.utils.set_shipping``.
+# SHOP_HANDLER_BILLING_SHIPPING = \
+#                       "cartridge.shop.checkout.default_billship_handler"
+
+# Dotted package path and name of the function that
+# is called once an order is successful and all of the order
+# object's data has been created. This is where any custom order
+# processing should be implemented.
+# SHOP_HANDLER_ORDER = "cartridge.shop.checkout.default_order_handler"
+
+# Dotted package path and name of the function that
+# is called on submit of the payment checkout step. This is where
+# integration with a payment gateway should be implemented.
+# SHOP_HANDLER_PAYMENT = "cartridge.shop.checkout.default_payment_handler"
+
+# Sequence of value/name pairs for order statuses.
+# SHOP_ORDER_STATUS_CHOICES = (
+#     (1, "Unprocessed"),
+#     (2, "Processed"),
+# )
+
+# Sequence of value/name pairs for types of product options,
+# eg Size, Colour. NOTE: Increasing the number of these will
+# require database migrations!
+# SHOP_OPTION_TYPE_CHOICES = (
+#     (1, "Size"),
+#     (2, "Colour"),
+# )
+
+# Sequence of indexes from the SHOP_OPTION_TYPE_CHOICES setting that
+# control how the options should be ordered in the admin,
+# eg for "Colour" then "Size" given the above:
+# SHOP_OPTION_ADMIN_ORDER = (2, 1)
+
+
+######################
 # MEZZANINE SETTINGS #
 ######################

@@ -21,7 +91,9 @@ from django.utils.translation import ugettext_lazy as _
 #
 # ADMIN_MENU_ORDER = (
 #     ("Content", ("pages.Page", "blog.BlogPost",
-#        "generic.ThreadedComment", (_("Media Library"), "media-library"),)),
+#        "generic.ThreadedComment", (_("Media Library"), "fb_browse"),)),
+#     (_("Shop"), ("shop.Product", "shop.ProductOption", "shop.DiscountCode",
+#        "shop.Sale", "shop.Order")),
 #     ("Site", ("sites.Site", "redirects.Redirect", "conf.Setting")),
 #     ("Users", ("auth.User", "auth.Group",)),
 # )
@@ -247,6 +319,7 @@ INSTALLED_APPS = (
     "mezzanine.core",
     "mezzanine.generic",
     "mezzanine.pages",
+    "cartridge.shop",
     "mezzanine.blog",
     "mezzanine.forms",
     "mezzanine.galleries",
@@ -272,6 +345,7 @@ MIDDLEWARE_CLASSES = (
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',

+    "cartridge.shop.middleware.ShopMiddleware",
     "mezzanine.core.request.CurrentRequestMiddleware",
     "mezzanine.core.middleware.RedirectFallbackMiddleware",
     "mezzanine.core.middleware.TemplateForDeviceMiddleware",
diff --git a/config/urls.py b/config/urls.py
index c67fd17..1b3b87c 100644
--- a/config/urls.py
+++ b/config/urls.py
@@ -8,6 +8,8 @@ from django.views.i18n import set_language
 from mezzanine.core.views import direct_to_template
 from mezzanine.conf import settings

+from cartridge.shop.views import order_history
+

 admin.autodiscover()

@@ -27,6 +29,10 @@ if settings.USE_MODELTRANSLATION:
     ]

 urlpatterns += [
+    # Cartridge URLs.
+    url("^shop/", include("cartridge.shop.urls")),
+    url("^account/orders/$", order_history, name="shop_order_history"),
+
     # We don't want to presume how your homepage works, so here are a
     # few patterns you can use to set it up.

@@ -93,7 +99,7 @@ urlpatterns += [
     # Note that for any of the various homepage patterns above, you'll
     # need to use the ``SITE_PREFIX`` setting as well.

-    # ("^%s/" % settings.SITE_PREFIX, include("mezzanine.urls"))
+    # url("^%s/" % settings.SITE_PREFIX, include("mezzanine.urls"))

 ]

diff --git a/requirements.txt b/requirements.txt
index 0c4e494..4d43fbe 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
 Mezzanine==4.2.2
+cartridge==0.12.0
 django-ses

追加した内容については、Mezzanine と Cartridge を同時にインストールするコマンドを叩いたときの settings.py の差分を参照しました(詳しくは後述)。


マイグレーションを実行します。

(akiyokoproject)$ python manage.py makemigrations
No changes detected

(akiyokoproject)$ python manage.py migrate
System check identified some issues:

WARNINGS:
?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
	HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it. See: https://docs.djangoproject.com/en/1.10/ref/databases/#mysql-sql-mode
Operations to perform:
  Apply all migrations: admin, auth, blog, conf, contenttypes, core, django_comments, forms, galleries, generic, pages, redirects, sessions, shop, sites, twitter
Running migrations:
  Applying shop.0001_initial... OK
  Applying shop.0002_auto_20141227_1331... OK
  Applying shop.0003_emailfield... OK
  Applying shop.0004_productimage_file_field... OK
  Applying shop.0005_auto_20150527_1127... OK
  Applying shop.0006_auto_20150916_0459... OK
  Applying shop.0007_auto_20150921_2323... OK

shop 関連のテーブルが作成されました。




以下の Cartridge のテンプレートを、templates/ および custom/ 配下にコピーします。
(例によって、admin 関連のテンプレートは修正しないため、コピー対象に含めていません。)

templates/accounts/account_profile_update.html
templates/email/base.html
templates/email/order_receipt.html
templates/email/order_receipt.txt
templates/email/receipt.html
templates/email/receipt_rtl.html
templates/pages/category.html
templates/shop/base.html
templates/shop/billing_shipping.html
templates/shop/cart.html
templates/shop/checkout.html
templates/shop/complete.html
templates/shop/confirmation.html
templates/shop/includes/order_details.html
templates/shop/includes/order_details_rtl.html
templates/shop/includes/order_totals.html
templates/shop/includes/order_totals.txt
templates/shop/includes/payment_fields.html
templates/shop/includes/user_panel.html
templates/shop/order_history.html
templates/shop/order_invoice.html
templates/shop/order_invoice_pdf.html
templates/shop/payment.html
templates/shop/product.html
templates/shop/wishlist.html

custom/templates/accounts/account_profile_update.html
custom/templates/email/base.html
custom/templates/email/order_receipt.html
custom/templates/email/order_receipt.txt
custom/templates/email/receipt.html
custom/templates/email/receipt_rtl.html
custom/templates/pages/category.html
custom/templates/shop/base.html
custom/templates/shop/billing_shipping.html
custom/templates/shop/cart.html
custom/templates/shop/checkout.html
custom/templates/shop/complete.html
custom/templates/shop/confirmation.html
custom/templates/shop/includes/order_details.html
custom/templates/shop/includes/order_details_rtl.html
custom/templates/shop/includes/order_totals.html
custom/templates/shop/includes/order_totals.txt
custom/templates/shop/includes/payment_fields.html
custom/templates/shop/includes/user_panel.html
custom/templates/shop/order_history.html
custom/templates/shop/order_invoice.html
custom/templates/shop/order_invoice_pdf.html
custom/templates/shop/payment.html
custom/templates/shop/product.html
custom/templates/shop/wishlist.html


最後に、プロセスを再起動して完了です。

(akiyokoproject)$ sudo supervisorctl restart all

 

細かい話

2.1. ライブラリの差分

Cartridge 導入前のライブラリ一覧

awscli (1.11.10)
beautifulsoup4 (4.5.1)
bleach (1.4.3)
boto (2.41.0)
botocore (1.4.67)
chardet (2.3.0)
colorama (0.3.7)
Django (1.10.3)
django-appconf (1.0.2)
django-compressor (2.0)
django-contrib-comments (1.7.3)
django-ses (0.8.0)
docutils (0.12)
filebrowser-safe (0.4.6)
future (0.16.0)
futures (3.0.5)
grappelli-safe (0.4.5)
gunicorn (19.6.0)
html5lib (0.9999999)
jmespath (0.9.0)
Mezzanine (4.2.2)
MySQL-python (1.2.5)
oauthlib (2.0.0)
Pillow (3.4.2)
pip (8.1.2)
psycopg2 (2.6.2)
pyasn1 (0.1.9)
python-dateutil (2.5.3)
python-memcached (1.58)
pytz (2016.7)
rcssmin (1.0.6)
requests (2.11.1)
requests-oauthlib (0.7.0)
rjsmin (1.0.12)
rsa (3.4.2)
s3transfer (0.1.9)
setproctitle (1.1.10)
setuptools (24.0.2)
six (1.10.0)
tzlocal (1.3)
wheel (0.29.0)

導入後のライブラリ一覧

$ pip list
awscli (1.11.10)
beautifulsoup4 (4.5.1)
bleach (1.4.3)
boto (2.41.0)
botocore (1.4.67)
Cartridge (0.12.0)
chardet (2.3.0)
colorama (0.3.7)
Django (1.10.3)
django-appconf (1.0.2)
django-compressor (2.0)
django-contrib-comments (1.7.3)
django-ses (0.8.0)
docutils (0.12)
filebrowser-safe (0.4.6)
future (0.16.0)
futures (3.0.5)
grappelli-safe (0.4.5)
gunicorn (19.6.0)
html5lib (0.9999999)
jmespath (0.9.0)
Mezzanine (4.2.2)
MySQL-python (1.2.5)
oauthlib (2.0.0)
Pillow (3.4.2)
pip (8.1.2)
psycopg2 (2.6.2)
pyasn1 (0.1.9)
PyPDF2 (1.26.0)
python-dateutil (2.5.3)
python-memcached (1.58)
pytz (2016.7)
rcssmin (1.0.6)
reportlab (3.3.0)
requests (2.11.1)
requests-oauthlib (0.7.0)
rjsmin (1.0.12)
rsa (3.4.2)
s3transfer (0.1.9)
setproctitle (1.1.10)
setuptools (24.0.2)
six (1.10.0)
tzlocal (1.3)
wheel (0.29.0)
xhtml2pdf (0.0.6)


 

2.2. config/setting.py の差分

Mezzanine と Cartridge を同時にインストールするには、

mezzanine-project -a cartridge config .

というコマンドを叩けばよいのですが、Mezzanine だけをインストールした場合と Mezzanine と Cartridge を同時にインストールした場合とで、自動生成される settings.py の差分を確認してみました。


1)(Cartridge 無しの)通常 Mezzanine インストール
sudo mkdir -p /opt/webapps/mezzproject
sudo chown -R `whoami`. /opt/webapps
cd /opt/webapps/mezzproject/
mezzanine-project config .
2)Cartridge 付きの Mezzanine インストール
sudo mkdir -p /opt/webapps/mezzproject_cartridge
sudo chown -R `whoami`. /opt/webapps
cd /opt/webapps/mezzproject_cartridge/
mezzanine-project -a cartridge config .


(diff 関連の参考リンク)

$ diff -r -q /opt/webapps/mezzproject /opt/webapps/mezzproject_cartridge
$ diff -r -u /opt/webapps/mezzproject /opt/webapps/mezzproject_cartridge | vi -R -

-q : ファイル名だけ表示
-r : ディレクトリ比較
-u : ユニファイド形式
| vi -R - : vim の機能を使って色分け

ちなみに、進んでいる方を第二引数にした方がよさそうです(+ で diff表示されるので)。

$ diff -r -u /opt/webapps/mezzproject /opt/webapps/mezzproject_cartridge
Only in /opt/webapps/mezzproject_cartridge/config: dev.db
diff -r -u /opt/webapps/mezzproject/config/local_settings.py /opt/webapps/mezzproject_cartridge/config/local_settings.py
--- /opt/webapps/mezzproject/config/local_settings.py   2016-11-03 03:06:58.160419703 +0000
+++ /opt/webapps/mezzproject_cartridge/config/local_settings.py 2016-11-03 03:07:09.000000000 +0000
@@ -8,8 +8,8 @@
 DEBUG = True

 # Make these unique, and don't share it with anybody.
-SECRET_KEY = "k8dd+%#@kw6vh1a-#k(l1agb=@kibpbz&s$7nk)l@s06gz*gx9"
-NEVERCACHE_KEY = "15fmx+kop%oxps(1w3uq5i1btc#3t=+t!#=ax@3!4^@$s%4gu2"
+SECRET_KEY = "(#eotia6m9*m(9vt)@u7^6@)e$f44c(4)4n9*((o%(^u3!u&cw"
+NEVERCACHE_KEY = "n66suh9!=@^-_6^toe)s_z)c$0g70e%in1ifzo7w%&uf&b#z(y"

 DATABASES = {
     "default": {
diff -r -u /opt/webapps/mezzproject/config/settings.py /opt/webapps/mezzproject_cartridge/config/settings.py
--- /opt/webapps/mezzproject/config/settings.py 2016-11-03 03:06:58.164419703 +0000
+++ /opt/webapps/mezzproject_cartridge/config/settings.py   2016-11-03 03:07:09.000000000 +0000
@@ -7,6 +7,76 @@


 ######################
+# CARTRIDGE SETTINGS #
+######################
+
+# The following settings are already defined in cartridge.shop.defaults
+# with default values, but are common enough to be put here, commented
+# out, for conveniently overriding. Please consult the settings
+# documentation for a full list of settings Cartridge implements:
+# http://cartridge.jupo.org/configuration.html#default-settings
+
+# Sequence of available credit card types for payment.
+# SHOP_CARD_TYPES = ("Mastercard", "Visa", "Diners", "Amex")
+
+# Setting to turn on featured images for shop categories. Defaults to False.
+# SHOP_CATEGORY_USE_FEATURED_IMAGE = True
+
+# Set an alternative OrderForm class for the checkout process.
+# SHOP_CHECKOUT_FORM_CLASS = 'cartridge.shop.forms.OrderForm'
+
+# If True, the checkout process is split into separate
+# billing/shipping and payment steps.
+# SHOP_CHECKOUT_STEPS_SPLIT = True
+
+# If True, the checkout process has a final confirmation step before
+# completion.
+# SHOP_CHECKOUT_STEPS_CONFIRMATION = True
+
+# Controls the formatting of monetary values accord to the locale
+# module in the python standard library. If an empty string is
+# used, will fall back to the system's locale.
+# SHOP_CURRENCY_LOCALE = ""
+
+# Dotted package path and name of the function that
+# is called on submit of the billing/shipping checkout step. This
+# is where shipping calculation can be performed and set using the
+# function ``cartridge.shop.utils.set_shipping``.
+# SHOP_HANDLER_BILLING_SHIPPING = \
+#                       "cartridge.shop.checkout.default_billship_handler"
+
+# Dotted package path and name of the function that
+# is called once an order is successful and all of the order
+# object's data has been created. This is where any custom order
+# processing should be implemented.
+# SHOP_HANDLER_ORDER = "cartridge.shop.checkout.default_order_handler"
+
+# Dotted package path and name of the function that
+# is called on submit of the payment checkout step. This is where
+# integration with a payment gateway should be implemented.
+# SHOP_HANDLER_PAYMENT = "cartridge.shop.checkout.default_payment_handler"
+
+# Sequence of value/name pairs for order statuses.
+# SHOP_ORDER_STATUS_CHOICES = (
+#     (1, "Unprocessed"),
+#     (2, "Processed"),
+# )
+
+# Sequence of value/name pairs for types of product options,
+# eg Size, Colour. NOTE: Increasing the number of these will
+# require database migrations!
+# SHOP_OPTION_TYPE_CHOICES = (
+#     (1, "Size"),
+#     (2, "Colour"),
+# )
+
+# Sequence of indexes from the SHOP_OPTION_TYPE_CHOICES setting that
+# control how the options should be ordered in the admin,
+# eg for "Colour" then "Size" given the above:
+# SHOP_OPTION_ADMIN_ORDER = (2, 1)
+
+
+######################
 # MEZZANINE SETTINGS #
 ######################

@@ -21,7 +91,9 @@
 #
 # ADMIN_MENU_ORDER = (
 #     ("Content", ("pages.Page", "blog.BlogPost",
-#        "generic.ThreadedComment", (_("Media Library"), "media-library"),)),
+#        "generic.ThreadedComment", (_("Media Library"), "fb_browse"),)),
+#     (_("Shop"), ("shop.Product", "shop.ProductOption", "shop.DiscountCode",
+#        "shop.Sale", "shop.Order")),
 #     ("Site", ("sites.Site", "redirects.Redirect", "conf.Setting")),
 #     ("Users", ("auth.User", "auth.Group",)),
 # )
@@ -243,6 +315,7 @@
     "mezzanine.core",
     "mezzanine.generic",
     "mezzanine.pages",
+    "cartridge.shop",
     "mezzanine.blog",
     "mezzanine.forms",
     "mezzanine.galleries",
@@ -251,6 +324,7 @@
     # "mezzanine.mobile",
 )

+
 # List of middleware classes to use. Order is important; in the request phase,
 # these middleware classes will be applied in the order given, and in the
 # response phase the middleware will be applied in reverse order.
@@ -267,12 +341,15 @@
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',

+    "cartridge.shop.middleware.ShopMiddleware",
     "mezzanine.core.request.CurrentRequestMiddleware",
     "mezzanine.core.middleware.RedirectFallbackMiddleware",
     "mezzanine.core.middleware.TemplateForDeviceMiddleware",
     "mezzanine.core.middleware.TemplateForHostMiddleware",
     "mezzanine.core.middleware.AdminLoginInterfaceSelectorMiddleware",
     "mezzanine.core.middleware.SitePermissionMiddleware",
+    # Uncomment the following if using any of the SSL settings:
+    # "mezzanine.core.middleware.SSLRedirectMiddleware",
     "mezzanine.pages.middleware.PageMiddleware",
     "mezzanine.core.middleware.FetchFromCacheMiddleware",
 )
diff -r -u /opt/webapps/mezzproject/config/urls.py /opt/webapps/mezzproject_cartridge/config/urls.py
--- /opt/webapps/mezzproject/config/urls.py 2016-11-03 03:06:58.160419703 +0000
+++ /opt/webapps/mezzproject_cartridge/config/urls.py   2016-11-03 03:07:09.000000000 +0000
@@ -4,10 +4,11 @@
 from django.conf.urls.i18n import i18n_patterns
 from django.contrib import admin
 from django.views.i18n import set_language
-
 from mezzanine.core.views import direct_to_template
 from mezzanine.conf import settings

+from cartridge.shop.views import order_history
+

 admin.autodiscover()

@@ -27,6 +28,11 @@
     ]

 urlpatterns += [
+
+    # Cartridge URLs.
+    url("^shop/", include("cartridge.shop.urls")),
+    url("^account/orders/$", order_history, name="shop_order_history"),
+
     # We don't want to presume how your homepage works, so here are a
     # few patterns you can use to set it up.

@@ -93,7 +99,7 @@
     # Note that for any of the various homepage patterns above, you'll
     # need to use the ``SITE_PREFIX`` setting as well.

-    # ("^%s/" % settings.SITE_PREFIX, include("mezzanine.urls"))
+    # url("^%s/" % settings.SITE_PREFIX, include("mezzanine.urls"))

 ]

Only in /opt/webapps/mezzproject_cartridge: .DS_Store


以上を踏まえて、内容を追加しました。

*1:Mezzanine 公式ページには「Ecommerce / Shopping cart module」と紹介されています。