Python で(多分)最も使われている全部入り(フルスタック) Webフレームワーク、Django。そのDjango 上で動く、(多分)最も勢いがある ECパッケージ、Oscar。
その Oscar を日本で使うためのガイドとして書きました。
Django-Oscar ドキュメント: https://django-oscar.readthedocs.org/
リポジトリ: https://github.com/django-oscar/django-oscar
Oscar の特徴
- Python + Django 環境上で動くので、経験者であればカスタマイズが容易
- Python3 でも動く
- Django は
最新の1.8 必須。(追記:1.9リリースされてた) - 修正BSDライセンス(BSD 3-Clause License (Revised))。Djangoと同じ。
- Bootstrap 入り。デモテンプレートからレスポンシブになってます。
- 多言語対応済み。日本語のPOファイルも最初から入ってますが、それほど充実してはいないです。
- 多通貨対応済み。通貨の型は標準では小数点付き10進数なので、日本人の僕は最初少し戸惑った。
- 最初からさまざまな国の住所が登録できる。
- 決済システムは最初は入ってないので、pip で入れるか自分で書く。
- コアコード以外を柔軟にカスタマイズ可能。例えば、モデルにフィールドを追加したり、ビューの動作を変えたりなどは元コードに手を入れなくてもできる。(コアコードを修正するならモンキーパッチかリポジトリのフォークかな…)
- 商品(カタログ) は、クラス > プロダクト > 在庫&料金 という3段構成。
- 在庫&料金クラスは、通貨別にそれぞれ在庫が設定できる。
- カテゴリクラスがある。 カテゴリ:プロダクトは 多:多
- 商品の画像は、通常サイトから画像ファイルをアップロードする形式で、外部サービスに画像を登録して URL だけモデルに書く、という使い方は最初はできない。モデルのカスタマイズとテンプレート修正が必要。
- 税率は、税率クラスをハードコーディングで書く。
- スタイルシートは less
- 管理サイトにログインする管理者アカウントと、一般顧客のアカウントは同じモデル(テーブル)に作ることになる。パーミッションフラグで権限を分ける。このモデルの分離をしたいならかなり困難そう。
最初から入ってる機能
- レビュー機能
- ウィッシュリスト
- オファー (特定条件指定のセール機能)
- バウチャー(いわゆるクーポンコード)
- 管理サイト(ダッシュボード)
- サムネイル生成
- JS, CSS圧縮 (django-compressor)
など。
レビューなどの機能を OFF にするのは比較的簡単にできます。(settingsで可能)
そういえば、日本でよくある「ポイント」機能は入ってません。
モデルのフォーク
Oscar 標準機能を延長(継承)したい場合、fork という機能を使います。
まず、プロジェクト直下に oscar_fork ( とか myapps とか ) といったディレクトリを作り、
$ ./manage.py oscar_fork_app catalogue oscar_fork
を実行すると、oscar_fork/catalogue が出来ます。
このフォークモジュールでは、
- モデルの拡張
- ビューの拡張、無効化
- URL の追加、無効化
などが行えます。
モデルフィールドの追加例
例えば、catalogue.Product に、thumbnail_url フィールドを追加したいなら
from django.db import models
from oscar.apps.catalogue.abstract_models import AbstractProduct
class Product(AbstractProduct):
image_url = models.URLField(default='', blank=True)
thumbnail_url = models.URLField(default='', blank=True)
from oscar.apps.catalogue.models import * # noqa
として、INSTALLED_APPS に
INSTALLED_APPS = [
'django.contrib.admin',
....
]
INSTALLED_APPS += get_core_apps([
'oscar_fork.catalogue',
])
と書くことで、INSTALLED_APPS を延長します。
./manage.py makemigrations
でマイグレーションファイルが出来るので、./manage.py migrate
でスキーママイグレーションします。
ビューの拡張
カート (checkout) のビューを拡張したいなら
$ ./manage.py oscar_fork_app checkout oscar_fork
してから
from oscar.apps.checkout import views
class IndexView(views.IndexView):
def get(self, request, *args, **kwargs):
# get処理...
のように書けます。
INSTALLED_APPS にも追加
INSTALLED_APPS += get_core_apps([
'oscar_fork.catalogue',
'oscar_fork.checkout',
])
URLs の追加、編集
URLs を追加したり無効化したい場合は、app.py を書きます。
$ ./manage.py oscar_fork_app customer oscar_fork
from oscar.apps.customer import app
class CustomerApplication(app.CustomerApplication):
def get_urls(self):
urls = [
# Login, logout and register doesn't require login
url(r'^login/$', self.login_view.as_view(), name='login'),
url(r'^logout/$', self.logout_view.as_view(), name='logout'),
# url(r'^register/$', self.register_view.as_view(),
# name='register'),
register のURL をコメントアウトすることで、無効化したりできます。
INSTALLED_APPS にも追加。(詳細略)
日本国内向けに使うにはどうしたら良いか
まずは、Djangoの settings を日本向けに。
LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'
Oscar 特有設定を日本向けに
OSCAR_DEFAULT_CURRENCY = 'JPY'
OSCAR_CURRENCY_FORMAT = '¤#,##0'
登録国を日本のみに
$ ./manage.py oscar_populate_countries
で、国モデル ( address_country ) に249カ国の初期データが入ります。
これの、is_shipping_country を Japan のみ 1に、他は 0 にすると、住所登録時に国名選択が出てこなくなります (日本がデフォルトになる)。
Django シェルからでも、SQL でも。
mysql> UPDATE address_country SET is_shipping_country = 0 WHERE printable_name != 'Japan';
POファイルをひたすら書いて翻訳
How do I translate Oscar? — django-oscar 0.5 documentation
https://django-oscar.readthedocs.org/en/releases-0.5/howto/how_do_i_translate_oscar.html
ここを参考に
$ mkdir locale i18n
$ ln -s $PATH_TO_OSCAR i18n/oscar
$ ./manage.py makemessages --symlinks --locale=ja
(うまくいかない場合、多分 gettext がインストールされてないとか。僕の場合(mac)は brew install gettext --force が必要でした)
locale/ja/LC_MESSAGES/django.po を編集
msgid "email address"
msgstr "メールアドレス"
msgid "First name"
msgstr "姓"
msgid "Last name"
msgstr "名"
msgid "View basket"
msgstr "カートを見る"
msgid "Basket total"
msgstr "合計金額"
msgid "Basket total:"
msgstr "合計金額:"
msgid "In stock"
msgstr "在庫有り"
msgid "Shipping"
msgstr "送料"
msgid "Total"
msgstr "小計"
msgid "Order total"
msgstr "合計金額"
msgid "State/County"
msgstr "都道府県"
msgid "City"
msgstr "市区町村"
msgid "First line of address"
msgstr "町名/字/番地"
msgid "Second line of address"
msgstr "建物/部屋番号"
...
(※ First name = 姓, Last name = 名 は意図して書いており、間違ったわけではないです。こっちのほうが僕は使いやすいので。)
poファイルを書いたら、
$ ./manage.py compilemessages
を実行し mo ファイルを作っておく。これでサイトが日本語表示になります。
税率の変更
デフォルトは、固定税率 0% になってます。日本は消費税 8% なので変更が必要です。
税率は、partner/strategy.py にあるので
$ ./manage.py oscar_fork_app partner oscar_fork
してから、
from decimal import Decimal as D
from oscar.apps.partner import strategy
class Selector(object):
def strategy(self, request=None, user=None, **kwargs):
return JPStrategy()
class JP(strategy.FixedRateTax):
rate = D('0.08')
exponent = D('1.')
def pricing_policy(self, product, stockrecord):
if not stockrecord:
return prices.Unavailable()
rate = self.get_rate(product, stockrecord)
exponent = self.get_exponent(stockrecord)
tax = (stockrecord.price_excl_tax * rate).quantize(
exponent, rounding=ROUND_DOWN)
return prices.TaxInclusiveFixedPrice(
currency=stockrecord.price_currency,
excl_tax=stockrecord.price_excl_tax,
tax=tax)
def parent_pricing_policy(self, product, children_stock):
stockrecords = [x[1] for x in children_stock if x[1] is not None]
if not stockrecords:
return prices.Unavailable()
# We take price from first record
stockrecord = stockrecords[0]
rate = self.get_rate(product, stockrecord)
exponent = self.get_exponent(stockrecord)
tax = (stockrecord.price_excl_tax * rate).quantize(
exponent, rounding=ROUND_DOWN)
return prices.FixedPrice(
currency=stockrecord.price_currency,
excl_tax=stockrecord.price_excl_tax,
tax=tax)
class JPStrategy(strategy.UseFirstStockRecord, strategy.StockRequired,
JP, strategy.Structured):
pass
このように書けます。
※ JPクラスが長ったらしくて少し冗長に見えると思います。
以前は、この JP クラスを
class JP(strategy.FixedRateTax):
rate = D('0.08')
こう書いていました。この場合、金額の小数点以下がそのまま扱われます。
例えば、税抜70円の商品の場合、税込75.6円として内部で計算されます。小数点以下を考慮します。
これだと、私のサービスだと不都合がありましたので、以下のように変更した所
class JP(strategy.FixedRateTax):
rate = D('0.08')
exponent = D('1.')
小数点以下は四捨五入となります。税抜70円商品の場合、税込76円になります。
今回は、小数点以下は切り捨てとしたかったので、pricing_policy
, parent_pricing_policy
両方のメソッドをオーバーライドし、Decimal
の quantize
メソッドの引数に rounding=ROUND_DOWN
を入れるようにしました。
ただ 端数切捨てにしたかっただけでメソッドをまるごとオーバーライドするのはちょっとエレガントではないですね。なんか良い方法ないでしょうか。
decimal.setcontext
で実現できるんでしょうかね。他に影響ありそうで怖くてできません。
例によって INSTALLED_APPS に追加
INSTALLED_APPS += get_core_apps([
...
'oscar_fork.partner',
])
ロジックで分岐など書けるので、複雑な処理も書けます。関税など。
これで、ほぼ日本で使えるようになったと思います。
あとはひたすらテンプレート作成と翻訳ですね。
トップページはどこ?
ちなみに。トップページは promotions の中にあります。
ビューは oscar.apps.promotions.views.HomeView
、
テンプレートは promotions/home.html
です。探すと少し時間がかかるので、書き記しておきます。
その他必要な機能
決済モジュール
paypal は、 django-oscar-paypal
モジュールがあって比較的簡単に使えるらしい。エクスプレスチェックアウト と payflow 対応。多分、エクスプレスチェックアウトはリンクが出るだけ。
僕は webpay でやろうと思ったので自分で書きました。
payment.views.PaymentDetailsView
の handle_payment
をオーバーライドして書くことになります。