Commit fd9d25a0 by aa.gusti

detable

1 parent 09de39e2
"""Add Wilayah to Partner
Revision ID: 86c1b4a1da16
Revises: 044041bb09ef
Create Date: 2022-04-14 23:50:08.268569
"""
# revision identifiers, used by Alembic.
revision = '86c1b4a1da16'
down_revision = '044041bb09ef'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def upgrade():
context = op.get_context()
helpers = context.opts['helpers']
if not helpers.table_has_column('partner', 'provinsi_id'):
op.add_column('partner', sa.Column('provinsi_id', sa.Integer, default=0))
if not helpers.table_has_column('partner', 'dati2_id'):
op.add_column('partner', sa.Column('dati2_id', sa.Integer, default=0))
if not helpers.table_has_column('partner', 'kecamatan_id'):
op.add_column('partner', sa.Column('kecamatan_id', sa.Integer, default=0))
if not helpers.table_has_column('partner', 'desa_id'):
op.add_column('partner', sa.Column('desa_id', sa.Integer, default=0))
if not helpers.table_has_column('partner', 'company_id'):
op.add_column('partner', sa.Column('company_id', sa.Integer, default=0))
def downgrade():
pass
from sqlalchemy import event
from .base import *
from .common import *
from .users import *
from .wilayah import *
from .partner import *
from .departemen import *
# from .meta import *
from .pegawai import *
# from .meta import *
# from .targets import *
# from .wilayah import *
def has_permission(action):
pass
@event.listens_for(User, 'before_delete', has_permission('delete'))
def receive_before_delete(mapper, connection, target):
"listen for the 'before_delete' event"
# ... (event handling logic) ...
pass
ALL_TABLES = {}
for sub_class in Base.__subclasses__():
if hasattr(sub_class, "__tablename__"):
......@@ -21,7 +32,7 @@ def query_table(table_name, fields=None, domain=None):
cls = ALL_TABLES[table_name]
if fields:
query = DBSession.query().select_from(cls)
for field in fields:
# for field in fields:
query = query.add_columns(*[getattr(cls, c) for c in fields])
else:
query = DBSession.query(cls)
......
......@@ -3,13 +3,22 @@ from datetime import datetime
from opensipkd.tools import as_timezone
from sqlalchemy.ext.hybrid import hybrid_property
import ziggurat_foundations.models
from sqlalchemy.orm import (scoped_session, sessionmaker)
from sqlalchemy.orm import (scoped_session, sessionmaker, Session)
from zope.sqlalchemy import register
from sqlalchemy import Column, String, SmallInteger, Integer, DateTime, func
from sqlalchemy import inspect as sa_inspect
session_factory = sessionmaker()
class MySession(Session):
def execute(self, clause, params=None, mapper=None, **kw):
# Your magic with clause here
# print("Session:", clause, params, mapper, kw)
return Session.execute(self, clause, params, mapper)
session_factory = sessionmaker(class_=MySession)
DBSession = scoped_session(session_factory)
register(DBSession)
ziggurat_foundations.models.DBSession = DBSession
TABLE_ARGS = dict(extend_existing=True, schema="public")
......
......@@ -111,6 +111,7 @@ class BaseView(object):
self.edit_schema = ""
self.add_schema = ""
self.table = ""
self.home = self.req.route_url('home')[:-1]
def route_list(self, msg=None, error=""):
if msg:
......
......@@ -2,10 +2,11 @@ import json
import colander
from deform import (widget, Form, )
from opensipkd.tools.buttons import btn_close, btn_cancel, btn_save
from opensipkd.tools.buttons import btn_close, btn_cancel, btn_save, btn_add, btn_edit, btn_delete
from pyramid.view import (view_config, )
from . import widget_os
from opensipkd.detable import DeTable
from .provinsi import provinsi_widget
from ..models import DBSession, ResDati2, kategori_dati2, ResProvinsi
from ..views import ColumnDT, DataTables, BaseView
......@@ -45,20 +46,18 @@ class EditSchema(AddSchema):
widget=widget.HiddenWidget(readonly=True))
class ListSchema(colander.Schema):
id = colander.SchemaNode(colander.Integer(), searchable=False, orderable=False, visible=False)
kode = colander.SchemaNode(colander.String(), width='100pt', title="Kode")
nama = colander.SchemaNode(colander.String(), title="Nama")
provinsi = colander.SchemaNode(colander.String())
status = colander.SchemaNode(colander.Integer(),width="30pt")
class ViewDati2(BaseView):
def __init__(self, request):
super(ViewDati2, self).__init__(request)
self.form_scripts = ""
self.list_col_defs = json.dumps(
[{"searchable": False, "visible": False, "targets": [0], }, {
"searchable": True, "orderable": True, "targets": [1, 2],
}])
self.list_cols = [{'title': "ID", 'data': "id"},
{'title': "Provinsi", 'data': "provinsi", 'width': '200pt'},
{'title': "Kode", 'data': "kode", 'width': '100pt'},
{'title': "Nama", 'data': "nama"}, ]
self.list_buttons = 'btn_view, btn_add, btn_edit, btn_delete, ' \
'btn_close'
self.form_params = dict(scripts="")
self.list_url = 'dati2'
self.list_route = 'dati2'
......@@ -113,6 +112,14 @@ class ViewDati2(BaseView):
schema.deserialize(row)
return Form(schema, buttons=buttons)
@view_config(route_name='dati2',
renderer='templates/form_input.pt',
permission='dati2')
def view_list(self):
table = DeTable(ListSchema(title="Kabupaten/Kota"), action=f"{self.home}/dati2",
buttons=(btn_close, btn_add, btn_edit, btn_delete))
return dict(form=table.render(), scripts=self.form_scripts)
@view_config(route_name='dati2-view',
renderer='templates/form_input.pt', permission='dati2')
def view_view(self): # row = query_id(request).first()
......@@ -128,11 +135,11 @@ class ViewDati2(BaseView):
form.set_appstruct(self.get_values(row))
return dict(form=form.render(readonly=True), scripts=self.form_scripts)
@view_config(route_name='dati2',
renderer='templates/list.pt',
permission='dati2')
def view_list(self):
return super().view_list()
# @view_config(route_name='dati2',
# renderer='templates/list.pt',
# permission='dati2')
# def view_list(self):
# return super().viewlist()
@view_config(route_name='dati2-act', renderer='json',
permission='view')
......
......@@ -3,7 +3,7 @@ from datetime import datetime
import colander
from deform import (Form, widget, )
from opensipkd.tools.buttons import btn_cancel, btn_save, btn_close
from opensipkd.tools.buttons import btn_cancel, btn_save, btn_close, btn_add, btn_edit, btn_delete, btn_view
from pyramid.httpexceptions import (HTTPFound, )
from pyramid.view import (view_config, )
from sqlalchemy.orm import aliased
......@@ -11,6 +11,7 @@ from sqlalchemy.orm import aliased
from . import widget_os
from ..models import DBSession, ResProvinsi, kategori_provinsi, flush
from ..views import ColumnDT, DataTables, BaseView
from ...detable import DeTable
SESS_ADD_FAILED = 'Tambah provinsi gagal'
SESS_EDIT_FAILED = 'Edit provinsi gagal'
......@@ -43,21 +44,17 @@ class EditSchema(AddSchema):
widget=widget.HiddenWidget(readonly=True))
class ListSchema(colander.Schema):
id = colander.SchemaNode(colander.Integer(), searchable=False, orderable=False, visible=False)
kode = colander.SchemaNode(colander.String(), width='100pt')
nama = colander.SchemaNode(colander.String())
class ViewProvinsi(BaseView):
def __init__(self, request):
super(ViewProvinsi, self).__init__(request)
self.form_scripts = ""
self.list_col_defs = json.dumps(
[{"searchable": False, "visible": False, "targets": [0], }, {
"searchable": True, "orderable": True, "targets": [1, 2],
}])
self.list_cols = [{'title': "ID", 'data': "id"},
{'title': "Kode", 'data': "kode", 'width': '100pt'},
{'title': "Nama", 'data': "nama"}, ]
self.list_buttons = 'btn_view, btn_add, btn_edit, btn_delete, ' \
'btn_close'
self.form_params = dict(scripts="")
self.list_url = 'provinsi'
self.list_route = 'provinsi'
self.add_schema = AddSchema
self.edit_schema = EditSchema
......@@ -115,10 +112,12 @@ class ViewProvinsi(BaseView):
return dict(form=form.render(readonly=True), scripts=self.form_scripts)
@view_config(route_name='provinsi',
renderer='templates/list.pt',
renderer='templates/list_table.pt',
permission='provinsi')
def view_list(self):
return super().view_list()
table = DeTable(ListSchema(), action=f"{self.home}/provinsi",
buttons=(btn_view, btn_add, btn_edit, btn_delete, btn_close))
return dict(table=table.render(), scripts="")
##########
# Action #
......
<html metal:use-macro="load: ./base3.1.pt"
tal:define="
home request.route_url('home');">
<div metal:fill-slot="content">
<div class="jarviswidget jarviswidget-color-blueLight"> <!-- jarviswidget -->
<div tal:content="structure table"></div>
</div>
</div>
</html>
TODO
- docs
- column formating (left, right, center)
- numeric formating
- date formating
"""Detable."""
import os
from pkg_resources import resource_filename
from . import detable # API
from deform.field import Field # API
from .detable import Button # API
from .detable import DeTable # API
from deform import ZPTRendererFactory # API
from deform import default_renderer # API
deform_templates = resource_filename('deform', 'templates')
path = os.path.dirname(__file__)
path = os.path.join(path, 'templates')
search_path = (path, deform_templates) #,
# renderer = ZPTRendererFactory(search_path)
DeTable.set_zpt_renderer(search_path)
"""Form."""
# Standard Library
import json
import re
from chameleon.utils import Markup
from deform import compat
from deform import field
from . import widget
class DeTable(field.Field):
"""
Field representing an entire form.
Arguments:
schema
A :class:`colander.SchemaNode` object representing a
schema to be rendered. Required.
action
The table action (inserted into the ``ajax_url`` attribute of
the datatable's scripts tag when rendered). Required.
# method
# The form method (inserted into the ``method`` attribute of
# the form's form tag when rendered). Default: ``POST``.
buttons
A sequence of strings or :class:`deform.form.Button`
objects representing submit buttons that will be placed at
the bottom of the form. If any string is passed in the
sequence, it is converted to
:class:`deform.form.Button` objects.
tableid
The identifier for this table. This value will be used as the
HTML ``id`` attribute of the rendered HTML table. You should
pass a string value for ``tableid`` when more than one Detable
table is placed into a single page and both share the same action.
When one of the tables on the page is posted, your code will to
be able to decide which of those tables was posted based on the
differing values of ``__tableid__``. By default,
``tableid`` is ``detable``.
use_ajax
If this option is ``True``, the form will use AJAX (actually
AJAH); when any submit button is clicked, the DOM node related
to this form will be replaced with the result of the form post
caused by the submission. The page will not be reloaded. This
feature uses the ``jquery.form`` library ``ajaxForm`` feature
as per `http://jquery.malsup.com/form/
<http://jquery.malsup.com/form/>`_. Default: ``False``. If
this option is ``True``, the ``jquery.form.js`` library must be
loaded in the HTML page which embeds the form. A copy of it
exists in the ``static`` directory of the ``deform`` package.
ajax_options
A *string* which must represent a JavaScript object
(dictionary) of extra AJAX options as per
`http://jquery.malsup.com/form/#tab3
<http://jquery.malsup.com/form/#tab3>`_. For
example:
.. code-block:: python
'{"success": function (rText, sText, xhr, form) {alert(sText)};}'
Default options exist even if ``ajax_options`` is not provided.
By default, ``target`` points at the DOM node representing the
form and and ``replaceTarget`` is ``true``.
A success handler calls the ``deform.processCallbacks`` method
that will ajaxify the newly written form again. If you pass
these values in ``ajax_options``, the defaults will be
overridden. If you want to override the success handler, don't
forget to call ``deform.processCallbacks``, otherwise
subsequent form submissions won't be submitted via AJAX.
This option has no effect when ``use_ajax`` is False.
The default value of ``ajax_options`` is a string
representation of the empty object.
The :class:`deform.Form` constructor also accepts all the keyword
arguments accepted by the :class:`deform.Field` class. These
keywords mean the same thing in the context of a Form as they do
in the context of a Field (a Form is just another kind of Field).
"""
css_class = "deform" # bw compat only; pass a widget to override
def __init__(
self,
schema,
action,
buttons=(),
tableid="detable",
# use_ajax=False,
# ajax_options="{}",
# autocomplete=None,
# focus="on",
# cols = None,
**kw
):
btn_close_js = "{window.location = '/'; return false;}"
btn_add_js = "{window.location = o%sUri+'/add';}" % tableid
btn_edit_js = "{window.location = o%sUri+'/'+m%sID+'/edit'}" % (tableid, tableid)
btn_view_js = "{window.location = o%sUri+'/'+m%sID+'/view';}" % (tableid, tableid)
btn_delete_js = "{window.location = o%sUri+'/'+m%sID+'/delete';}" % (tableid, tableid)
btn_csv_js = "{window.location = o%sUri+'/csv/act';}" % tableid
btn_pdf_js = "{window.location = o%sUri+'/pdf/act';}" % tableid
field.Field.__init__(self, schema, **kw)
_buttons = []
for button in buttons:
if isinstance(button, compat.string_types):
button = Button(button)
_buttons.append(button)
buttons = _buttons
_buttons = []
_scripts = []
for button in buttons:
_buttons.append(
f"""<button
id="{tableid}{button.name}"
name="{button.name}"
type="{button.type}"
class="btn {button.css_class}">
{button.title} </button>
""")
_scripts.append(f'$("#{tableid + button.name}").click(function ()' +
eval('btn_' + button.name + '_js')+');')
# <span tal:condition="button.icon" class="glyphicon glyphicon-${button.icon}"></span>
self.buttons = "','".join(_buttons).replace('\n', "").replace(';',';\n')
self.tableid = tableid
self.scripts = ''.join(_scripts).replace(';', ";\n")
table_widget = getattr(schema, "widget", None)
if table_widget is None:
table_widget = widget.TableWidget()
self.widget = table_widget
columns = []
for f in schema:
d = {'data': f.name}
if hasattr(f, 'width'):
d["width"] = f.width
if hasattr(f, 'text_align'):
d["text-align"] = f.align
if hasattr(f, 'searchable'):
d["searchable"] = f.searchable
if hasattr(f, 'visible'):
d["visible"] = f.visible
if hasattr(f, 'orderable'):
d["orderable"] = f.orderable
columns.append(d)
self.columns = json.dumps(columns)
self.url = action
class Button(object):
"""
A class representing a form submit button. A sequence of
:class:`deform.widget.Button` objects may be passed to the
constructor of a :class:`deform.form.Form` class when it is
created to represent the buttons renderered at the bottom of the
form.
Arguments:
name
The string or unicode value used as the ``name`` of the button
when rendered (the ``name`` attribute of the button or input
tag resulting from a form rendering). Default: ``submit``.
title
The value used as the title of the button when rendered (shows
up in the button inner text). Default: capitalization of
whatever is passed as ``name``. E.g. if ``name`` is passed as
``submit``, ``title`` will be ``Submit``.
type
The value used as the type of button. The HTML spec supports
``submit``, ``reset`` and ``button``. A special value of
``link`` will create a regular HTML link that's styled to look
like a button. Default: ``submit``.
value
The value used as the value of the button when rendered (the
``value`` attribute of the button or input tag resulting from
a form rendering). If the button ``type`` is ``link`` then
this setting is used as the URL for the link button.
Default: same as ``name`` passed.
icon
glyph icon name to include as part of button. (Ex. If you
wanted to add the glyphicon-plus to this button then you'd pass
in a value of ``plus``) Default: ``None`` (no icon is added)
disabled
Render the button as disabled if True.
css_class
The name of a CSS class to attach to the button. In the default
form rendering, this string will replace the default button type
(either ``btn-primary`` or ``btn-default``) on the the ``class``
attribute of the button. For example, if ``css_class`` was
``btn-danger`` then the resulting default class becomes
``btn btn-danger``. Default: ``None`` (use default class).
attributes
HTML5 attributes passed in as a dictionary. This is especially
useful for a Cancel button where you do not want the client to
validate the form inputs, for example
``attributes={"formnovalidate": "formnovalidate"}``.
"""
def __init__(
self,
name="view",
oid=None,
title=None,
type="button", # noQA
css_class=None,
icon=None,
attributes=None,
disabled=None
):
if attributes is None:
attributes = {}
if title is None:
title = name.capitalize()
name = re.sub(r"\s", "_", name)
if oid is None:
self.oid = f"detable_btn_{name}"
self.name = name
self.title = title
self.type = type # noQA
self.disabled = disabled
self.css_class = css_class
self.icon = icon
self.attributes = attributes
"""I18n."""
from translationstring import TranslationStringFactory
_ = TranslationStringFactory("detable")
<div tal:define="style style|field.widget.style;
css_class css_class|string:${field.widget.css_class or field.css_class or 'jarviswidget jarviswidget-color-blueLight'};
item_template item_template|field.widget.item_template;
title title|field.title;
errormsg errormsg|field.errormsg;
description description|field.description;
buttons buttons|field.buttons;
tableid tableid|field.tableid;
columns columns|field.columns;
btnscripts scripts|field.scripts;
url url|field.url"
tal:attributes="style style;
class css_class;
attributes|field.widget.attributes|{};"
i18n:domain="detable">
<header role="heading" class="txt-color-grayDark">
<h2 tal:condition="title">
<i class="fa fa-fw fa-table"></i>${title}
</h2>
<div role="content">
<div class="widget-body">
<table id="${tableid}"
class="table table-bordered table-hover table-condensed dataTable no-footer">
<thead>
<tr>
<th tal:repeat="child field">${child.title}
</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div> <!-- widget-body -->
</div>
</header>
<script type="text/javascript">
deform.addCallback(
'${tableid}',
function (oid) {
var m${tableid}ID;
var o${tableid};
var o${tableid}Uri = "${url}"
var o${tableid}Url = o${tableid}Uri + "/grid/act";
var tb_array = [
'<div class="btn-group pull-left">',
'${structure:buttons}',
' &nbsp;',
'</div>',
]
var language = {
"search": "Cari: ",
"paginate": {
"first": '<span class="glyphicon glyphicon-step-backward"></span> ',
"last": '<span class="glyphicon glyphicon glyphicon-step-forward"></span> ',
"previous": '<span class="glyphicon glyphicon-backward"></span> ',
"next": '<span class="glyphicon glyphicon-forward"></span> ',
},
"lengthMenu": " _MENU_ baris "
};
o${tableid} = $('#${tableid}').DataTable({
dom: '<"row"<"col-md-8"<"toolbar">Bl><"col-md-4"fr>>tip',
processing: true,
serverSide: true,
ajax: o${tableid}Url,
stateSave: true,
scrollCollapse: true,
sort: true,
info: false,
filter: true,
autoWidth: false,
paginate: true,
paginationType: "full_numbers",
lengthMenu: [
[10, 25, 50, 100],
[10, 25, 50, 100]
],
//columnDefs: columnDefs,
columns: ${columns},
"language": language,
});
var tb = tb_array.join(' ');
$("div.toolbar").html(tb);
$("div.toolbar").attr('style', 'display:block; float: left; margin-bottom:6px; line-height:16px;');
$('#${tableid} tbody').on('click', 'tr', function () {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
} else {
var aData = o${tableid}.row(this).data();
o${tableid}.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
m${tableid}ID = aData.id;
// console.log(mID);
o${tableid}.$('tr.row_selected').removeClass('row_selected');
$(this).addClass('row_selected');
}
});
${structure:btnscripts}
});
</script>
</div>
\ No newline at end of file
"""Widget."""
# Standard Library
import csv
import json
import random
# Pyramid
from colander import Invalid
from colander import Mapping
from colander import SchemaNode
from colander import SchemaType
from colander import Sequence
from colander import String
from colander import null
from iso8601.iso8601 import ISO8601_REGEX
from translationstring import TranslationString
from deform.widget import MappingWidget
from deform.compat import text_
from .i18n import _
_BLANK = text_("")
class TableWidget(MappingWidget):
"""
The top-level widget; represents an entire table.
**Attributes/Arguments**
template
The template name used to render the widget. Default:
``detable``.
"""
template = "detable"
readonly_template = "readonly/detable"
sqlalchemy
zope
pyramid
colander
deform
\ No newline at end of file
sqlalchemy~=1.4.22
zope.sqlalchemy
pyramid~=2.0
pyramid_beaker
colander~=1.8.3
deform~=2.0.15
transaction~=3.0.1
colorama~=0.4.4
Pillow~=8.3.1
lxml~=4.6.3
beautifulsoup4~=4.9.3
soupsieve~=2.2.1
pip~=18.1
wheel~=0.37.0
rsa~=4.7.2
pyasn1~=0.4.8
Chameleon~=3.9.1
six~=1.16.0
Mako~=1.1.4
Babel~=2.9.1
Beaker~=1.11.0
Pygments~=2.9.0
MarkupSafe~=2.0.1
Genshi~=0.7.5
pytz~=2021.1
WebOb~=1.8.7
translationstring~=1.4
peppercorn~=0.6
iso8601~=0.1.16
google~=3.0.0
cachetools~=4.2.2
certifi~=2021.5.30
urllib3~=1.26.6
requests~=2.25.1
google-api-python-client~=2.15.0
python-dateutil~=2.8.2
alembic~=1.6.5
passlib~=1.7.4
venusian~=3.0.0
plaster~=1.0
hupper~=1.10.3
waitress~=2.0.0
greenlet~=1.1.1
pyparsing~=2.4.7
httplib2~=0.19.1
icecream~=2.1.1
executing~=0.8.0
paginate~=0.5.6
idna~=2.10
chardet~=4.0.0
asttokens~=2.0.5
setuptools~=57.0.0
uritemplate~=3.0.1
zipp~=3.5.0
reportlab~=3.5.68
PyJWT~=2.1.0
qrcode~=6.1
\ No newline at end of file
......@@ -61,15 +61,16 @@ setup(
extras_require={
'dev': dev_requires,
},
package_data={'opensipkd': ['base/views/templates/*.pt',
package_data={'opensipkd': [
'base/views/templates/*.pt',
'base/static/*.*',
'base/reports/*.*',
'base/alembic/*.*',
'base/alembic/versions/*.*',
'base/views/*.tpl',
],},
data_files=[('etc',['etc/live_opensipkd.tpl',
'etc/test_opensipkd.tpl',])],
], },
data_files=[('etc', ['etc/live_opensipkd.tpl',
'etc/test_opensipkd.tpl', ])],
include_package_data=True,
entry_points="""\
[paste.app_factory]
......
......@@ -71,7 +71,7 @@ if __name__ == "__main__":
from opensipkd.base.models import query_table
res = query_table('routes', ['id', 'nama', 'kode'],
[('|', ('nama', 'ilike', "a%"),
('kode', 'ilike', 'g%'))])
[ ('nama', 'ilike', "a%"),
('kode', 'ilike', 'g%')])
for row in res.all():
print(row.id, row.nama, row.kode)
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!