akiyoko blog

akiyoko の IT技術系ブログです

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」と紹介されています。