Mezzanine REST API を使ってみる


Mezzanine には、REST API で Blog 投稿可能な、Plugin が存在します。

この Plugin は Client 側と、Service 側 package が存在します。
これらを install して、API を叩いてみた結果を記載します。


0. 前提

以下の環境で実行しています。

  • OS
    CentOS release 6.7 (Final)

  • Python Version
    Python 2.7.8

  • Package (必要そうなものだけ抜粋)
    Django (1.10)
    Mezzanine (4.2.0)


1. Service の インストール

Mezzanine REST API - RESTful web API for Mezzanine CMS の、
Existing project の手順を踏んでインストールしていきます。

1. pip インストール

pip install -U mezzanine-api
---------------------------------------------
  Found existing installation: Django 1.10.2
    Uninstalling Django-1.10.2:
      Successfully uninstalled Django-1.10.2
  Found existing installation: oauthlib 2.0.0
    Uninstalling oauthlib-2.0.0:
      Successfully uninstalled oauthlib-2.0.0
  Found existing installation: requests 2.11.1
    Uninstalling requests-2.11.1:
      Successfully uninstalled requests-2.11.1
  Found existing installation: Pillow 3.4.1
    Uninstalling Pillow-3.4.1:
      Successfully uninstalled Pillow-3.4.1
  Found existing installation: pytz 2016.7
    Uninstalling pytz-2016.7:
      Successfully uninstalled pytz-2016.7
  Found existing installation: tzlocal 1.2.2
    Uninstalling tzlocal-1.2.2:
      Successfully uninstalled tzlocal-1.2.2
  Found existing installation: bleach 1.4.3
    Uninstalling bleach-1.4.3:
      Successfully uninstalled bleach-1.4.3
  Found existing installation: future 0.15.2
    Uninstalling future-0.15.2:
      Successfully uninstalled future-0.15.2
Successfully installed PyYAML-3.12 bleach-1.5.0 django-1.10.4 django-braces-1.10.0 django-filter-1.0.1 django-oauth-toolkit-0.11.0 django-rest-swagger-0.3.10 djangorestframework-3.5.3 future-0.16.0 mezzanine-api-0.5.0 oauthlib-1.1.2 pillow-3.4.2 pytz-2016.10 requests-2.12.4 tzlocal-1.3
--------------------------------------------
依存関係が多いらしく、package がいろいろインストールされました。
そして、Django の Version が 上がっていて嫌な臭いがしますが、進めます。

2. settings.py に Application を追加

記載されている通り、mezzanine_apirest_frameworkrest_framework_swaggeroauth2_provider を追加します。

INSTALLED_APPS = (
    'mezzanine_api',
    'rest_framework',
    'rest_framework_swagger',
    'oauth2_provider',
    ...
    )

3. settings.py の MIDDLEWARE_CLASSES の一番上に、mezzanine_api.middleware.ApiMiddleware を追加します。

MIDDLEWARE_CLASSES = (
    'mezzanine_api.middleware.ApiMiddleware',  
    ...
    )

4. LOCAL SETTINGS の直前に、 REST API SETTINGS を追加します。

#####################
# REST API SETTINGS #
#####################
try:
    from mezzanine_api.settings import *
except ImportError:
    pass

5. Mezzanine 4.1.0以上は、urls.py の 29行目に以下のコードを追加します。

version 3 から引き継いで使っているので、この辺りはだいぶずれています。
どこを指しているのかわからず、
if settings.USE_MODELTRANSLATION: の直後に追加しました。

if settings.USE_MODELTRANSLATION:
    urlpatterns += [
        url('^i18n/$', set_language, name='set_language'),
    ]

# REST API URLs
urlpatterns += [
    url("^api/", include("mezzanine_api.urls")),
]

6. Migration を実行

python2.7 manage.py migrate
----------------------------
Operations to perform:
  Apply all migrations: admin, auth, blog, conf, contenttypes, core, django_comments, forms, galleries, generic, oauth2_provider, pages, redirects, request, robots, sessions, sites, twitter
Running migrations:
  Applying auth.0008_alter_user_username_max_length... OK
  Applying oauth2_provider.0001_initial... OK
  Applying oauth2_provider.0002_08_updates... OK
  Applying oauth2_provider.0003_auto_20160316_1503... OK
  Applying oauth2_provider.0004_auto_20160525_1623... OK
----------------------------

7. HTTP サーバ再起動

service httpd restart

2. client_id、 client_secret の発行

インストール完了後、http://${yourdomain}/api/docs にアクセスすると、
以下の画面が表示されます。
Broken API docs

画面が出ています。
最初これで上手くインストールができたと喜んでいたのですが、
ブラウザの開発者コンソールを開くと、
おもいきり javascript error が出力されていたので、
collectstatic で 静的ファイルをコピーしました。

python2.7 manage.py collectstatic

collectstatic 実行後は、以下の通りの画面が表示されます。
API docs

インストール、設定がうまくいったようなので、 client_idclient_secret を発行します。

  1. API Docs の OAuth App Manager リンクをクリックします。
    OAuth App Manager

  2. UsernamePassword を入力し、Log in をクリックします。
    API Login

  3. Click here をクリックします。
    Click here

  4. Client SDK - Mezzanine REST API に従って、
    各項目を入力して、Save をクリックします。Form

    • Name
      Mezzanine Python Client と入力
    • Cient id
      自動生成されます。
    • Client secret
      自動生成されます。
    • Client Type Confidential を選択
    • Authorization grant type
      Authorization code を選択
    • Redirect urls
      https://httpbin.org/get

3. Client の インストール

続いて ClientRemote CLI - Mezzanine REST API の手順を踏んで、
インストールします。

1. pip インストール

pip install -U mezzanine-client

2. 設定

2. で発行した、client_idclient_secret と、通信先のURLを設定します。

  • 通信先 URL の設定

    mezzanine-cli config api_url https://www.monotalk.xyz/api
    --------------------------------------
        from configparser import ConfigParser as Parser, NoOptionError
    ImportError: No module named configparser
    --------------------------------------
    
    import エラーになったので、configparser を インストールします。
    pip install configparser
    -------------------------
    Installing collected packages: configparser
      Running setup.py install for configparser ... done
    Successfully installed configparser-3.5.0
    -------------------------
    
    再度、mezzanine-cli を実行します。
    mezzanine-cli config api_url https://www.monotalk.xyz/api
    -------------------------
    Configuration updated successfully.
    -------------------------
    
    成功しました。

  • client_id の設定

    mezzanine-cli config client_id ${id}
    -------------------------
    Configuration updated successfully.
    -------------------------
    

  • client_secret の設定

    mezzanine-cli config client_secret ${secret}
    -------------------------
    Configuration updated successfully.
    -------------------------
    


4. Client を使ってみる

1. mezzanine-cli posts list を実行します。

mezzanine-cli posts list
-----------------------------------------------------
Please click to authorize this app: https://monotalk.xyz/..........
Paste the authorization code (args > code) from your browser here: 
-----------------------------------------------------
出力される URL から、authorization code を発行します。

2. URLにアクセスして、Authorize をクリック。

AuthorizationJson 文字列がレスポンスとして取得できるので、code 値をterminal にコピーすると、実行されます。
が以下のエラーが発生して、client がエラーになりました。

__init__() got an unexpected keyword argument 'lookup_type'

調べたところ、django-filter の lookup_type というパラメータがなくなったことが原因のようです。
Service 側の コード上で使用しているところがあったので、lookup_type > lookup_expr に変更します。
Error in the docs for lookup expressions ? - Google グループ

  • /usr/local/lib/python2.7/site-packages/mezzanine_api/views.py を編集

    cd /usr/local/lib/python2.7/site-packages/mezzanine_api
    vi views.py
    

  • UserFilter

    class UserFilter(django_filters.FilterSet):
        """
        A class for filtering users.
        """
        #username = django_filters.CharFilter(name="username", lookup_type='istartswith')
        username = django_filters.CharFilter(name="username", lookup_expr='istartswith')
    
        class Meta:
            model = User
            fields = ['username']
    

  • PostFilter

    class PostFilter(django_filters.FilterSet):
        """
        A class for filtering blog posts.
        """
        category_id = django_filters.NumberFilter(name="categories__id")
        #category_name = django_filters.CharFilter(name="categories__title", lookup_type='contains')
        category_name = django_filters.CharFilter(name="categories__title", lookup_expr='contains')
        #category_slug = django_filters.CharFilter(name="categories__slug", lookup_type='exact')
        category_slug = django_filters.CharFilter(name="categories__slug", lookup_expr='exact')
        #tag = django_filters.CharFilter(name='keywords_string', lookup_type='contains')
        tag = django_filters.CharFilter(name='keywords_string', lookup_expr='contains')
        author_id = django_filters.NumberFilter(name="user__id")
        #author_name = django_filters.CharFilter(name="user__username", lookup_type='istartswith')
        author_name = django_filters.CharFilter(name="user__username", lookup_expr='istartswith')
        #date_min = django_filters.DateFilter(name='publish_date', lookup_type='gte')
        date_min = django_filters.DateFilter(name='publish_date', lookup_expr='gte')
        #date_max = django_filters.DateFilter(name='publish_date', lookup_type='lte')
        date_max = django_filters.DateFilter(name='publish_date', lookup_expr='lte')
    
        class Meta:
            model = Post
            fields = ['category_id', 'category_name', 'tag', 'author_id', 'author_name', 'date_min', 'date_max']
    

3. 再度mezzanine-cli posts list を実行します。

mezzanine-cli posts list
----------------

  File "/Library/Python/2.7/site-packages/mezzanine_client/cli.py", line 70, in posts_list
    print(fmt.format(id=post['id'], title=post['title'], url=post['url']))
UnicodeEncodeError: 'ascii' codec can't encode character u'\u3092' in position 22: ordinal not in range(128)
----------------
今度はclient 側でエラーです。
unicode で エラーのようなので、対象箇所に.encode('utf-8') を追加します。
def posts_list(offset, limit):
    api = get_client()
    published_posts = api.get_posts(offset=offset, limit=limit)

    click.echo(click.style('Published Posts', bold=True))
    fmt = '{id} <{title} {url}>'
    for post in published_posts:
        #print(fmt.format(id=post['id'], title=post['title'], url=post['url']))
        print(fmt.format(id=post['id'], title=post['title'].encode('utf-8'), url=post['url'].encode('utf-8')))

4. 再度mezzanine-cli posts list を実行します。

※やっと動きました。

mezzanine-cli posts list
----------------
Published Posts
159 <SonarQube SonarPython を 使って Python の静的解析をしてみる https://monotalk.xyz/blog/Try-static-analysis-of-Python-using-SonarPython/>
158 <SonarQube Javaプログラムの警告をOFFにする https://monotalk.xyz/blog/Turn-off-Java-program-warning-on-SonarQube/>
157 <OS X El Capitan に SonarQubeをインストール、日本語化までしてみる https://monotalk.xyz/blog/install-SonarQube-on-El-Capitan/>
156 <施設名から住所を検索するAPIとして、Google Place API を使う https://monotalk.xyz/blog/Use-the-Google-Place-API-as-a-facility-search-API/>
155 <PyCharm terminal からpythonスクリプトを実行できるようにする https://monotalk.xyz/blog/pycharm-terminal-%E3%81%8B%E3%82%89python%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E5%AE%9F%E8%A1%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B/>
154 <dropwizard の task で、 wicket の mount されたページのURL一覧を出力する https://monotalk.xyz/blog/dropwizard-%E3%81%AE-task-%E3%81%A7-wicket-%E3%81%AE-mount-%E3%81%95%E3%82%8C%E3%81%9F%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AEurl%E4%B8%80%E8%A6%A7%E3%82%92%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B/>
153 <Wicket URLからバージョン番号 [?0] を消したい https://monotalk.xyz/blog/delete-version-number-from-url-on-wicket/>
152 <Django BooleanField で Null許可する https://monotalk.xyz/blog/BooleanFiled-Null-True-on-django/>
151 <django、DBシーケンスを作成、使用する https://monotalk.xyz/blog/genelate-db-sequence-on-django/>
150 <Django-extentions の admin_generator で admin.py を自動生成する https://monotalk.xyz/blog/genelate-admin-on-django/>
----------------

5. mezzanine-cli posts getを実行します。

  • command

    mezzanine-cli posts get 3
    

  • output

    {
        "updated": "2016-05-17T00:58:15.116627Z", 
        "title": "djangoのsettings.py内で、LogHandler StreamHandlerのログ出力先を指定する", 
        "url": "https://monotalk.xyz/blog/mezzanine-pagedown/", 
        "short_url": "/blog/mezzanine-pagedown/", 
        "tags": "", 
        "excerpt": "........", 
        "allow_comments": true, 
        "comments": [], 
        "slug": "mezzanine-pagedown", 
        "content": "........", 
        "publish_date": "2015-04-28T09:30:23Z", 
        "user": {
            "username": "monotalk", 
            "first_name": "kem", 
            "last_name": "", 
            "email": null, 
            "is_staff": true, 
            "id": 2
        }, 
        "featured_image": "", 
        "comments_count": 0, 
        "id": 3, 
        "categories": [
            {
                "slug": "django", 
                "id": 6, 
                "title": "django"
            }, 
            {
                "slug": "python", 
                "id": 1, 
                "title": "python"
            }
        ]
    }
    

6. mezzanine-cli posts create を実行します。

  • command

    mezzanine-cli posts create \
      --title='Test Post from API Client' \
      --content='Test' \
      --categories='Test,Fun'
    

  • output

    HTTP 401 Unauthorized
    

401エラーが発生しました。どうも認証が上手く通っていないらしく、
それで動作しないようです。
ローカル環境だと、上手くいくのですが、リモートに対する POST リクエストが上手くいきません。
半日ほど費やしましたが、WSGIアプリケーションからAuthorizationヘッダーを参照する - スコトプリゴニエフスク通信 が原因でした。。
Apache の wsgi の設定ファイルに以下を追記します。

WSGIPassAuthorization On

7. mezzanine-cli posts create を実行します。

  • command

    mezzanine-cli posts create \
      --title='Test Post from API Client' \
      --content='Test' \
      --categories='Test,Fun'
    

  • output

    Blog post successfully published with ID #162 
    

登録できました。


5. API のパーミッションについて

mezzanine-api/views.py at master · gcushen/mezzanine-api
で、permission_classesで、各APIのパーミッションが定義されています。

permission_classes = [IsAdminOrReadOnly]

デフォルトだと、認証なしでも閲覧できる API もありますので、
利用用途によっては、内容を見直したほうがいいかもしれません。
常識的にまあそうだよねというパーミッション指定は行われていますが、
気にするのであればPermissions - Django REST framework
を参考に変更すればよいかと思います。

以上です。

コメント