2022年5月18日 星期三

CKAN Route(2.9): add_ckan_admin_tab

https://github.com/ckan/ckan/blob/2.9/ckanext/example_iconfigurer/plugin.py

參考 CKAN Extensions Tutorial(v2.9):一個空的 CKAN extension,建立 ckanext-example_iconfigurer 擴充套件。

建立:ckanext-example_iconfigurer  

  1. 進入Python Virtual Environment:
    . /usr/lib/ckan/default/bin/activate
  2. 切換目錄
    cd /usr/lib/ckan/default/src 
  3. 執行 ckan generate extension,輸入:ckanext-example_iconfigurer

撰寫 plugin 類別:ExampleIConfigurerPlugin

修改 ckanext-example_iconfigurer/ckanext/example_iconfigurer/plugin.py。參考完整程式,主要內容如下:

略...
import ckanext.example_iconfigurer.blueprint as blueprint

class ExampleIConfigurerPlugin(plugins.SingletonPlugin):
    略...

    plugins.implements(plugins.IConfigurer)
    plugins.implements(plugins.IBlueprint)

    # IConfigurer

    def update_config(self, config):
        # Add extension templates directory

        toolkit.add_template_directory(config, u'templates')
        # Add a new ckan-admin tabs for our extension
        toolkit.add_ckan_admin_tab(
            config, u'example_iconfigurer.config_one',
            u'My First Custom Config Tab'
        )
        toolkit.add_ckan_admin_tab(
            config, u'example_iconfigurer.config_two',
            u'My Second Custom Config Tab'
        )

撰寫 blueprint.py

參考完整程式,主要內容如下:

# encoding: utf-8

import ckan.lib.base as base
import ckan.lib.helpers as helpers
from flask import Blueprint
render = base.render

example_iconfigurer = Blueprint(u'example_iconfigurer', __name__)


def config_one():
    u'''Render the config template with the first custom title.'''

    return render(
        u'admin/myext_config.html',
        extra_vars={u'title': u'Custom Config Page A'}
    )


def config_two():
    u'''Render the config template with the second custom title.'''
    return render(
        u'admin/myext_config.html',
        extra_vars={u'title': u'Custom Config Page B'}
    )


def build_extra_admin_nav():
    u'''Return results of helpers.build_extra_admin_nav for testing.'''
    return helpers.build_extra_admin_nav()


example_iconfigurer.add_url_rule(
    u'/ckan-admin/myext_config_one', view_func=config_one
)
example_iconfigurer.add_url_rule(
    u'/ckan-admin/myext_config_two', view_func=config_two
)
example_iconfigurer.add_url_rule(
    u'/build_extra_admin_nav', view_func=build_extra_admin_nav
)

修改 setup.py:設定 entry_points

修改 ckanext-example_iconfigurer/setup.py 的 entry_points:

entry_points='''
        [ckan.plugins] 
        example_iconfigurer=ckanext.example_iconfigurer.plugin:ExampleIConfigurerPlugin

修改 ckan.ini:啟用 plugin

修改 /etc/ckan/default/ckan.ini。CKAN 的 extension plugins 必須加到 CKAN 設定檔的 ckan.plugins 中,CKAN 才會呼叫。

ckan.plugins = stats text_view 略... example_iconfigurer

安裝

  1. 進入Python Virtual Environment: 
    . /usr/lib/ckan/default/bin/activate
  2. 切換目錄:
    cd /usr/lib/ckan/default/src/example_iconfigurer
  3. 安裝 Plugin: 
    python setup.py develop
  4. 重啟 CKAN 服務:
    sudo supervisorctl restart ckan-uwsgi:*

測試

瀏覽 http://CKAN web site]/ckan-admin/ 可看到兩個新增的管理頁籤。




2022年5月17日 星期二

CKAN Route(2.9): Flask IBlueprint

參考 CKAN Extensions Tutorial(v2.9):一個空的 CKAN extension,建立 ckanext-example_flask_iblueprint 擴充套件。

建立:ckanext-example_flask_iblueprint  

  1. 進入Python Virtual Environment:
    . /usr/lib/ckan/default/bin/activate
  2. 切換目錄
    cd /usr/lib/ckan/default/src 
  3. 執行 ckan generate extension,輸入:ckanext-example_flask_iblueprint

撰寫 plugin 類別:ExampleFlaskIBlueprintPlugin

修改 ckanext-example_flask_iblueprint/ckanext/example_flask_iblueprint/plugin.py。參考完整程式,主要內容如下:

class ExampleFlaskIBlueprintPlugin(p.SingletonPlugin):
    u'''
    An example IBlueprint plugin to demonstrate Flask routing from an
    extension.
    '''
    p.implements(p.IBlueprint)
    p.implements(p.IConfigurer)

    # IConfigurer

    def update_config(self, config):
        # Add extension templates directory
        p.toolkit.add_template_directory(config, u'templates')

    def get_blueprint(self):
        u'''Return a Flask Blueprint object to be registered by the app.'''

        # Create Blueprint for plugin
        blueprint = Blueprint(self.name, self.__module__)
        blueprint.template_folder = u'templates'
        # Add plugin url rules to Blueprint object
        rules = [
            (u'/hello_plugin', u'hello_plugin', hello_plugin),
            (u'/', u'home', override_flask_home),
            (u'/helper_not_here', u'helper_not_here', helper_not_here),
            (u'/helper', u'helper_here', helper_here),
            (u'/flask_request', u'flask_request', flask_request),
        ]
        for rule in rules:
            blueprint.add_url_rule(*rule)

        return blueprint

修改 setup.py:設定 entry_points

修改 ckanext-example_flask_iblueprint/setup.py 的 entry_points:

entry_points='''
        [ckan.plugins] 
        example_flask_iblueprint=ckanext.example_flask_iblueprint.plugin:ExampleFlaskIBlueprintPlugin

修改 ckan.ini:啟用 plugin

修改 /etc/ckan/default/ckan.ini。CKAN 的 extension plugins 必須加到 CKAN 設定檔的 ckan.plugins 中,CKAN 才會呼叫。

ckan.plugins = stats text_view 略... example_flask_iblueprint

安裝

  1. 進入Python Virtual Environment: 
    . /usr/lib/ckan/default/bin/activate
  2. 切換目錄:
    cd /usr/lib/ckan/default/src/example_flask_iblueprint
  3. 安裝 Plugin: 
    python setup.py develop
  4. 重啟 CKAN 服務:
    sudo supervisorctl restart ckan-uwsgi:*

測試

瀏覽 http://[CKAN web site] 下的 /, /hello_plugin, /helper, /helper_not_here, /flask_request 可看到對應的程式內容。

2022年5月12日 星期四

CKAN JavaScript Tutorial 2(v2.9): Webassets

http://docs.ckan.org/en/2.9/theming/webassets.html

CSS 檔等靜態檔案有兩種方式加入:簡單的 extra_public_paths 作法或是 Webassets。但 JavaScript module 就只能透過 Webassets。

Webasset 的好處是在 production 版中會自動最小化檔案、快取並打包/bundling,以減少下載時間,載入指定相依檔⋯⋯。

CKAN 只有 *.js、*.css 會以 Webasset 資源處理,其它的靜態檔(圖檔、PDF)要透過 extra_public_paths 方式處理。

建立: ckanext-example_theme

延續 CKAN Theming Tutorial 1(v2.9): 靜態檔案(圖片與CSS) 往下做。

建立 assets 目錄

把 example_theme.css 改放到 assets 目錄下:

ckanext-example_theme/
  ckanext/
    example_theme/
      public/
        promoted-image.jpg
      assets/
        example_theme.css
        webassets.yml

另外再加一個 webassets.yml ,內容如下:

example_theme:
  output: example_theme/example_theme.css
  contents:
    - example_theme.css

修改 plugin.py

編輯 ckanext-example_theme/ckanext/example_theme/plugin.py ,在 update_config() 中呼叫 add_resource() 向 CKAN 註冊 assets 目錄,如下:

# encoding: utf-8

import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit

def most_popular_groups():
    略⋯⋯

class ExampleThemePlugin(plugins.SingletonPlugin):
    略⋯⋯

    def update_config(self, config):
        略⋯⋯

        # Register this plugin's assets directory with CKAN.
        # Here, 'assets' is the path to the webassets directory
        # (relative to this plugin.py file), and 'example_theme' is the name
        # that we'll use to refer to this assets directory from CKAN
        # templates.
        toolkit.add_resource('assets', 'example_theme')
  • toolkit.add_resource 的兩個參數:
    • assets: webassets 所在目錄(相對於plugin.py的路徑)
    • example_theme: 在 CKAN template 中存取此 assets 目錄的名稱

在 templates 中使用 {% asset %}

編輯 ckanext-example_theme/ckanext/example_theme/templates/base.html,在其中以 Jinja2 的{% asset %} 取代<link>  匯入檔案,如下:

{% ckan_extends %}

{% block styles %}
  {{ super() }}

  {# Import example_theme.css using Webassets.  'example_theme/' is
    the name that the example_theme/fanstatic directory was registered
    with when the toolkit.add_resource() function was called.
    'example_theme' is the name to the Webassets bundle file,
    registered inside webassets.yml file. #}
  {% asset 'example_theme/example_theme' %}

{% endblock %}

所有 template 都可用 {% asset %},Webassets 會加入需要的 <style> 、 <script>  來 include CSS 及JavaScript檔案,但最好把他們集中在 Jinja2 的  styles、scripts block.

Assets 不會在 {% asset %} 出現那行被 include。

用一個設定檔設定 Webassets 如何處理每個 asset 檔(是否打包/bundle,include的順序,include 檔要放在網頁的最前面還是最後面,檔案間的相依性...),參考:Assets

安裝

  1. 進入Python Virtual Environment: 
    . /usr/lib/ckan/default/bin/activate
  2. 切換目錄:
    cd /usr/lib/ckan/default/src/ckanext-example_theme
  3. 安裝 Plugin: 
    python setup.py develop
  4. 重啟 CKAN 服務:
    (production版) sudo supervisorctl restart ckan-uwsgi:*
    (develop版) ckan -c /etc/ckan/default/ckan.ini run

測試

檢視 http://[CKAN Website IP] 的網頁原始碼,在HTML的 <head>...</head> 可發現:

<link href="/webassets/webassets-external/2dce0ddda2882168f0c12c639a912e24_example_theme.css" rel="stylesheet"/>

2022年5月8日 星期日

CAKN 偵錯: flask_debugtoolbar

  1. 啟動 Python 虛擬環境
    . /usr/lib/ckan/default/bin/activate
  2. 安裝 flask_debugtoolbar
    pip install flask_debugtoolbar
  3. 開啟偵錯模式
    vi /etc/ckan/default/ckan.ini
    debug = true
  4. 重啟 CKAN
    sudo supervisorctl restart ckan-uwsgi:*
  • 若有錯檢視 uwsgi.ERR
    cat /etc/ckan/default/uwsgi.ERR


2022年5月6日 星期五

CKAN Templates Tutorial 9(v2.9): 存取自訂的 config 設定

http://docs.ckan.org/en/2.9/theming/templates.html#accessing-custom-config-settings-from-templates

template 裡的 app_globals 不是所有 CKAN config 設定都能存取,自訂的 config 設定無法取得,但透過 helper function 就可以。

參考:Using custom config settings in extensions

延續 上個 CKAN templates CSS tutorial

新增自訂 config 設定

如下 show_most_popular_groups 設定,決定首頁是否顯示最受歡迎群組。

修改 extension 根目錄下的 plugin.py,新增 helper function 存取自訂的 show_most_popular_groups 設定:

# encoding: utf-8


import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit
from ckan.common import config


def show_most_popular_groups():
    '''Return the value of the most_popular_groups config setting.

    To enable showing the most popular groups, add this line to the
    [app:main] section of your CKAN config file::

      ckan.example_theme.show_most_popular_groups = True

    Returns ``False`` by default, if the setting is not in the config file.

    :rtype: bool

    '''
    value = config.get('ckan.example_theme.show_most_popular_groups', False)
    value = toolkit.asbool(value)
    return value

略⋯⋯

class ExampleThemePlugin(plugins.SingletonPlugin):
    略⋯⋯

    def get_helpers(self):
        '''Register the most_popular_groups() function above as a template
        helper function.

        '''
        # Template helper function names should begin with the name of the
        # extension they belong to, to avoid clashing with functions from
        # other extensions.
        return {'example_theme_most_popular_groups': most_popular_groups,
                'example_theme_show_most_popular_groups':
                show_most_popular_groups,
                }

extension 加入的 config 設定名稱要以 extension 名稱開頭,以避免與其他 extension 或 CKAN 內建設定衝突(參考 Avoid name clashes)。

在 layout1.html 中呼叫自訂 helper function

修改 templates/home/layout1.html 如下:

{% ckan_extends %}

略⋯⋯

{# Show the most popular groups if the show_most_popular_groups config setting
   is True, otherwise call the super block. #}
{% block featured_organization %}
<p>example_theme_show_most_popular_groups: [{{h.example_theme_show_most_popular_groups()}}]</p>
  {% if h.example_theme_show_most_popular_groups() %}
    {% snippet 'snippets/example_theme_most_popular_groups.html' %}
  {% else %}
    {{ super() }}
  {% endif %}
{% endblock %}

修改 ckan.ini 設定

/etc/ckan/default/ckan.ini

[app:main]
## example_theme
ckan.example_theme.show_most_popular_groups = False

安裝

  1. 進入Python Virtual Environment: 
    . /usr/lib/ckan/default/bin/activate
  2. 切換目錄:
    cd /usr/lib/ckan/default/src/ckanext-example_theme
  3. 安裝 Plugin: 
    python setup.py develop
  4. 重啟 CKAN 服務:
    (production版) sudo supervisorctl restart ckan-uwsgi:*
    (develop版) ckan -c /etc/ckan/default/ckan.ini run


CKAN Templates Tutorial 8(v2.9): CSS class

http://docs.ckan.org/en/2.9/theming/templates.html#html-tags-and-css-classes

CKAN 使用的 CSS class 來自:

  1. Bootstrap 3.3.7
    這個版本 Bootstrap 的 HTML, CSS, JavaScript 都可在 CKAN 中使用
  2. CKAN 的 development primer page
    CKAN 網站下的 /testing/primer

調整 snippet 裡的 CSS class

修改 ckanext-example_theme/ckanext/example_theme/templates/snippets/example_theme_most_popular_groups.html 如下:

{#

Renders a list of the site's most popular groups.

groups - the list of groups to render

#}
<div class="box">
  <header class="module-heading">
    <h3>Most popular groups</h3>
  </header>
  <section class="module-content">
    <ul class="unstyled">
      {% for group in groups %}
      <li>
        <a href="{{ h.url_for('group_read', action='read', id=group.name) }}">
          <h3>{{ group.display_name }}</h3>
        </a>
        {% if group.description %}
          <p>
            {{ h.markdown_extract(group.description, extract_length=80) }}
          </p>
        {% else %}
          <p>{{ _('This group has no description') }}</p>
        {% endif %}
        {% if group.package_count %}
          <strong>{{ ungettext('{num} Dataset', '{num} Datasets', group.package_count).format(num=group.package_count) }}</strong>
        {% else %}
          <span>{{ _('0 Datasets') }}</span>
        {% endif %}
      </li>
    {% endfor %}
 </ul>
  <section>
</div>

以上加入:

  • 把程式碼放進
    • <div class="box">
    • <header class="module-heading">
    • <section class="module-content">
  • 用 Bootstrap 的 class="unstyled" 移除 <ul> 前的黑點

調整 layout1.html 的 CSS class

修改 ckanext-example_theme/ckanext/example_theme/templates/home/layout1.html 如下:

{% ckan_extends %}

{% block featured_group %}
  <div class="box">
    <header class="module-heading">
      <h3>Recent activity</h3>
    </header>
    <div class="module-content">
    <ul>
        {% for stream in h.recently_changed_packages_activity_stream(limit=4) %}
            <li>stream id: {{ stream.id }}</li>
        {% endfor %}
    </ul>
    </div>
  </div>

{% endblock %}

{% block featured_organization %}
  {% snippet 'snippets/example_theme_most_popular_groups.html',
             groups=h.example_theme_most_popular_groups()  %}
{% endblock %}


重新檢視網頁:






CKAN Templates Tutorial 7(v2.9): 自訂 snippet

http://docs.ckan.org/en/2.9/theming/templates.html#adding-your-own-template-snippets

延續 上個 CKAN templates snippet tutorial,plugin 只要在它的 templates/snippets 目錄下加檔案即可自訂 snippet。

以下撰寫一個 snippet 來顯示最受歡迎群組。


在 templates/snippets 目錄下新增 snippet

建立目錄 ckanext-example_theme/ckanext/example_theme/templates/snippets,並在其下加入一個檔案:example_theme_most_popular_groups.html ,內容如下:

{#

Renders a list of the site's most popular groups.

groups - the list of groups to render

#}
<h3>熱門群組(自訂snippet)</h3>
<ul>
  {% for group in groups %}
    <li>
      <a href="{{ h.url_for('group_read', action='read', id=group.name) }}">
        <h3>{{ group.display_name }}</h3>
      </a>
      {% if group.description %}
        <p>
          {{ h.markdown_extract(group.description, extract_length=80) }}
        </p>
      {% else %}
        <p>{{ _('This group has no description') }}</p>
      {% endif %}
      {% if group.package_count %}
        <strong>{{ ungettext('{num} Dataset', '{num} Datasets', group.package_count).format(num=group.package_count) }}</strong>
      {% else %}
        <span>{{ _('0 Datasets') }}</span>
      {% endif %}
    </li>
  {% endfor %}
</ul>

  • snippet 檔最開頭需以 docstring 描述需傳入的參數。在這個例子裡是:groups。
  • 呼叫 url_for() 為每個 group 名稱產生超連結至群組頁面
  • 若群組有 description 呼叫 markdown_extract() 產生其內容,若無則呼叫 _() 把 'This group has no description' 訊息翻譯使用者瀏覽器對應的語言。
  • 呼叫 ungettext() 顯示群組的資料集數(翻譯成對應的複數表示方式)。
  • 群組屬性/group attributes 如: {{ group.name }}, {{ group.display_name }}, {{ group.description }}, {{ group.package_count }},及其他 CKAN 物件(packages/datasets, organizations, users…) 屬性可透過 CKAN API 讀取,如 group_show()。


修改 templates/home/layout1.html

如下:

{% ckan_extends %}

{% block featured_group %}
  {{ h.recently_changed_packages_activity_stream(limit=4) }}
{% endblock %}

{% block featured_organization %}
  {% snippet 'snippets/example_theme_most_popular_groups.html',
             groups=h.example_theme_most_popular_groups()  %}
{% endblock %}

重啟 CKAN,再次瀏覽網頁可看到:

覆寫預設 snippet

若 plugin 加入的 snippet 名稱與 CKAN 預設 snippet 同名,則 plugin 的 snippet 會覆寫預設 snippet,無論是否有使用它。

若兩個 plugins 有同名 snippet 便會互相覆蓋。為避免這個問題,snippet 檔名最好以它的 extension 名稱開頭,如:snippets/example_theme_*.html。

snippet 「不能」存取 template 全域環境變數 c ( Variables and functions available to templates). 但可以存取:

  • h
  • app_globals
  • request
  • 其他父 template 透過 {% snippet %} 呼叫時傳入的參數

CKAN Templates Tutorial 6(v2.9): snippet

http://docs.ckan.org/en/2.9/theming/templates.html#template-snippets

Template snippets 是一小段/snippet template code,如同 helper functions,可在 template 裡呼叫。CKAN 在 ckan/templates/ 目錄下,可看到如:ckan/templates/snippets/ 及 ckan/templates/package/snippets/。

如下 ckan/templates/group/snippets/group_list.html 這個 snippet,可協助很好地顯示 group 清單。它裡面又呼叫了另一段 snippet- group/snippets/group_item.html:

{#
Display a grid of group items.

groups - A list of groups.

Example:

    {% snippet "group/snippets/group_list.html" %}

#}
{% block group_list %}
<ul class="media-grid" data-module="media-grid">
{% block group_list_inner %}
  {% for group in groups %}
    {% snippet "group/snippets/group_item.html", group=group, position=loop.index %}
  {% endfor %}
  {% endblock %}
</ul>
{% endblock %}


修改 layout1.html

ckanext-example_theme/ckanext/example_theme/templates/home/layout1.html:

⋯⋯略

{% block featured_organization %}

  <h3>熱門群組</h3>
  {# Call the group_list.html snippet. #}
  {% snippet 'group/snippets/group_list.html',
             groups=h.example_theme_most_popular_groups() %}

{% endblock %}

如上程式,傳了兩個參數給 {% snippet %} tag:

  • 'group/snippets/group_list.html'
    要呼叫的 template snippet
  • groups=h.example_theme_most_popular_groups()

在 snippet 檔名後面,可傳入任意個參數給 snippet 裡的程式,變成 snippet 裡的全域變數。每個 snippet 檔應該在最前面的 docstring 註解它需要傳入那些參數。

更新後,首頁右下區塊結果如下:




2022年5月5日 星期四

CKAN Templates Tutorial 5(v2.9): Template helper functions

http://docs.ckan.org/en/2.9/theming/templates.html#template-helper-functions

若要讀出 CKAN 裡的資訊,template helper function 是方法之一。

內建的 template helper functions

延續 上個 CKAN templates Tutorial,將 example_theme 底下的 templates/home/layout1.html 修改如下:

{% ckan_extends %}

{% block featured_group %}
<p>recently_changed_packages_activity_stream</p>
    <ul>
        {% for stream in h.recently_changed_packages_activity_stream(limit=4) %}
            <li>stream id: {{ stream.id }}</li>
        {% endfor %}
    </ul>
{% endblock %}

以 h 開頭的 h.recently_changed_packages_activity_stream,便是 template helper function,可讀出最近資料集(package)的更新資訊 ,結果如下:

在 templates 裡可透過 h 全域變數呼叫 ckan.lib.helpers 底下的 template helper function。參考:Template helper functions reference

自訂 template helper functions

實作 ckan.plugins.interfaces.ITemplateHelpers,列出最受歡迎的 group。

修改 plugin.py 

import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit

def most_popular_groups():
    '''Return a sorted list of the groups with the most datasets.'''

    # Get a list of all the site's groups from CKAN, sorted by number of
    # datasets.
    groups = toolkit.get_action('group_list')(
        data_dict={'sort': 'package_count desc', 'all_fields': True})

    # Truncate the list to the 10 most popular groups only.
    groups = groups[:10]

    return groups


class ExampleThemePlugin(plugins.SingletonPlugin):
    '''An example theme plugin.
    '''
    plugins.implements(plugins.IConfigurer)

    # Declare that this plugin will implement ITemplateHelpers.
    plugins.implements(plugins.ITemplateHelpers)

    def update_config(self, config):

        # Add this plugin's templates dir to CKAN's extra_template_paths, so
        # that CKAN will use this plugin's custom templates.
        toolkit.add_template_directory(config, 'templates')

    def get_helpers(self):
        '''Register the most_popular_groups() function above as a template
        helper function.
        '''
        # Template helper function names should begin with the name of the
        # extension they belong to, to avoid clashing with functions from
        # other extensions.
        return {'example_theme_most_popular_groups': most_popular_groups}

修改 layout1.html

修改 ckanext-example_theme/ckanext/example_theme/templates/home/layout1.html 如下:

{% ckan_extends %}

{% block featured_group %}
  {{ h.recently_changed_packages_activity_stream(limit=4) }}
{% endblock %}

{% block featured_organization %}

  {# Show a list of the site's most popular groups. #}
  <h3>Most popular groups</h3>
  <ul>
    {% for group in h.example_theme_most_popular_groups() %}
      <li>{{ group.display_name }}</li>
    {% endfor %}
  </ul>

{% endblock %}

測試

重新安裝 plugin

切換到 ckanext-example_theme 目錄,執行:

cd ckanext-example_theme
python setup.py develop

重啟 CKAN

  • 進入Python Virtual Environment:
    . /usr/lib/ckan/default/bin/activate
  • 重啟 CKAN 服務:
    (production版) sudo supervisorctl restart ckan-uwsgi:*
    (develop版) ckan -c /etc/ckan/default/ckan.ini run

結果如下:






CKAN v2.9 templates 4: {% ckan_extends %} {% block %} {{ super() }}

{% ckan_extends %}

用 Jinja 的 {% ckan_extends %} 可讓自訂 template 「擴充」預設內容,而不是取代它。

修改 index.html

修改上個 tutorial 產生的 home/index.html 如下:

{% ckan_extends %}

重新瀏覽 CKAN 首頁,自訂 index.html 裡的 {% ckan_extends %} 告訴 CKAN 先執行原本的 home/index.html。

{% block %}

Jinja templates 中可嵌入 block 讓其它 templates 取代其內容,如 CKAN 預設 /usr/lib/ckan/default/src/ckan/ckan/templates/home/layout1.html:


<div role="main">
  <div class="main hero">
    <div class="container">
      <div class="row row1">
        <div class="col-md-6 col1">
          {% block promoted %}
            {% snippet 'home/snippets/promoted.html' %}
          {% endblock %}
        </div>
        <div class="col-md-6 col2">
          {% block search %}
            {% snippet 'home/snippets/search.html' %}
          {% endblock %}
        </div>
      </div>
    </div>
  </div>
  <div class="main">
    <div class="container">
      <div class="row row2">
        <div class="col-md-6 col1">
          {# Note: this featured_group block is used as an example in the theming
             tutorial in the docs! If you change this code, be sure to check
             whether you need to update the docs. #}
          {# Start template block example. #}
          {% block featured_group %}
            {% snippet 'home/snippets/featured_group.html' %}
          {% endblock %}
          {# End template block example. #}
        </div>
        <div class="col-md-6 col2">
          {% block featured_organization %}
            {% snippet 'home/snippets/featured_organization.html' %}
          {% endblock %}
        </div>
      </div>
    </div>
  </div>
</div>

CKAN 支援不同的layout: layout1, layout2, layout3 等。由 sysadmin 在 admin 頁設定。假設使用第一個 layout1.html。

以 {% ckan_extends %} 擴充 CKAN 預設的 template,可用 {% block %} 取代預設 template 中的任何 block。

新增 layout1.html

建立 ckanext-example_theme/ckanext/example_theme/templates/home/layout1.html ,內容如下:

{% ckan_extends %}

{% block featured_group %}
  取代原本的 featured_group: Hello block world!
{% endblock %}

這個檔案擴充原本的 layout1.html,覆寫其中的 featured_group block。

重啟 web server,瀏覽 CKAN 首頁,可看到 featured_group 區塊已被取代為 Hello block world!,但其它內容都沒有動。


{{ super() }}

{{ super() }} 可在 block 加入內容,而不是取代它。

修改:layout1.html

{% ckan_extends %}

{% block featured_group %}
    <p>這段文字會出現在 <code>featured_group</code> block 上方</p>      
    {{ super() }}
    <p>這段文字會出現在 <code>featured_group</code> block 下方</p>
{% endblock %}

以上 block 執行時,{{ super() }} 會被 parent block 的內容取代。



CKAN v2.9 templates 3: Jinja2

Jinjia2

CKAN template 以 Jinja2 撰寫。

敍述式及變數

{{ ... }} 中即 Jinja2 敍述式,最後放入執行結果。

{{ foo }} 會顯示成變數 foo 的值。

template 可用 CKAN 的許多全域變數,像是 app_globals 用 g 別名取得 CKAN 設定檔中部分設定,例如 ckan.site_title:

<p>The title of this site is: {{ g.site_title }}.</p>

其他可參考:http://docs.ckan.org/en/2.9/theming/variables-and-functions.html

並不是所有設定都讀得到,自訂設定就讀不到,參考:CKAN Templates Tutorial 9(v2.9): 存取自訂的 config 設定

若 template 要讀取的變數不存在,不會 crash 也不會有錯誤訊息,Jinja2 只會傳回空字串。

{{ g.an_attribute_that_does_not_exist }}
{{ a_variable_that_does_not_exist }}

但如果要產生不存在變數的 attribute,Jinja2 會 crash,如下:

{{ a_variable_that_does_not_exist.an_attribute_that_does_not_exist }}

UndefinedError: 'a_variable_that_does_not_exist' is undefined

Jinja2 不只是列印變數,還可以呼叫 Jinja2 的 global functions, CKAN 的 template helper functions 及自訂的 template helper functions。參考:Variables and functions available to templates

Jinja tags

{% ... %} 裡的 Jinja tags 可控制程式邏輯。我們可以顯示啟用的 plugin 清單:

<ul>
  {% for plugin in g.plugins %}
    <li>{{ plugin }}</li>
  {% endfor %}
</ul>

用 Jinja’s {% if %} 測試條件:

{% if g.tracking_enabled %}
  <p>CKAN's page-view tracking feature is enabled.</p>
{% else %}
  <p>CKAN's page-view tracking feature is <i>not</i> enabled.</p>
{% endif %}

註解

{# ... #} 裡的是註解。

{# This text will not appear in the output when this template is rendered. #}

修改 index.html

修改上個 tutorial 產生的 home/index.html 如下

{# Jinja variable example #}
<p>The title of this site is: {{ g.site_title }}.</p>
{# End example #}

{# Jinja for-loop example #}
<p>The currently enabled plugins are:</p>
<ul>
  {% for plugin in g.plugins %}
    <li>{{ plugin }}</li>
  {% endfor %}
</ul>
{# End example #}

{# Jinja if example #}
{% if g.tracking_enabled %}
  <p>CKAN's page-view tracking feature is enabled.</p>
{% else %}
  <p>CKAN's page-view tracking feature is <i>not</i> enabled.</p>
{% endif %}
{# End example #}

重啟 CKAN

  1. 進入Python Virtual Environment: 
    . /usr/lib/ckan/default/bin/activate
  2. 重啟 CKAN 服務:
    (production版) sudo supervisorctl restart ckan-uwsgi:*
    (develop版) ckan -c /etc/ckan/default/ckan.ini run

測試

重新瀏覽 http://[CKAN web site]/



CKAN v2.9 templates 2: 取代預設的 template

http://docs.ckan.org/en/2.9/theming/templates.html#replacing-a-default-template-file

每個 CKAN 網頁都由對應的 template 產生,如:

  • /index --> ckan/templates/home/index.html
  • /about --> ckan/templates/home/about.html
  • /dataset --> ckan/templates/package/search.html

要自訂,plugin 需註冊一個包含 template 的 template 目錄來覆寫 CKAN 的預設內容。

延續上個 CKAN templates Tutorial 1(v2.9): 空的example_theme

修改 plugin.py

修改 ckanext-example_theme/ckanext/example_theme/plugin.py,如下

import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit

class ExampleThemePlugin(plugins.SingletonPlugin):
    plugins.implements(plugins.IConfigurer)

    def update_config(self, config):
        toolkit.add_template_directory(config, 'templates')

程式中:

  • import ckan.plugins.toolkit as toolkit
    plugins toolkit 是 Python module 提供 CKAN plugins 必要的 functions, classes 及 exceptions。
  • plugins.implements(plugins.IConfigurer)
    實作 plugins.IConfigurer
  • def update_config
    實作 plugins.IConfigurer 介面的 update_config。
    CKAN 會在啟動時呼叫 IConfigurer 介面的 update_config,讓我們有機會修改 CKAN 的設定。
  • ckan.plugins.toolkit.add_template_directory 向 CKAN 註冊 template 目錄,告訴 CKAN 產生/render 網頁時來找這個plugin目錄下的 templates 目錄。
    只要與 CKAN 預設 template 同名的檔案就會取代掉原本的。

查看 CKAN 使用的 template

修改 /etc/ckan/default/ckan.ini 中的 debug = true。

重新載入 CKAN 首頁,會看到下方有個 Debug 連結,按下後展開 debug footer 顯示許多偵錯資訊,其中便有產生此網頁的 template 檔名。

Template name: home/index.html
Template path: /usr/lib/ckan/default/src/ckan/ckan/templates/home/index.html

大部分的 CKAN 網頁由多個 template 產生,第一個是 root template,其它則是相關的 included 檔。

覆寫 home/index.html

在 ckanext-example_theme 目錄下為要覆寫的 template 建立對應的目錄及檔案,如:
  ckanext/
    example_theme/
      templates/
        home/
          index.html  

編輯 index.html 如下:

Hello new Index Page! 中文也沒問題!

如果執行 ckan 沒有用 -r(--disable-reloader) 選項,如此修改 Python file,  template file,  CKAN config file, 或其它 CKAN file 都不需重啟伺服器。但如果是新加檔案、目錄就要重啟。

測試

重新瀏覽 http://[你的IP]/



CKAN v2.9 templates 1: 空的example_theme

http://docs.ckan.org/en/2.9/theming/templates.html

CKAN 網頁由 Jinja2 template 產生。以下練習將修改 layout 及預設內容。

CKAN theme 就是 CKAN plugin,內含自訂的 templates、靜態檔案。

建立: ckanext-example_theme

  • 啟用 CKAN Virtual Environment
. /usr/lib/ckan/default/bin/activate
  • 切換到 /usr/lib/ckan/default/src 執行 ckan generate extension
Extension's name [must begin 'ckanext-']: ckanext-example_theme
 
ckanext-example_theme
├── ckanext
│   ├── example_theme
│   │   ├── assets
│   │   │   └── webassets.yml
│   │   ├── fanstatic
│   │   ├── i18n
│   │   ├── __init__.py
│   │   ├── plugin.py
│   │   ├── public
│   │   ├── templates
│   │   └── tests
│   │       ├── __init__.py
│   │       └── test_plugin.py
│   └── __init__.py
├── dev-requirements.txt
├── LICENSE
├── MANIFEST.in
├── README.md
├── requirements.txt
├── setup.cfg
├── setup.py
└── test.ini
 

修改 plugin.py 

所有 CKAN plugin 都需繼承自 plugins.SingletonPlugin。修改 ckanext-example_theme/ckanext/example_theme/plugin.py 如下:

import ckan.plugins as plugins

class ExampleThemePlugin(plugins.SingletonPlugin):
    '''An example theme plugin.

    '''
    pass


        修改並執行 setup.py 

        修改 ckanext-example_theme/setup.py 的 entry_points
        entry_points='''
            [ckan.plugins]
            example_theme=ckanext.example_theme.plugin:ExampleThemePlugin
        ''',

        切換到 ckanext-example_theme 目錄,執行:

        cd ckanext-example_theme
        python setup.py develop


        修改 /etc/ckan/default/ckan.ini 並重啟 CKAN

        extension plugins 必須加到 CKAN 設定檔的 ckan.plugins 中,CKAN 才會呼叫。

        ckan.plugins = stats text_view 略... example_theme

        重啟 CKAN

        1. 進入Python Virtual Environment: 
          . /usr/lib/ckan/default/bin/activate
        2. 重啟 CKAN 服務:
          (production版) sudo supervisorctl restart ckan-uwsgi:*
          (develop版) ckan -c /etc/ckan/default/ckan.ini run

        測試

        重新載入 http://[CKAN web site]/

        沒錯就是對了。

        2022年5月4日 星期三

        CKAN 前端開發(v2.9): 自訂 base template

         http://docs.ckan.org/en/2.9/contributing/frontend/template-tutorial.html

        page.html 提供基本 CKAN 網頁結構及其 header、footer。

        {% extends "page.html" %}

        page.html template 中有許多 block 可擴充,最常用的就是 *_content:

        • primary_content
        • secondary_content: sidebar
        • breadcrumb_content
        • actions_content: breadcrumb 左邊的 actions bar 

        Primary Content

        加入以下內容:

        {% block primary_content %}
          {{ super() }}

          {% block my_content %}
            <h2>{{ _('This is my content heading') }}</h2>
            <p>{{ _('This is my content') }}</p>
          {% endblock %}
        {% endblock %}

        把內容放在 block,如此其他 template 也能覆寫。

        Secondary Content

        通常由 reusable module 的 snippet 內容組成。snippet 可讓 template 保持簡潔,方便 theme extension 覆寫。

        {% block primary_content %}
          {{ super() }}
          {% block my_sidebar_module %}
            {% snippet "snippets/my-sidebar-module.html" %}
          {% endblock %}
        {% endblock %}

        Breadcrumb 及 Actions

        每個網頁上方都有 breadcrumb ,也用來顯示這個網頁可做的 action。

        {% block breadcrumb_content %}
          <li class="active">{% link_for _('Viewing Dataset'), named_route=pkg.type ~ '.read', id=pkg.id %}</li>
        {% endblock %}

        {% block actions_content %}
          {{ super() }}
          <li class="active">{% link_for _('New Dataset'), named_route='dataset.new', class_='btn', icon='plus' %}</li>
        {% endblock %}

        Scripts 及 Stylesheets

        透過 styles 及 scripts 這兩個 block 可自訂樣式及 JavaScript。這功能將被 {% resource %} 取代。

        {% block styles %}
          {{ super() }}
          <link rel="stylesheet" href="{% url_for_static "my-style.css" %}" />
        {% endblock %}

        {% block scripts %}
          {{ super() }}
          <script src="{% url_for_static "my-script.js" %}"></script>
        {% endblock %}

        CKAN 前端開發(v2.9): form macros

        http://docs.ckan.org/en/2.9/contributing/frontend/templating.html#form-macros

        CKAN 用 form macro 產生基本的表單輸入欄位,複雜的自己寫。

        這些 macros 可用 {% import %} 匯入網頁。如下:

        {% import 'macros/form.html' as form %}

        form.input()

        包含產生對應的 label, error message。

        name        - The name of the form parameter.
        id          - The id to use on the input and label. Convention is to prefix with 'field-'.
        label       - The human readable label.
        value       - The value of the input.
        placeholder - Some placeholder text.
        type        - The type of input eg. email, url, date (default: text).
        error       - A list of error strings for the field or just true to highlight the field.
        classes     - An array of classes to apply to the control-group.
        attrs       - Dictionary of extra tag attributes
        is_required - Boolean of whether this input is required for the form to validate

        如:

        {% import 'macros/form.html' as form %}
        {{ form.input('title', label=_('Title'), value=data.title, error=errors.title) }}

        其餘參考: http://docs.ckan.org/en/2.9/contributing/frontend/templating.html#form-macros



        CKAN 前端開發(v2.9): template

        http://docs.ckan.org/en/2.9/contributing/frontend/templating.html

        template 搜尋順序

        以 user/index.html 為例,CKAN 會依以下順序去找:

        1. 所有載入 extension 的 template 目錄
        2. 所有載入 extension 的 template_legacy 目錄
        3. CKAN 的 template 目錄
        4. CKAN 的 template_legacy 目錄
        以上的 legacy 指的是 Genshi template,它們全都被放在 templates_legacy 目錄。若 template 目錄找不到,才會去 legacy 目錄下找。在 CKAN 升級至新版向前相容。

        檔案結構

        師之前版本一樣,每個 controller 一個目錄、每個 action 一個 template 檔案。

        controller 下的 snippets 目錄存放此 controller 專用的 snippet,通用的 snippet 則放在上層目錄。

        templates/
          base.html             # A base template with just core HTML structure
          page.html             # A base template with default page layout
          header.html           # The site header.
          footer.html           # The site footer.
          snippets/             # A folder of generic sitewide snippets
          home/
            index.html          # Template for the index action of the home controller
            snippets/           # Snippets for the home controller
          user/
            ...
        templates_legacy/
          # All ckan templates

        templating system

        Jinja2 使用很多 template 繼承的功能來產生網頁。action 的 template 大多也繼承自 page.html:

        {% extends "page.html" %}

        其中有許多 block 可讓子 template 覆寫。page.html 決定了基本頁面內容架構,通常只有 {% block primary_content %} 需要自訂。

        {% extends "page.html" %}
        {% block page_content.html %}
          <h1>My page title</h1>
          <p>This content will be added to the page</p>
        {% endblock %}

        慣例/Conventions

        Includes

        儘量少用 Include。 {% include %} 的內容需放在使用它的程式下的 _snippets_ 目錄。

        通常會用 {% snippet %} ,除非 parents context 只能被此 snippet 使用。

        Snippets

        用 {% snippet %} 比 h.snippet() 好。

        snippet 介於 include 和 macro 之間,透過參數決定那些資訊要傳入 snippet。(include 只能接收到 父 context).

        儘量 include,因為可讓偵錯變簡單。

        Macros

        Macros 應節制使用在為通用的 code snippet 建立 custom generator。像是 macros/form.html  中用 macro 來建立表單欄位。

        儘量少用,marcro不好擴充及自訂。

        extension 中的 template

        在 plugin 呼叫 update_config() 向 CKAN 註冊額外的 template 路徑/extra_template_paths。

        Custom Control Structures

        {% ckan_extends %}

        用法和 {% extend %} 很像,但它會向上載入下一個 template。

        如果要移除使用者資訊頁面的 breadcrumb。

        1. 先確定要覆寫的 template:
          ckan/templates/user/read.html
        2. 在你的 extension 的 template 目錄下,建立對應要覆寫的目錄及檔案:
          ckanext-mytheme/ckanext/mytheme/templates/user/read.html
        3. 在 read.html 中透過 {% ckan_extends %} 拉出 CKAN 核心網頁內容:
          {% ckan_extends %}
        4. 然後覆寫原本的 breadcrumb block:
          {% ckan_extends %}
          {# 以下會移除 breadcrumb #}
          {% block breadcrumb %}{% endblock %}

        {% ckan_extends %} 會 recursively 執行,{% ckan_extend %} 只會套用在相同名稱的 template。

        {% snippet [filepath], [arg1=arg1], [arg2=arg2]... %}

        很像 Jinja2 的 {% include %},差別在 CKAN 的 snippet 不會繼承 parent template 的 context,也就是只有明確傳入的參數可用,好處是容易偵錯。用法如下:

        {% snippet "package/snippets/package_form.html", data=data, errors=errors %}

        {% url_for [arg1=arg1], [arg2=arg2]... %}

        用法同 h.url_for():

        <a href="{% url_for controller="home", action="index" %}">Home</a>

        {% link_for text, [arg1=arg1], [arg2=arg2]... %}

        用法同 h.link_for():

        <li>{% link_for _("Home"), controller="home", action="index" %}</li>

        {% url_for_static path %}

        用法同 h.url_for_static():

        <script src="{% url_for_static "/javascript/home.js" %}"></script>




        CKAN 前端開發(v2.9): 概覽

        http://docs.ckan.org/en/2.9/contributing/frontend/index.html 

        前端的 stylesheet 採用 Less (系統需安裝 node.js ) 撰寫。

        檔案結構

        所有前端用到的檔案都放在 public 目錄下(ckan/lib/default/src/ckan/ckan/public/base)如下,檔名及目錄名稱都需小寫或用減號分隔

        css/             --- Less 編譯後的 production  版 CSS 檔
          main.css
        less/            --- less 檔。vendor 的樣式檔放在 vendor 目錄,在 main.less 裡 include。
          main.less
          ckan.less
          ...
        javascript/     --- 建議使用 main.js 做為起始檔
          main.js
          utils.js
          components/
          ... 
        vendor/
        --- 存放外部相依檔,版本數字不能出現在檔名,應放在檔頭註解。 
        --- 函式庫需以函式庫名稱開頭。 
        --- 若相依檔有多個(如bootstrap檔)則整個目錄應用 distributed 方式處理。  
          jquery.js
          jquery.plugin.js
          underscore.js
          bootstrap.css
          ...

        Stylesheets

        所有樣式檔都以 Less 方式處理,開發前需先編譯:

        $ npm run watch

        以上指令會偵測所有 less 檔的修改並自動重建。(ctrl-c結束)。以下指令啟用偵錯模式,可看到sourcemaps:

        $ DEBUG=1 npm run watch

        樣式檔很多,主要有以下兩群:

        • main.less
          網站所有 dependancies、local 樣式,只排除某些情況下才載入的樣式,像是IE才用的 CSS  和只在某個網頁才顯示的外部 apps (如 recline)。
        • ckan.less
          所有 ckan 本身使用的樣式。

          JavaScript

          三部分:

          • Core (such as i18n, pub/sub and API clients)
            • Modules
            • Publisher/Subscriber
            • Client
            • i18n/Jed

          • Modules (small HTML components or widgets)
          • jQuery Plugins (very small reusable components)

          Module

          網頁上所有可互動的元件都應該是一個 module。在 HTML 元素裡加入 data-module 屬性/attribute  進行初始化:

          <select name="format" data-module="autocomplete"></select>

          這種作法的好處是讓它是一個小的可測試元件。所有全域物件應該透過 sandbox 傳入。

          用 global factory 建立新 modules,jQuery 及 Localisation 則透過 this.sandbox.jQuery 及 this.sandbox.translate()。

          ckan.module('my-module', function (jQuery) {
            return {
              initialize: function () {
                // Called when a module is created.
                // jQuery and translate are available here.
              },
              teardown: function () {
                // Called before a module is removed from the page.
              }
            }
          });

          Publisher/subscriber

          在 ckan.pubsub 下有一個簡單的 pub/sub module,透過 this.sandbox.publish/subscribe/unsubscribe,可在 module 間發佈/publish 訊息。

          module 間應透過 publish/subscribe 的方式溝通,讓 UI 上相關區域/area 做對應的刷新。

          ckan.module('language-picker', function (jQuery) {
            return {
              initialize: function () {
                var sandbox = this.sandbox;
                this.el.on('change', function () {
                  sandbox.publish('change:lang', this.selected);
                });
              }
            }
          });

          ckan.module('language-notifier', function (jQuery) {
            return {
              initialize: function () {
                this.sandbox.subscribe('change:lang', function (lang) {
                  alert('language is now ' + lang);
                });
              }
            }
          });

          Client

          module 不該用 jQuery.ajax() 呼叫 CKAN API,應該透過 client object。

          ckan.module('my-module', function (jQuery) {
            return {
              initialize: function () {
                this.sandbox.client.getCompletions(this.options.completionsUrl);
              }
            }
          });

          多國語系/Internationalization

          參考 Internationalizing strings in JavaScript code.

          生命週期/Life cycle

          CKAN module 在 dom ready 後初始化,ckan.module.initialize() 找出網頁中有 data-module  屬性的的 HTML 元素,試著建立對應的物件。

          <select name="format" data-module="autocomplete" data-module-key="id"></select>

          module 會依 HTML 元素中設定的 data-module-* 屬性建立對應的 sandbox 物件,建立後便呼叫 module 的 initialize() 初始化。

          module 應提供 teardown() 回復原始狀態。


              CKAN Theming Tutorial 1(v2.9): 靜態檔案(圖片與CSS)

              https://docs.ckan.org/en/2.9/theming/static-files.html

              新增 CKAN 設定 extra_public_paths。plugin 可讓存放靜態檔案的目錄開放給 template 使用。以下以一個在首頁的 template 圖檔為例。

              延續 CKAN templates Tutorial1(v2.9): 空的example_theme ,切換到 /usr/lib/ckan/default/src/ 目錄下。

              public 目錄

              將圖片與CSS 放在如下目錄:

              ckanext-example_theme/
                ckanext/
                   example_theme/
                      public/
                         ckan-logo.jpg
                         example_theme.css


              example_theme.css 檔內容:

              .account-masthead {
                  background-color: rgb(40, 40, 40);
              }

              420x220px ckan-logo.jpg

              plugin.py 註冊 public 目錄

              編輯 ckanext-example_theme/ckanext/example_theme/plugin.py,在 update_config() 中呼叫 add_public_directory() 向 CKAN 註冊 public 目錄:

                  def update_config(self, config):
                      略...

                      # Add this plugin's public dir to CKAN's extra_public_paths, so
                      # that CKAN will use this plugin's custom static files.
                      toolkit.add_public_directory(config, 'public')

              覆寫 base.html

              在如下目錄新增 base.html:

              ckanext-example_theme/
                ckanext/
                  example_theme/
                    templates/
                      base.html

              base.html 內容如下:

              {% ckan_extends %}

              {% block styles %}
                {{ super() }}
                <link rel="stylesheet" href="/example_theme.css" />
              {% endblock %}

              styles  block 中的內容會出現在HTML網頁的 <head>...</head> 中。

              安裝

              1. 進入Python Virtual Environment: 
                . /usr/lib/ckan/default/bin/activate
              2. 切換目錄:
                cd /usr/lib/ckan/default/src/ckanext-example_theme
              3. 安裝 Plugin: 
                python setup.py develop
              4. 重啟 CKAN 服務:
                (production版) sudo supervisorctl restart ckan-uwsgi:*
                (develop版) ckan -c /etc/ckan/default/ckan.ini run

              測試

              http://[CKAN Website IP]/ckan-logo.jpghttp://[CKAN Website IP]/example_theme.css 可看到對應內容。

              檢視 http://[CKAN Website IP] 的網頁原始碼,在HTML的 <head>...</head> 可發現:

              <link rel="stylesheet" href="/example_theme.css" />




              2022年5月3日 星期二

              CKAN Extensions Tutorial 3(v2.9): 在 plugin 中呼叫 toolkit

              https://docs.ckan.org/en/2.9/extensions/tutorial.html#using-the-plugins-toolkit

              CKAN plugins toolkit 是一個 Python module,提供 CKAN 相關的 functions, classes 及 exceptions 協助撰寫 CKAN extension。

              toolkit.get_action 可用來呼叫 CKAN 的 action function,這和透過 web interface 或 API 呼叫 的方法一樣。以下程式,ckan.plugins.toolkit.get_action 呼叫ckan.logic.action.get.member_list 取得 curators group 的成員名單,結果和 API 呼叫 /api/3/action/member_list 會是一樣的:

                  members = toolkit.get_action('member_list')(
                      data_dict={'id': 'curators', 'object_type': 'user'})


              延續上個 CKACKAN Extensions Tutorial(v2.9): 實作 IAuthFunctions 自訂授權規則,以下將實作只允許 curators 群組成員建立群組的功能。

              建立 curators 群組

              在 CKAN 中建立 curators 群組。

              修改 plugin.py:實作 IAuthFunctions

              修改 ckanext-iauthfunctions/ckanext/iauthfunctions/plugin.py (程式碼)

              import ckan.plugins as plugins
              import ckan.plugins.toolkit as toolkit

              def group_create(context, data_dict=None):

                  # Get the user name of the logged-in user.
                  user_name = context['user']

                  # Get a list of the members of the 'curators' group.
                  members = toolkit.get_action('member_list')(
                      data_dict={'id': 'curators', 'object_type': 'user'})

                  # 'members' is a list of (user_id, object_type, capacity) tuples, we're
                  # only interested in the user_ids.
                  member_ids = [member_tuple[0] for member_tuple in members]

                  # We have the logged-in user's user name, get their user id.
                  convert_user_name_or_id_to_id = toolkit.get_converter(
                      'convert_user_name_or_id_to_id')
                  user_id = convert_user_name_or_id_to_id(user_name, context)

                  # Finally, we can test whether the user is a member of the curators group.
                  if user_id in member_ids:
                      return {'success': True}
                  else:
                      return {'success': False,
                              'msg': 'Only curators are allowed to create groups'}

              class ExmapleAuthFunctionsPlugin(plugins.SingletonPlugin):
                  plugins.implements(plugins.IAuthFunctions)

                  def get_auth_functions(self):
                      return {'group_create': group_create}


              • 實作 IAuthFunctions
              • 覆寫 IAuthFunctions 介面的 get_auth_functions 方法
              • 呼叫 toolkit.get_action('member_list') 判斷目前使用者是否在 curators 群組

              安裝

              1. 進入Python Virtual Environment: 
                . /usr/lib/ckan/default/bin/activate
              2. 切換目錄:
                cd /usr/lib/ckan/default/src/ckanext-iauthfunctions
              3. 安裝 Plugin: 
                python setup.py develop
              4. 重啟 CKAN 服務:
                sudo supervisorctl restart ckan-uwsgi:*

              測試

              • 開 Postman 執行 api 呼叫
              curl POST 'http://[CKAN web site]/api/3/action/group_create
              --header 'Authorization: [測試會員的API Key]
              --header 'Content-Type: application/json' 
              --data-raw '{
                  "name": "0506-Group",
                  "owner_org": "0427-org",
                  "description": "test ckanext-iauthfunctions"    
              }'

              視是否為 curators group 的帳號,success 回傳 true 或 false:

              {
                  "error": {
                      "__type": "Authorization Error",
                      "message": "拒絕存取: Only curators are allowed to create groups[ExampleIAuthFunctionsPlugin]"
                  },
                  "help": "...",
                  "success": false
              }
              • 以管理員登入 CKAN,建立 curators 群組
                • 把測試會員加入這個群組。
                • 再次以 Postman 執行以上 api 呼叫
                • 即可成功

              CKAN Extensions Tutorial(v2.9): 實作 IAuthFunctions 自訂授權規則

              https://docs.ckan.org/en/2.9/extensions/tutorial.html#implementing-the-iauthfunctions-plugin-interface

              CKAN 提供許多 plugin interfaces(v2.9) 以便掛勾/hook 到 CKAN 修改或擴充功能:

              • u'Interface',
              • u'IRoutes',
              • u'IMapper',
              • u'ISession',
              • u'IMiddleware',
              • u'IAuthFunctions',
              • u'IDomainObjectModification',
              • u'IFeed',
              • u'IGroupController',
              • u'IOrganizationController',
              • u'IPackageController',
              • u'IPluginObserver',
              • u'IConfigurable',
              • u'IConfigurer',
              • u'IActions',
              • u'IResourceUrlChange',
              • u'IDatasetForm',
              • u'IValidators',
              • u'IResourcePreview',
              • u'IResourceView',
              • u'IResourceController',
              • u'IGroupForm',
              • u'ITagController',
              • u'ITemplateHelpers',
              • u'IFacets',
              • u'IAuthenticator',
              • u'ITranslation',
              • u'IUploader',
              • u'IBlueprint',
              • u'IPermissionLabels',
              • u'IForkObserver',
              • u'IApiToken',
              • u'IClick',

              CKAN 如何驗證

              參考: plugin interfaces。凡可透過 CKAN web 介面或 API 呼叫的動作都是以 ckan/logic/action/{create,delete,get,update}.py 實作的 action function。

              例如資料集相關的 Web API 便對應到:

              • func:`ckan.logic.action.create.package_create`
              • func:`ckan.logic.action.get.package_show`
              • func:`ckan.logic.action.update.package_update`
              • func:`ckan.logic.action.delete.package_delete`

              每個 action function 都有對應的 authorization function (ckan/logic/auth/{create,delete,get,update}.py),CKAN 呼叫 authorization function 決定使用者是否有被授權執行。

              實作 ckan.plugins.interfaces.IAuthFunctions,並註冊與 CKAN 同名的 authorization function,如:'user_create', 'group_create',即可覆寫 CKAN 的授權規則。

              在 CKAN 呼叫 authorization function 之前,會先驗證呼叫端是否提供合法的 API key。若要允許匿名執行, authorization function 需標示為 auth_allow_anonymous_access。如:

                          @p.toolkit.auth_allow_anonymous_access
                          def my_search_action(context, data_dict):
                              略⋯⋯

              IAuthFunctions

              實作 ckan.plugins.interfaces.IAuthFunctions。所有 authorization function 都有兩個參數:

              • context
                • model: 可用來查詢資料庫
                • user: 內容使用者的 名稱/IP(匿名存取)
                • auth_user_obj: model.User物件/None(匿名存取)
              • data_dict
                • CKAN 會傳給所有 authorization and action functions data_dict,內容所有 post 進來的資料,可能是網頁中填寫的表單欄位內容,或 API 呼叫傳入的 JSON 內容如:
              {'description': u'A really cool group',
               'image_url': u'',
               'name': u'my_group',
               'title': u'My Group',
               'type': 'group',
               'users': [{'capacity': 'admin', 'name': u'seanh'}]}

              回傳 dictionary:

              • {'success': True} 授權
              • {'success': False} 拒絕

              範例如下:

              def user_create(context, data_dict=None):
                if (some condition):
                  return {'success': True}
                else:
                  return {'success': False, 'msg': 'Not allowed to register'}

              CKAN Extensions Tutorial(v2.9):一個空的 CKAN extension

              https://docs.ckan.org/en/2.9/extensions/tutorial.html

              建立:ckan generate extension  

              1. 進入Python Virtual Environment:
                . /usr/lib/ckan/default/bin/activate
              2. 切換目錄
                cd /usr/lib/ckan/default/src 
              3. 執行 ckan generate extension

              Extension's name [must begin 'ckanext-']: ckanext-你的extension名稱
              (如: ckanext-iauthfunctions )]

              ├── ckanext
              │   ├── iauthfunctions //Python package 程式碼目錄
              │   │   ├── assets
              │   │   │   └── webassets.yml
              │   │   ├── fanstatic
              │   │   ├── i18n
              │   │   ├── __init__.py
              │   │   ├── plugin.py //CKAN Extension的功能寫在這裡
              │   │   ├── public
              │   │   ├── templates
              │   │   └── tests
              │   │       ├── __init__.py
              │   │       └── test_plugin.py
              │   └── __init__.py
              ├── dev-requirements.txt
              ├── LICENSE
              ├── MANIFEST.in
              ├── README.md
              ├── requirements.txt
              ├── setup.cfg
              ├── setup.py  //用它安裝到Virtual Environment
              └── test.ini

              若出現錯誤 `cookiecutter` library is missing from import path.。則加裝 cookiecutter :

               pip install cookiecutter 

              修改 plugin.py:撰寫 plugin 類別

              修改 ckanext-iauthfunctions/ckanext/iauthfunctions/plugin.py。CKAN 的 extension 其實就是一個 plugin,所有 CKAN plugin 都需繼承自 plugins.SingletonPlugin
               
              import ckan.plugins as plugins

              class ExampleIAuthFunctionsPlugin(plugins.SingletonPlugin):
                  pass

              修改 setup.py:設定 entry_points

              修改 ckanext-iauthfunctions/setup.py 的 entry_points

              entry_points='''
                      [ckan.plugins] 
                      example_iauthfunctions=ckanext.iauthfunctions.plugin:ExampleIAuthFunctionsPlugin

              修改 ckan.ini:啟用 plugin

              修改 /etc/ckan/default/ckan.ini。CKAN 的 extension plugins 必須加到 CKAN 設定檔的 ckan.plugins 中,CKAN 才會呼叫。

              ckan.plugins = stats text_view 略... example_iauthfunctions

              安裝測試

              1. 進入Python Virtual Environment: 
                . /usr/lib/ckan/default/bin/activate
              2. 切換目錄:
                cd /usr/lib/ckan/default/src/ckanext-iauthfunctions
              3. 安裝 Plugin: 
                python setup.py develop
              4. 重啟 CKAN 服務:
                sudo supervisorctl restart ckan-uwsgi:*

              檢視錯誤

              cat /etc/ckan/default/uwsgi.ERR