1. SQLEDITABLE デモ

1.1. インフォメーション

1.2. SQLEDITABLE 基本的な使い方

最初に、SQLEDITABLEのプラグインファイルをリンクからダウンロードし、アプリケーションにセットします。

https://github.com/hiho-/SQLEDITABLE/releases

  • web2py.plugin.sqleditable.w2p - プラグインファイル
  • web2py.app.demo.w2p - デモアプリ

モデル(db.pyなど)もしくはコントローラ(default.pyなど)に、次の記述を追加します。

from plugin_sqleditable.editable import SQLEDITABLE
SQLEDITABLE.init()

後は、SQLFORM と同様に記述するだけです。

1.2.1. デモ1

Demo010 - リンクをクリックしてください

モデル定義

db.define_table('employee_sheet',
    Field('employee_number','integer',length=5,label='Emp.no.'),
    Field('name','string',length=50,notnull=True),
    Field('date_employment','date',label='Enter',requires=IS_DATE()),
    Field('employee_section','string',label='Section',
    requires=IS_IN_SET({'s':'sales','p':'production','d':'development',
                                                'c':'control'})),
    Field('employee_comment','string',label='Comment'),
    Field('date_resignation','date',label='Exit',readable=False,
                                            requires=IS_EMPTY_OR(IS_DATE())),
    Field('resigned','boolean', writable=False, default=False),
    Field('remuneration','decimal(12,2)', readable=False),
    Field('currency','string',readable=False,
                                requires=IS_IN_SET(['USD','EUR','GBP','JPY'])))

コントローラ

def demo010():
    response.title = 'demo010'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.employee_sheet, showid=False, maxrow=5).process()
    return dict(editable=editable)

現在のところ、対応しているフィールドタイプは、interger , double , float , decimal, string , boolean , date , datetime , time です。

また、IS_IN_SET バリデータが記述されていると、リストボックスが表示されます。 IS_IN_DB にも対応しました(デモ13 - IS_IN_DB)。

../../_images/sqleditable_001.PNG

1.2.2. デモ2 - 縦向き

Demo011 - リンクをクリックしてください

コントローラ

def demo011():
    response.title = 'demo011'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.employee_sheet, showid=False, maxrow=5,
                                                    vertical=False).process()
    return dict(editable=editable)

vertical=False で横向きに表示されます。

../../_images/sqleditable_002.PNG

1.2.3. デモ3 - バリデート

Demo012 - リンクをクリックしてください

コントローラ

def demo012():
    response.title = 'demo012'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.employee_sheet, showid=False, maxrow=5,
                                                validate_js=False).process()
    return dict(editable=editable)

validate_js=False によって、Javascriptバリデータを無効にしています。

../../_images/sqleditable_003.PNG

1.2.4. デモ4 - 削除

Demo020 - リンクをクリックしてください

コントローラ

def demo020():
    db.employee_sheet.date_resignation.readable = True
    db.employee_sheet.resigned.writable = True
    db.employee_sheet.remuneration.readable = True
    db.employee_sheet.currency.readable = True

    response.title = 'demo020'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.employee_sheet, maxrow=5, deletable=True).process()
    return dict(editable=editable)

SQLEDITABLE の前にテーブル属性を変更しています。 また、deletable=True によって削除チェックボックスを表示しています。

../../_images/sqleditable_004.PNG

1.2.5. デモ5 - レコード指定

Demo030 - リンクをクリックしてください

コントローラ

def demo030():
    def record():
        rows = db(db.employee_sheet).select(
                    orderby=~db.employee_sheet.employee_number, limitby=(0,3))
        return [row.id for row in rows.sort(lambda row:row.employee_number)]

    response.title = 'demo030'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.employee_sheet, record=record,showid=False,
                                                            maxrow=5).process()
    return dict(editable=editable)

record によって、レコードidを指定しています。 通常はリストで指定しますが、callableのため、DAL構文を記述してもOKです。 3行分を登録したデータで、2行分を新規データ用に表示します。

../../_images/sqleditable_005.PNG

1.2.6. デモ6 - as_dict

Demo040 - リンクをクリックしてください

コントローラ

def demo040():
    response.title = 'demo040'
    response.view = 'plugin_sqleditable/sample_as_dict.html'
    editable = SQLEDITABLE(db.employee_sheet, showid=False, maxrow=5).process()
    return editable.as_dict()

as_dict() によって、シートとボタン、スクリプトを辞書でビューに渡します。 サンプルビューでは、editable , button , script の各値の処理をしています。

{{=button}} はビュー上に何個設置しても動作します。(changed:2015-02)

<table class='col-md-12'>
<tr><td> {{=button}}  </td></tr>
<tr><td> {{=editable}} </td></tr>
<tr><td> {{=button}}  </td></tr>
</table>
{{block head}}
{{super}}
{{=script}}
{{end}}
../../_images/sqleditable_006.PNG

1.2.7. デモ7 - 画面遷移

Demo050 - リンクをクリックしてください

コントローラ

def demo050():
    response.title = 'demo050'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.employee_sheet, showid=False, maxrow=5,
                deletable=True).process(next=URL('static', 'success.html'))
    return dict(editable=editable)

next_js=URL(‘static’, ‘success.html’) next=URL('static', 'success.html') によって、成功時別画面に遷移します。(changed:2014-07-02)

../../_images/sqleditable_007.PNG

1.2.8. デモ8

Demo060 - リンクをクリックしてください
Demo061 - リンクをクリックしてください

モデル定義

db.define_table('scadule_sheet',
    Field('scadule_date','date',label='date'),
    Field('scadule_time','time',label='time'),
    Field('term','integer'),
    Field('term_unit','string',
    requires=IS_IN_SET({'min':'minute','hr':'hour','d':'day','wk':'week',
                                                                'mo':'month'})),
    Field('detail','string',length=30,requires=IS_NOT_EMPTY()))

コントローラ

def demo060():
    response.title = 'demo060'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.scadule_sheet, showid=False, maxrow=8,
                                                    deletable=True).process()
    return dict(editable=editable)

def demo061():
    response.title = 'demo061'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.scadule_sheet, showid=False, maxrow=8,
                                    deletable=True, vertical=False).process()
    return dict(editable=editable)

別のテーブルを表示しています。

../../_images/sqleditable_008.PNG
../../_images/sqleditable_009.PNG

1.2.9. デモ9 - タッチデバイス

Demo070 - リンクをクリックしてください
Demo071 - リンクをクリックしてください
Demo072 - リンクをクリックしてください
Demo073 - リンクをクリックしてください

コントローラ

def demo070():
    response.title = 'demo070'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.scadule_sheet, showid=False, maxrow=8,
                                    deletable=True, touch_device=True).process()
    return dict(editable=editable)

def demo071():
    response.title = 'demo071'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.scadule_sheet, showid=False, maxrow=8,
                                deletable=True, touch_device=False).process()
    return dict(editable=editable)

def demo072():
    response.title = 'demo072'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.scadule_sheet, showid=False, maxrow=8,
                                deletable=True, touch_device='Auto').process()
    return dict(editable=editable)

def demo073():
    response.title = 'demo073'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.scadule_sheet, showid=False, maxrow=8,
                                                    deletable=True).process()
    return dict(editable=editable)

date , time , datetime フィールド入力で、タッチデバイス用に入力方法を変更します。

touch_device パラメータが Auto もしくは指定しない場合は、request.user_agent() をチェックし、タッチデバイスと判定したアクセスでは、 web2pyのウィジェットではなく、HTML5対応のブラウザウィジェットを使用します。

../../_images/sqleditable_011.PNG
../../_images/sqleditable_010.PNG

1.2.10. デモ10 - 仮想フィールド

Demo090 - リンクをクリックしてください

モデル定義

db.define_table('employee_sheet',
    Field('employee_number','integer',length=5,label='Emp.no.'),
    Field('name','string',length=50,notnull=True),
    Field('date_employment','date',label='Enter',requires=IS_DATE()),
    Field('employee_section','string',label='Section',
    requires=IS_IN_SET({'s':'sales','p':'production','d':'development',
                                                'c':'control'})),
    Field('employee_comment','string',label='Comment'),
    Field('date_resignation','date',label='Exit',readable=False,
                                            requires=IS_EMPTY_OR(IS_DATE())),
    Field('resigned','boolean', writable=False, default=False),
    Field('remuneration','decimal(12,2)', readable=False),
    Field('currency','string',readable=False,
                                requires=IS_IN_SET(['USD','EUR','GBP','JPY'])))

class san(object):
    def san(self):
        return self.employee_sheet.name + '-san'
db.employee_sheet.virtualfields.append(san())
db.employee_sheet.san2 = Field.Virtual(lambda row: row.employee_sheet.name + '-SAN')

コントローラ

def demo090():
    response.title = 'demo090'
    response.view = 'plugin_sqleditable/sample.html'
    header = ['employee_number','name','san','san2']
    editable = SQLEDITABLE(db.employee_sheet, header=header, showid=False, maxrow=5,
                                                            deletable=True).process()
    return dict(editable=editable)

仮想フィールドを表示します。 header パラメータで表示フィールドを指定します。

../../_images/sqleditable_012.PNG

1.2.11. デモ11 - アンカーリンク

Demo091 - リンクをクリックしてください

モデル定義

db.define_table('employee_sheet',
    Field('employee_number','integer',length=5,label='Emp.no.'),
    Field('name','string',length=50,notnull=True),
    Field('date_employment','date',label='Enter',requires=IS_DATE()),
    Field('employee_section','string',label='Section',
    requires=IS_IN_SET({'s':'sales','p':'production','d':'development',
                                                'c':'control'})),
    Field('employee_comment','string',label='Comment'),
    Field('date_resignation','date',label='Exit',readable=False,
                                            requires=IS_EMPTY_OR(IS_DATE())),
    Field('resigned','boolean', writable=False, default=False),
    Field('remuneration','decimal(12,2)', readable=False),
    Field('currency','string',readable=False,
                                requires=IS_IN_SET(['USD','EUR','GBP','JPY'])))

db.employee_sheet.history = Field.Virtual(lambda row: A('History',
                                _href=URL(f='demo092', args=row.employee_sheet.id)))

db.define_table('history_in_house',
    Field('employee',db.employee_sheet),
    Field('date_history','date',label='Enter',requires=IS_DATE()),
    Field('employee_section','string',label='Section',
    requires=IS_IN_SET({'s':'sales','p':'production','d':'development',
                                                'c':'control'})))

コントローラ

def demo091():
    response.title = 'demo091'
    response.view = 'plugin_sqleditable/sample.html'
    header = ['employee_number','name','history']
    editable = SQLEDITABLE(db.employee_sheet, header=header, showid=False, maxrow=5,
                                                            deletable=True).process()
    return dict(editable=editable)

def demo092():
    def record():
        session.employee = request.args(0)
        rows = db(db.history_in_house.employee==request.args(0)).select(
                    orderby=~db.history_in_house.date_history, limitby=(0,4))
        return [row.id for row in rows.sort(lambda row:row.date_history)]

    def onvalidation(form):
        form.vars.employee = session.employee

    db.history_in_house.employee.readable = False
    response.title = 'demo092'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.history_in_house, record=record, showid=False, maxrow=5,
                                     deletable=True).process(onvalidation=onvalidation)
    return dict(editable=editable)

アンカーリンクを仮想フィールドを使って表示します。 header パラメータで表示フィールドを指定します。 リンク先のテーブルでは、関連するレコードのみを表示します。このため、 processメソッドで onvalidation パラメータを指定しています。 onvalidation 関数内で、 employee フィールドの値としてURLパラメータ値を指定しています(record関数内で、一旦セッションに値を渡しています)。

../../_images/sqleditable_013.PNG
../../_images/sqleditable_014.PNG

なお、仮想フィールドの記述を次のように変更すれば、ボタン部品で配置することも可能です。

db.employee_sheet.history = Field.Virtual(lambda row: BUTTON('Hisotry',
_onClick="location.href='%s'" % URL(f='demo092', args=row.employee_sheet.id)))

onvalidation パラメータを使用せず、 次のように demo092 コントローラを記述することも可能です。フィールドのデフォルト値を設定しています(9行目)。

def demo092():
    def record():
        session.employee = request.args(0)
        rows = db(db.history_in_house.employee==request.args(0)).select(
                    orderby=~db.history_in_house.date_history, limitby=(0,4))
        return [row.id for row in rows.sort(lambda row:row.date_history)]

    db.history_in_house.employee.readable = False
    db.history_in_house.employee.default = session.employee
    response.title = 'demo092'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.history_in_house, record=record, showid=False, maxrow=5,
                                                               deletable=True).process()
    return dict(editable=editable)

1.2.12. デモ12 - onvalidation

Demo100 - リンクをクリックしてください

モデル定義

db.define_table('employee_sheet',
    Field('employee_number','integer',length=5,label='Emp.no.'),
    Field('name','string',length=50,notnull=True),
    Field('date_employment','date',label='Enter',requires=IS_DATE()),
    Field('employee_section','string',label='Section',
    requires=IS_IN_SET({'s':'sales','p':'production','d':'development',
                                                'c':'control'})),
    Field('employee_comment','string',label='Comment'),
    Field('date_resignation','date',label='Exit',readable=False,
                                            requires=IS_EMPTY_OR(IS_DATE())),
    Field('resigned','boolean', writable=False, default=False),
    Field('remuneration','decimal(12,2)', readable=False),
    Field('currency','string',readable=False,
                                requires=IS_IN_SET(['USD','EUR','GBP','JPY'])))

コントローラ

def demo100():
    def onvalidation(form):
        if len(form.vars.name) < 5:
            form.errors.name = 'Enter 5 characters or more'
        else:
            form.vars.name = form.vars.name.title()

    response.title = 'demo100'
    response.view = 'plugin_sqleditable/sample.html'
    header = ['employee_number','name']
    editable = SQLEDITABLE(db.employee_sheet, header=header, showid=False, maxrow=5,
         deletable=True, update_display_record=True).process(onvalidation=onvalidation)
    return dict(editable=editable)

onvalidation パラメータのデモです。processメソッドで onvalidation パラメータを指定しています。 web2py の Form と同様に、form.vars.xxxform.errors.xxx 変数を使用できます。 onvalidation関数内でフィールド値を変更した場合、通常はテーブルの表示は変更されません。このため、SQLEDITABLEのパラメータの update_display_recordTrue にすると、 変更された値にテーブルの表示が更新されます。

../../_images/sqleditable_015.PNG

1.2.13. デモ13 - IS_IN_DB

Demo093 - リンクをクリックしてください

モデル定義

db.define_table('employee_sheet',
    Field('employee_number','integer',length=5,label='Emp.no.'),
    Field('name','string',length=50,notnull=True),
    Field('date_employment','date',label='Enter',requires=IS_DATE()),
    Field('employee_section','string',label='Section',
    requires=IS_IN_SET({'s':'sales','p':'production','d':'development',
                                                'c':'control'})),
    Field('employee_comment','string',label='Comment'),
    Field('date_resignation','date',label='Exit',readable=False,
                                            requires=IS_EMPTY_OR(IS_DATE())),
    Field('resigned','boolean', writable=False, default=False),
    Field('remuneration','decimal(12,2)', readable=False),
    Field('currency','string',readable=False,
                                requires=IS_IN_SET(['USD','EUR','GBP','JPY'])),

    format='%(name)s')

db.define_table('history_in_house',
    Field('employee',db.employee_sheet),
    Field('date_history','date',label='Enter',requires=IS_DATE()),
    Field('employee_section','string',label='Section',
    requires=IS_IN_SET({'s':'sales','p':'production','d':'development',
                                                'c':'control'})))

コントローラ

def demo093():
    response.title = 'demo093'
    response.view = 'plugin_sqleditable/sample.html'
    editable = SQLEDITABLE(db.history_in_house, showid=False, maxrow=5, deletable=True).process()
    return dict(editable=editable)

IS_IN_DB バリデータをサポートするようになりました。 セレクトボックスから、他のテーブルのデータを表示します。フィールドに IS_IN_DB バリデータをセットするか、 web2pyによる暗黙設定によってリンク先データをセレクトボックスに表示可能です(暗黙設定では、リンク先のテーブルに format 設定が必要です)。

../../_images/sqleditable_016.PNG

1.2.14. デモ14 - 自己参照型フィールド

Demo110 - リンクをクリックしてください
Demo111 - リンクをクリックしてください

モデル定義

db.define_table('node',
    Field('parent_id','reference node'),
    Field('name'),
    Field('node_comment'))

コントローラ

def demo110():
    def record():
        session.parent_id = request.args(0) if request.args(0) else None
        rows = db(db.node.parent_id==request.args(0)).select()
        rec = [row.id for row in rows]
        return rec

    def parent():
        parent = []
        if session.parent_id:
            row = db.node(session.parent_id)
            if row:
                parent.insert(0, LI(row.name, _class='active'))
                while row:
                    if row.parent_id:
                        row = db.node(row.parent_id)
                        if row:
                            parent.insert(0, LI(A(row.name,
                                _href=URL(f='demo110', args=row.id))))
                    else:
                        break
        parent.insert(0, LI(A('Top', _href=URL(f='demo110'))))
        return OL(parent, _class='breadcrumb')

    db.node.children = Field.Virtual(lambda row: A(I(_class='glyphicon glyphicon-arrow-right'),
        ' Children', _class='btn btn-default btn-sm', _href=URL(f='demo110', args=row.node.id)))

    response.title = 'demo110'
    db.node.parent_id.default = session.parent_id
    header = ['name', 'node_comment', 'children']
    editable = SQLEDITABLE(db.node, record=record, maxrow=10, showid=False, deletable=True,
                                                                    header=header).process()
    return dict(editable=editable, parent=parent())

ビュー

<table class='col-md-12'>
<tr><td> {{=parent}}   </td></tr>
<tr><td> {{=editable}} </td></tr>
</table>

自己参照型フィールドを使ったアプリの例です。 parent 辞書変数を追加しています。

as_dict() メソッドを利用する場合は、直接 editable に変数を追加するのではなく、as_dict()のパラメータとして渡す必要があります。

コントローラ

def demo111():
    def record():
        session.parent_id = request.args(0) if request.args(0) else None
        rows = db(db.node.parent_id==request.args(0)).select()
        rec = [row.id for row in rows]
        return rec

    def parent():
        parent = []
        if session.parent_id:
            row = db.node(session.parent_id)
            if row:
                parent.insert(0, LI(row.name, _class='active'))
                while row:
                    if row.parent_id:
                        row = db.node(row.parent_id)
                        if row:
                            parent.insert(0, LI(A(row.name,
                                _href=URL(f='demo111', args=row.id))))
                    else:
                        break
        parent.insert(0, LI(A('Top', _href=URL(f='demo111'))))
        return OL(parent, _class='breadcrumb')

    db.node.children = Field.Virtual(lambda row: A(I(_class='glyphicon glyphicon-arrow-right'),
        ' Children', _class='btn btn-default btn-sm', _href=URL(f='demo111', args=row.node.id)))

    response.title = 'demo111'
    db.node.parent_id.default = session.parent_id
    header = ['name', 'node_comment', 'children']
    editable = SQLEDITABLE(db.node, record=record, maxrow=10, showid=False, deletable=True,
                                                                    header=header).process()
    return editable.as_dict(parent=parent())

ビュー

<table class='col-md-12'>
<tr><td> {{=parent}}   </td></tr>
<tr><td> {{=button}}
         {{=editable}}
         {{=button}}  </td></tr>
</table>
{{block head}}
{{super}}
{{=script}}
{{end}}
../../_images/sqleditable_017.PNG
../../_images/sqleditable_018.PNG

1.2.15. デモ15 - Field.Method

Demo120 - リンクをクリックしてください

コントローラ

def demo120():
    def record():
        session.parent_id = request.args(0) if request.args(0) else None
        rows = db(db.node.parent_id==request.args(0)).select()
        rec = [row.id for row in rows]
        if request.args(0):
            rec.insert(0, request.args(0))
        return rec

    def parent():
        parent = []
        if session.parent_id:
            row = db.node(session.parent_id)
            if row:
                parent.insert(0, LI(row.name, _class='active'))
                while row:
                    if row.parent_id:
                        row = db.node(row.parent_id)
                        if row:
                            parent.insert(0, LI(A(row.name,
                                _href=URL(f='demo120', args=row.id))))
                    else:
                        break
        parent.insert(0, LI(A('Top', _href=URL(f='demo120'))))
        return OL(parent, _class='breadcrumb')

    def onvalidation(form):
        if form.delete and form.vars.id == session.parent_id:
            form.errors.delete = 'This record is a parent and you can not be removed.'

    def argument():
        return int(session.parent_id) if session.parent_id else None

    db.node.children = Field.Method(lambda row, parent_id: A(I(_class='glyphicon glyphicon-arrow-right'),
                ' Children', _class='btn btn-default btn-sm', _href=URL(f='demo120', args=row.node.id)) \
                if row.node.id!=parent_id else SPAN(I(_class='glyphicon glyphicon-home'), ' Parent'))

    response.title = 'demo120'
    db.node.parent_id.default = session.parent_id
    header = ['name', 'node_comment', {'field':'children','argument':argument}]
    editable = SQLEDITABLE(db.node, record=record, maxrow=10, showid=False, deletable=True,
                                                header=header).process(onvalidation=onvalidation)
    return dict(editable=editable, parent=parent())

Field.Method を使用したデモです。Field.Method フィールドの情報は header パラメータに、

{'field':'children','argument':argument}

のように指定します。辞書型の field キーの値にフィールド名を、 argument キーの値にパラメータを指定します。 Field.Method のパラメータは遅延評価のために、呼び出し可能オブジェクト(callable)である必要があります。

../../_images/sqleditable_019.PNG

1.2.16. デモ16 - ページネーション

Demo140 - リンクをクリックしてください

コントローラ

def demo140():
    # page
    limit = 5
    dbset = db(db.employee_sheet)
    rows_count = dbset.count()
    total_page = rows_count // limit + 1 if rows_count % limit < limit else rows_count // limit
    page = int(request.vars.page) if request.vars.page and int(request.vars.page) <= total_page else 0
    vars = request.get_vars
    vars['page'] = page-1
    previous = LI(A('prev', _href=URL(vars=vars, user_signature=True))) if page else ''
    vars['page'] = page+1
    if request.post_vars.keywords:
        vars['keywords'] = request.post_vars.keywords
    next = LI(A('next', _href=URL(vars=vars, user_signature=True))) if page*limit+limit <= rows_count else ''
    page_no = LI('{0}/{1}'.format(page+1, total_page), _style='margin-right:10px;')
    pagination = TAG.nav(UL(CAT(page_no, previous, next)), _class='pager')

    response.title = 'demo140'
    rows = dbset.select(orderby=db.employee_sheet.employee_number, limitby=(page*limit, page*limit+limit))
    editable = SQLEDITABLE(db.employee_sheet, rows, showid=False, maxrow=limit, deletable=True).process()
    return dict(editable=editable, pagination=pagination)

ビュー

<table class='col-md-12'>
<tr><td>
    {{=editable}}
</td></tr>
<tr><td>
    {{=pagination}}
</td></tr>
</table>

大きな表の更新に時間が掛かったり、エラーが発生する場合があります。

この場合は、表を分割表示するテクニックが必要になります。デモ16では、表をページ分割しています。

../../_images/sqleditable_020.JPG