Commit 8e3bd313 by aagusti

perbaikan detable

1 parent 2d066240
......@@ -2,8 +2,6 @@ import locale
import logging
import re
# from opensipkd.base.tools.this_framework import api_has_permission_
try:
from urllib import (urlencode, quote, quote_plus, )
except ImportError:
......@@ -11,15 +9,8 @@ except ImportError:
from pyramid.config import Configurator
from pyramid_beaker import session_factory_from_settings
from pyramid.authentication import AuthTktAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
from pyramid.events import subscriber
from pyramid.events import BeforeRender
from pyramid.interfaces import IRoutesMapper
from pyramid.httpexceptions import (
default_exceptionresponse_view,
HTTPFound,
)
from pyramid.renderers import JSON
from pyramid_mailer import mailer_factory_from_settings
import datetime, decimal
......@@ -130,7 +121,8 @@ def get_params(params, alternate=None, settings=None):
if not settings:
settings = get_settings()
result = settings and params in settings and settings[params].strip() or None
result = settings and params in settings and \
settings[params].strip() or None
if not result:
row = Parameter.query_kode(params).first()
result = row and row.value or None
......@@ -138,6 +130,26 @@ def get_params(params, alternate=None, settings=None):
return result and result or alternate
def get_ini(request, var):
settings = get_settings()
if var in settings and settings[var]:
return settings[var]
return
def get_ini_params(request, params=None, alternate=None, settings=None):
"""
Digunakan untuk mengambil nilai dari konfigurasi sesuai params yang disebut
:param params: variable
:param alternate: default apabila tidak ditemukan data/params
:param settings: default settings
:return: value
contoh penggunaan:
get_params('devel', False)
"""
return get_params(params, alternate, settings)
def allow_register(request):
allow = get_params('allow_register', 'false')
return allow == 'true' or allow == "True" or allow == True
......@@ -182,13 +194,6 @@ def get_app_name(request):
return get_params('app_name', 'openSIPKD Application')
def get_ini(request, var):
settings = get_settings()
if var in settings and settings[var]:
return settings[var]
return
def is_devel(request):
return get_params('devel') == 'true'
......@@ -217,7 +222,8 @@ def get_modules(settings=None):
setting = settings
settings = setting
modules = 'modules' in settings and settings['modules'] and settings['modules'].split(',') or []
modules = 'modules' in settings and settings['modules'] and settings[
'modules'].split(',') or []
result = {}
for modul in modules:
if not 'opensipkd.base' in modul:
......@@ -322,22 +328,23 @@ def json_rpc():
def get_host(request):
host = get_params('_host', "")
if not host:
proto = 'HTTP_X_FORWARDED_PROTO' in request.environ \
and request.environ['HTTP_X_FORWARDED_PROTO'] \
or "http"
host = f"{proto}://{request.host}"
return host
# if not host:
# host = request.route_url('home')[:-1]
# proto = 'HTTP_X_FORWARDED_PROTO' in request.environ \
# and request.environ['HTTP_X_FORWARDED_PROTO'] \
# or "http"
# host = f"{proto}://{request.host}"
return host and host or get_home(request)
def get_home(request):
return request.route_url('home')
return request.route_url('home')[:-1]
def set_routes(config, app_id=None):
q = DBSession.query(Route)
if not app_id:
q.filter(or_(Route.app_id == 0, Route.app_id == None))
q.filter(or_(Route.app_id == 0, None == Route.app_id))
else:
q.filter(Route.app_id == app_id)
......@@ -350,7 +357,10 @@ def set_routes(config, app_id=None):
config.add_jsonrpc_endpoint(route.kode, route.path,
default_renderer="json_rpc")
partner_idcard_folder = 'partner/idcard'
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
......@@ -372,7 +382,6 @@ def main(global_config, **settings):
config = Configurator(settings=settings,
root_factory='opensipkd.models.RootFactory',
session_factory=session_factory)
from opensipkd.models import RootFactory
modules = get_modules(settings)
from importlib import import_module
for module in modules:
......@@ -403,18 +412,25 @@ def main(global_config, **settings):
config.add_request_method(get_home, 'home', reify=True)
# config.add_request_method(api_has_permission_, 'api_has_permission', reify=True)
config.add_request_method(google_signin_client_id, 'google_signin_client_id', reify=True)
config.add_request_method(google_signin_client_ids, 'google_signin_client_ids', reify=True)
config.add_request_method(google_signin_client_id,
'google_signin_client_id', reify=True)
config.add_request_method(google_signin_client_ids,
'google_signin_client_ids', reify=True)
config.add_request_method(allow_register, 'allow_register', reify=True)
config.add_request_method(disable_responsive, 'disable_responsive', reify=True)
config.add_request_method(get_params, 'get_params', reify=True)
config.add_static_view('static', 'opensipkd.base:static', cache_max_age=3600)
config.add_request_method(disable_responsive, 'disable_responsive',
reify=True)
# config.add_request_method(get_params, 'get_params', reify=True)
# config.add_request_method(get_ini_params, 'get_ini', reify=True)
config.add_request_method(get_ini, 'get_ini', reify=True)
config.add_static_view('static', 'opensipkd.base:static',
cache_max_age=3600)
config.add_static_view(partner_idcard_folder,
get_params("partner_idcard_folder", '/tmp/idcard'),
cache_max_age=3600)
config.add_static_view('deform_static', 'deform:static')
captcha_files = get_params('captcha_files', settings=settings, alternate="/tmp/captcha")
captcha_files = get_params('captcha_files', settings=settings,
alternate="/tmp/captcha")
if not os.path.exists(captcha_files):
os.makedirs(captcha_files)
......
kode,url,nama,status,order_id,parent_id/menus.kode,icon,need_login,class_name
login,/login,Login,1,1,,,False,
reset-password,/reset/password,Reset Password,1,1,,,False,
register,/register,Register,1,1,,,False,
base,/,Base,1,1,,,False,
logout,/logout,Logout,1,2,base,,,
change-password,/password/{code},Ubah password,1,3,base,,,
menus,/menu,Menus,1,4,base,,,
departemen,/departemen,Departemen,1,5,base,,,
profile,/profile,Profile,1,6,base,,,
group,/group,Daftar group,1,7,base,,,
routes,/routes,Routes,1,8,base,,,
parameter,/parameter,Parameter,1,9,base,,,
partner,/partner,Partner,1,10,base,,,
departemen,/departemen,Organisasi,1,11,base,,,
permission,/permission,Daftar Permission,1,12,base,,,
eselon,/eselon,Daftar Eselon,1,13,base,,,
jabatan,/jabatan,Daftar Jabatan,1,14,base,,,
partner-departemen,/partner/departemen,Partner Departemen,1,15,base,,,
log,/log,Log Aplikasi,1,16,base,,,
provinsi,/provinsi,Provinsi,1,17,base,,,
dati2,/dati2,Kabupaten/Kota,1,18,base,,,
kecamatan,/kecamatan,Kecamatan,1,19,base,,,
desa,/desa,Desa/Kelurahan,1,20,base,,,
company,/company,Pemda,1,21,base,,,
g
248/04.06.2022 (DISC : 0 %)
E 6281 JL 0944061/R1/11400-14/PU/VI/22 SUMBER
SATAR 32092307077XXXXX/ P.U.
BLOK KLANGENAN RT 03 RW 02 DS KLANGENAN- [11]
KEC KLANGENAN CRBN-KLANGENAN 0 0 0
HONDA/E1F02N11M2 A/T 232.800 0 232.800
SEPEDA MOTOR BENSIN 35.000 0 35.000
2017/2017 HITAM 100.000 0 100.000
HITAM 1 : 1,75 60.000 0 60.000
125 CC 427.800 0 427.800
MH1JFU119HK892552
JFU1E1897403 R01183325 04 Jun 2022 REZA F., A.MD.A.PJ. GINA & EKO
N03125569 [12:51:31#04-06-2022#E 6281 JL #Rp. 427.800<GINA & EKO>DPD#BJB 000]
05 Jun 2023
KETERANGAN:
bea_bbnkb_pok = bea_bbnkb1_pok+bea_bbnkb1_tgk+bea_bbnkb2_pok+bea_bbnkb2_tgk
bea_pkb_pok = bea_pkb_pok+ bea_pkb_tgk1+ bea_pkb_tgk2+ bea_pkb_tgk3+ bea_pkb_tgk4
bea_swdkllj_pok = bea_swdkllj_pok+ bea_swdkllj_tgk1+ bea_swdkllj_tgk2+ bea_swdkllj_tgk3+ bea_swdkllj_tgk4
jumlah_pokok = bea_bbnkb_pok + bea_pkb_pok + bea_swdkllj_pok + bea_adm_stnk + bea_adm_tnkb
bea_bbnkb_den = bea_bbnkb1_den + bea_bbnkb2_den
bea_pkb_den = bea_pkb_den+ bea_pkb_den1 + bea_pkb_den2 + bea_pkb_den3 + bea_pkb_den4
bea_swdkllj_den = bea_swdkllj_den + bea_swdkllj_den1+ bea_swdkllj_den2+ bea_swdkllj_den3+ bea_swdkllj_den4
jumlah_den = bea_bbnkb_den + bea_pkb_den + bea_swdkllj_den
jumlah_bbnkb = bea_bbkb_pok + bea_bbnkb_den
jumlah_pkb = bea_pkb_pok + bea_pkb_den
jumlah_swdkllj = bea_swdkllj_pok + bea_swdkllj_den
jumlah_total = jumlah_pokok + jumlah_den
========================================================================================================================================
g
{no_urut_daf}/{tg_pros_daftar}(DISC : {no_kohir2} %)
{no_polisi} {no_kohir1} {nm_wil}
{nm_pemilik} {no_ktp}/{npwpd} {no_kohir1[4]}
{al_pemilik}- [{kd_fungsi}]
{al_pemilik2}-{nm_kecamatan} {bea_bbnkb_pok} {bea_bbnkb_den} {jumlah_bbnkb}
{nm_merek_kb}/{nm_model_kb} {bea_pkb_pok} {bea_pkb_den} {jumlah_pkb}
{nm_jenis_kb} {bbm} {bea_swdkllj_pok} {bea_swdkllj_den} {jumlah_swdkllj}
{th_buatan}/{th_rakitan} {warna_tnkb} {bea_adm_stnk} 0 {bea_adm_stnk}
{warna_kb} {milik_ke} : {tarif} {bea_adm_tnkb} 0 {bea_adm_tnkb}
{jumlah_cc} CC {jumlah_pokok} {jumlah_den} {jumlah_total}
{no_rangka}
{no_mesin} {kd_merek_kb} {tg_pros_tetap} {nm_operator} {nm_kasir}
{no_bpkb} [{jam_proses}#{tg_pros_bayar}#{no_polisi}#Rp. {jumlah_total}<{nm_kasir}>DPD#BJB {kd_bank}]
{tg_akhir_pajak}
kode,path,nama,status,type
home,/,Home,0
home-auth,/home,Home Auth,0
home,/,Home,1
home-auth,/home,Home Auth,1
home-about,/home/{id}/about,Home About
login,/login,Login,0
logout,/logout,,0
login,/login,Login,1,0
logout,/logout,Logout,1,0
rpc,/rpc,RPC,1,1
change-password,/password/{code},Ubah password,0
password,/password,Ubah password,0
reset-password,/reset-password,0
reset-password-sent,/reset-password-sent,0
menu,/menu,Menus,0
menu-act,/menu/{act}/act,Menus Act,0
menu-add,/menu/add,Menus Add,0
menu-edit,/menu/{id}/edit,Menus Edit,0
menu-delete,/menu/{id}/delete,Menus Delete,0
menu-view,/menu/{id}/view,Menus View,0
user,/user,Daftar User
user-add,/user/add,Tambah User
user-edit,/user/{id}/edit,Edit User
......
......@@ -14,12 +14,12 @@ from pyramid.paster import (get_appsettings, setup_logging, )
from opensipkd.models.handlers import LogDBSession
from opensipkd.models import (
init_model, DBSession, Base, Group, UserGroup, Permission, GroupPermission,
User, Route, Eselon, Jabatan, ResProvinsi, ResDati2, ResKecamatan, ResDesa)
User, Route, Eselon, Jabatan, ResProvinsi, ResDati2, ResKecamatan, ResDesa,
Menus)
from sqlalchemy.dialects import oracle
from sqlalchemy import text
# , mssql
# from .tools import mkdir
......@@ -32,10 +32,12 @@ def usage(argv):
def create_schema(engine, schema):
sql = select([text('schema_name')]).select_from(text('information_schema.schemata')).where(
sql = select([text('schema_name')]).select_from(
text('information_schema.schemata')).where(
text("schema_name = '%s'" % schema))
if isinstance(engine.dialect, oracle.dialect):
sql = select(['owner']).select_from('dba_segments').where("owner = '%s'" % schema.upper())
sql = select(['owner']).select_from('dba_segments').where(
"owner = '%s'" % schema.upper())
print(sql)
q = engine.execute(sql)
if not q.fetchone():
......@@ -250,7 +252,9 @@ def alembic_run(ini_file, name=None):
bin_path = os.path.split(sys.executable)[0]
alembic_bin = os.path.join(bin_path, 'alembic')
if not name:
command = (alembic_bin, '-c', ini_file, '-n', 'alembic_ziggurat', 'upgrade', 'head')
command = (
alembic_bin, '-c', ini_file, '-n', 'alembic_ziggurat', 'upgrade',
'head')
if subprocess.call(command) != 0:
sys.exit()
else:
......@@ -258,6 +262,7 @@ def alembic_run(ini_file, name=None):
if subprocess.call(command) != 0:
sys.exit()
def alembic_run(ini_file, name='alembic_base'):
bin_path = os.path.split(sys.executable)[0]
alembic_bin = os.path.join(bin_path, 'alembic')
......@@ -265,6 +270,7 @@ def alembic_run(ini_file, name='alembic_base'):
if subprocess.call(command) != 0:
sys.exit()
def base_alembic_run(ini_file):
alembic_run(ini_file)
......@@ -296,8 +302,10 @@ def main(argv=sys.argv):
append_csv(Group, 'groups.csv', ['group_name'])
restore_csv(UserGroup, 'users_groups.csv')
append_csv(Permission, 'permissions.csv', ['perm_name'])
append_csv(GroupPermission, 'group_permission.csv', ['group_id', 'perm_name'])
append_csv(GroupPermission, 'group_permission.csv',
['group_id', 'perm_name'])
append_csv(Route, 'routes.csv', ['kode'])
append_csv(Menus, 'menus.csv', ['kode'])
append_csv(Eselon, 'eselon.csv', ['kode'])
append_csv(Jabatan, 'jabatan.csv', ['kode'])
restore_csv(ResProvinsi, 'provinsi.csv')
......
import logging
from opensipkd.base.tools.api import rpc_auth
from opensipkd.tools.api import JsonRpcInvalidLoginError
from pyramid.renderers import render_to_response
from opensipkd.models import (
User,
UserGroup,
DBSession,
)
from opensipkd.models import (User, UserGroup, DBSession, )
log = logging.getLogger(__name__)
# It is used by RootFactory
def group_finder(user_id, request):
if user_id != 'None':
q = DBSession.query(User).filter_by(id=user_id)
......
import json
from datetime import timedelta, timezone, tzinfo
from opensipkd.models import (DBSession, User, GroupPermission, UserDeviceModel)
import requests
from opensipkd.tools import (
get_random_number, devel, get_random_string, get_settings, DefaultTimeZone, get_params, get_timezone)
devel, get_random_string)
from opensipkd.tools.api import *
from opensipkd.models import (DBSession, User, GroupPermission, UserDeviceModel)
import logging
log = logging.getLogger(__name__)
lima_menit = 300
#
def auth_from_rpc(request):
return auth_from(request)
def rpc_auth(request):
return auth_from(request)
def auth_from(request, field=None):
global lima_menit
env = request.environ
......@@ -49,6 +38,13 @@ def auth_from(request, field=None):
raise JsonRpcInvalidLoginError
return user
def auth_from_rpc(request):
return auth_from(request)
def rpc_auth(request):
return auth_from(request)
# def auth_from_token(request):
# return auth_from(request, "security_code")
......
import logging
import os
import re
from datetime import datetime
from datatables import ColumnDT
from dateutil.relativedelta import relativedelta
from opensipkd.tools.api import JsonRpcInvalidLoginError
from opensipkd.tools.form_api import formfield2dict
from pyramid.security import remember
from opensipkd.tools.captcha import get_captcha
from pyramid.httpexceptions import HTTPFound
......@@ -11,15 +17,19 @@ from .common import DataTables
from .. import DBSession, get_params
from opensipkd.tools import dmy, dmy_to_date, get_settings, get_ext
import colander
from deform import (widget, Form, ValidationFailure, )
from deform import (widget, Form, ValidationFailure, Button, )
from email.utils import parseaddr
from opensipkd.tools.buttons import btn_save, btn_cancel, btn_close, btn_delete, btn_view, btn_add, btn_edit, btn_csv, \
from opensipkd.tools.buttons import btn_save, btn_cancel, btn_close, btn_delete, \
btn_view, btn_add, btn_edit, btn_csv, \
btn_pdf
from opensipkd.models import User
from opensipkd.models import User, Menus
from ..tools.api import auth_from_rpc
from ...detable import DeTable
log = logging.getLogger(__name__)
class BaseView(object):
def __init__(self, request):
......@@ -138,7 +148,8 @@ class BaseView(object):
if msg:
self.ses.flash(msg, error)
if self.headers:
return HTTPFound(location=self.req.route_url(self.list_route), headers=self.headers)
return HTTPFound(location=self.req.route_url(self.list_route),
headers=self.headers)
else:
return HTTPFound(location=self.req.route_url(self.list_route))
......@@ -148,7 +159,8 @@ class BaseView(object):
def get_params(self, params, default=None):
return get_params(params, default)
def get_form(self, class_form, row=None, buttons=(btn_save, btn_cancel), **kwargs):
def get_form(self, class_form, row=None, buttons=(btn_save, btn_cancel),
**kwargs):
buttons = self.buttons and self.buttons or buttons
if "bindings" in kwargs and kwargs["bindings"]:
bindings = kwargs["bindings"]
......@@ -173,12 +185,14 @@ class BaseView(object):
def view_list(self, arg=None):
if self.list_schema:
table = DeTable(self.list_schema(), action=self.req.route_url(self.list_route),
table = DeTable(self.list_schema(),
action=self.req.route_url(self.list_route),
action_suffix="/grid/act",
buttons=self.list_buttons)
resources = table.get_widget_resources()
# resources=dict(css="", js="")
return dict(form=table.render(), scripts="", css=resources["css"], js=resources["js"])
return dict(form=table.render(), scripts="", css=resources["css"],
js=resources["js"])
arg = arg and arg or {}
arg.update(url=self.list_url, col_defs=self.list_col_defs,
......@@ -194,13 +208,15 @@ class BaseView(object):
if not row:
return self.id_not_found()
bindings = self.get_bindings(row)
form = self.get_form(self.edit_schema, buttons=(btn_close,), bindings=bindings)
form = self.get_form(self.edit_schema, buttons=(btn_close,),
bindings=bindings)
if request.POST:
return self.route_list()
form.set_appstruct(self.get_values(row))
table = self.get_item_table(row)
return dict(form=form.render(readonly=True), table=table and table.render() or None,
return dict(form=form.render(readonly=True),
table=table and table.render() or None,
scripts=self.form_scripts)
def before_add(self):
......@@ -226,20 +242,25 @@ class BaseView(object):
if url_dict['act'] == 'grid':
columns = []
for d in self.list_schema():
global_search = hasattr(d, "searchable") and hasattr(d, "searchable") == False and False or True
global_search = hasattr(d, "searchable") and hasattr(d,
"searchable") == False and False or True
if hasattr(d, "field"):
if type(d.field) == str:
columns.append(
ColumnDT(getattr(self.table, d.field), mData=d.name, global_search=global_search))
ColumnDT(getattr(self.table, d.field), mData=d.name,
global_search=global_search))
else:
columns.append(ColumnDT(d.field, mData=d.name))
else:
columns.append(ColumnDT(getattr(self.table, d.name), mData=d.name))
columns.append(
ColumnDT(getattr(self.table, d.name), mData=d.name))
query = DBSession.query().select_from(self.table)
query = self.list_join(query)
if self.req.user and self.req.user.company_id and hasattr(self.table, "company_id"):
query = query.filter(self.table.company_id == self.req.user.company_id)
if self.req.user and self.req.user.company_id and hasattr(
self.table, "company_id"):
query = query.filter(
self.table.company_id == self.req.user.company_id)
row_table = DataTables(self.req.GET, query, columns)
return row_table.output_result()
else:
......@@ -278,12 +299,6 @@ class BaseView(object):
scripts=self.form_scripts, css=resources["css"],
js=resources["js"])
def before_save(self, row, values):
return row
def after_save(self, row, values):
pass
def save(self, values, user, row=None):
self.ses["old_email"] = user and user.email or None
if not row:
......@@ -296,10 +311,8 @@ class BaseView(object):
row.from_dict(values)
row.status = 'status' in values and values['status'] and 1 or 0
row = self.before_save(row, values)
DBSession.add(row)
DBSession.flush()
self.after_save(row, values)
return row
def save_request(self, values, row=None):
......@@ -347,8 +360,10 @@ class BaseView(object):
controls = form.validate(controls)
except ValidationFailure as e:
form.set_appstruct(e.cstruct)
return dict(form=form.render(), table=table and table.render() or None,
scripts=self.form_scripts, css=resources["css"], js=resources["js"])
return dict(form=form.render(),
table=table and table.render() or None,
scripts=self.form_scripts, css=resources["css"],
js=resources["js"])
c = dict(controls)
self.save_request(c, row)
return self.route_list()
......@@ -356,7 +371,8 @@ class BaseView(object):
form.set_appstruct(values)
form = self.before_edit(form)
return dict(form=form.render(), table=table and table.render() or None,
scripts=self.form_scripts, css=resources["css"], js=resources["js"])
scripts=self.form_scripts, css=resources["css"],
js=resources["js"])
def before_delete(self, row):
pass
......@@ -381,8 +397,10 @@ class BaseView(object):
table = self.get_item_table(row)
resources = form.get_widget_resources()
form.set_appstruct(self.get_values(row))
return dict(form=form.render(readonly=True), table=table and table.render() or None,
scripts=self.form_scripts, css=resources["css"], js=resources["js"])
return dict(form=form.render(readonly=True),
table=table and table.render() or None,
scripts=self.form_scripts, css=resources["css"],
js=resources["js"])
def query_id(self):
q = DBSession.query(self.table).filter_by(
......@@ -393,7 +411,8 @@ class BaseView(object):
def filter_company(self, query):
if self.req.user.company_id:
return query.filter(self.table.company_id == self.req.user.company_id)
return query.filter(
self.table.company_id == self.req.user.company_id)
return query
def next_add(self, form):
......@@ -405,6 +424,8 @@ class BaseView(object):
return self.route_list()
@colander.deferred
def deferred_status(node, kw):
values = kw.get('daftar_status', [])
......@@ -422,7 +443,6 @@ class Store(dict):
return ""
username_re = re.compile('^[a-z0-9_]{6,16}$', re.IGNORECASE)
......
from datetime import datetime
from deform import ValidationFailure, Form, Button
from icecream import ic
from opensipkd.models import flush, DBSession, Menus
from pyramid_rpc.jsonrpc import JsonRpcError
from opensipkd.tools.form_api import formfield2dict
class BaseApi(object):
def __init__(self, request):
self.request = request
self.url = request.home + '/rpc/pbb/eta'
self.add_schema = {}
self.buttons = ()
self.data = {}
def get_form(self, class_form, row=None, **kwargs):
bindings = kwargs.get("bindings")
validator = kwargs.get("validator")
if "action" in kwargs:
action = kwargs.get("action", "")
else:
action = self.url
if validator:
schema = class_form(validator=validator)
else:
schema = class_form() # validator=validator
if bindings:
schema = schema.bind(request=self.request, **bindings)
else:
schema = schema.bind(request=self.request)
if row:
schema.deserialize(row)
return Form(schema, action=action, buttons=self.buttons)
def validate_field(self, form):
resp = {}
controls = ((k, v) for k, v in self.data.items())
try:
c = form.validate(controls)
except ValidationFailure as e:
form.set_appstruct(e.cstruct)
resp.update(formfield2dict(form))
message = "\n".join([v for k,v in e.error.asdict().items()])
raise JsonRpcError(message=message, data=resp)
return dict(c)
def get_menu_buttons(self, kode):
qry = Menus.get(kode).order_by(Menus.order_id)
if self.request.user:
qry = qry.filter_by(need_login=True)
else:
qry = qry.filter_by(need_login=False)
buttons = []
for row in qry.all():
if not self.request.user:
if row.need_login:
continue
if row.permissions:
if not self.request.user or not self.request.has_permission(
row.permissions):
continue
buttons.append(Button(row.kode, title=row.nama, type="button",
value=row.kode, icon=row.icon))
return tuple(buttons)
def update_headers(self, headers):
self.request.headers.update(headers)
self.request.response.headers.update(headers)
def _view_add(self, **kwargs):
form = self.get_form(self.add_schema, **kwargs)
if "action" in self.data:
self.action = self.data["action"]
if 'save' == self.action:
values = self.validate_field(form)
row = self.save_request(values)
elif "cancel" == self.action or 'batal' == self.action:
self.cancel_act()
else:
return self.next_add(form)
return self.route_list()
values = self.before_add()
form.set_appstruct(values)
return form
def view_add_api(self, **kwargs):
form = self._view_add(**kwargs)
resp = formfield2dict(form)
return dict(data=resp)
def save(self, values, user, row=None):
self.ses["old_email"] = user and user.email or None
if not row:
row = self.table()
row.created = datetime.now()
row.create_uid = user and user.id or None
else:
row.updated = datetime.now()
row.update_uid = user and user.id or None
row.from_dict(values)
row.status = 'status' in values and values['status'] and 1 or 0
flush(row)
return row
def save_request(self, values, row=None):
for k, v in self.request.GET.items():
if k not in values:
if v:
values[k] = v
return self.save(values, self.request.user, row)
def id_not_found(self):
msg = f"Data yang dicari Tidak Ditemukan ID:" \
f" {self.request.matchdict['id']}."
self.request.session.flash(msg, 'error')
return self.route_list()
def next_add(self, form):
"""
Digunakan untuk memverifikasi button yang lainnya
:param form: Object Form
:return:
"""
return self.route_list()
def query_id(self):
q = DBSession.query(self.table).filter_by(
id=self.req.matchdict['id'])
if hasattr(self.table, 'company_id') and self.req.user.company_id:
q = q.filter_by(company_id=self.req.user.company_id)
return q
def route_list(self, msg=None, error=""):
if msg:
self.ses.flash(msg, error)
if self.headers:
return HTTPFound(location=self.req.route_url(self.list_route),
headers=self.headers)
else:
return HTTPFound(location=self.req.route_url(self.list_route))
def before_add(self):
return {}
\ No newline at end of file
import colander
from deform import (widget, )
from opensipkd.models import Menus, flush
from pyramid.view import (view_config, )
from sqlalchemy.orm import aliased
from ..views import ColumnDT, DataTables, BaseView
SESS_ADD_FAILED = 'Tambah menu gagal'
SESS_EDIT_FAILED = 'Edit menu gagal'
def get_menu_list():
q = Menus.get_list()
return [(str(row.id), f"{row.kode}/ {row.nama}") for row in q]
@colander.deferred
def menu_widget(node, kw):
values = kw.get('menu_list', [])
return widget.Select2Widget(values=values)
class AddSchema(colander.Schema):
# parent_id = colander.SchemaNode(
# colander.Integer(),
# widget=widget.HiddenWidget(), oid="parent_id", missing=colander.drop,
# )
#
# parent_nm = colander.SchemaNode(
# colander.String(), missing=colander.drop,
# widget=widget.AutocompleteInputWidget(
# size=60, min_length=3,
# requirements=(("typeahead", None), ("deform", None),
# {"js": "opensipkd.base:static/js/form/menu.js"})
# ),
# oid="parent_nm", title="Parent")
# parent_kd = colander.SchemaNode(colander.String(),
# widget=widget.TextInputWidget(css_class="readonly"),
# missing=colander.drop, oid="parent_kd", title="Kode Induk")
kode = colander.SchemaNode(colander.String(),
validator=colander.Length(max=32), oid="kode")
nama = colander.SchemaNode(colander.String(), oid="nama")
status = colander.SchemaNode(colander.Boolean(), oid="status")
def after_bind(self, schema, kwargs):
request = kwargs["request"]
# self["parent_nm"] = colander.SchemaNode(
# colander.String(),
# missing=colander.drop,
# widget=AutocompleteInputWidget(
# size=60, min_length=3,
# values=f"{request.route_url('menu')}/hon/act"),
# oid="parent_nm",
# title="Induk", )
# self["parent_nm"].widget = widget.AutocompleteInputWidget(
# size=60, min_length=3,
# requirements=(("typeahead", None), ("deform", None),
# {"js": "opensipkd.base:static/js/form/menu.js"}),
# values=f"{request.route_url('menu')}/hon/act")
# if request.user.company_id:
# self["company_id"].widget = widget.HiddenWidget()
# self["company_id"].default = request.user.company_id
class EditSchema(AddSchema):
id = colander.SchemaNode(colander.String(), missing=colander.drop,
widget=widget.HiddenWidget(readonly=True))
class ListSchema(colander.Schema):
id = colander.SchemaNode(colander.String(), title="ID", visible=False)
kode = colander.SchemaNode(colander.String(), title="Kode", width='100pt')
nama = colander.SchemaNode(colander.String(), title="Nama")
status = colander.SchemaNode(colander.Boolean(), title="Status",
width='50pt')
level_id = colander.SchemaNode(colander.String(), title="Level",
width='50pt')
parent = colander.SchemaNode(colander.String(), title="Induk",
width='200pt')
class ViewMenus(BaseView):
def __init__(self, request):
super(ViewMenus, self).__init__(request)
self.list_schema = ListSchema
self.add_schema = AddSchema
self.edit_schema = EditSchema
self.table = Menus
self.list_route = 'menu'
self.form_scripts = ""
def form_validator(self, form, value):
def err_kode():
raise colander.Invalid(form, 'Kode %s sudah digunakan oleh %s' % (
value['kode'], found.nama))
def err_nama():
raise colander.Invalid(form,
'Uraian %s sudah digunakan oleh kode %s' % (
value['nama'], found.kode))
if 'id' in form.request.matchdict:
uid = form.request.matchdict['id']
q = Menus.query_id(uid)
current = q.first()
else:
current = None
found = Menus.query_kode(value['kode'])
if current:
if found and found.id != current.id:
err_kode()
elif found:
err_kode()
found = Menus.query_nama(value['nama'])
if current:
if found and found.id != current.id:
err_nama()
elif found:
err_nama()
def update_children(self, children):
for child in children:
child.level_id = child.parent.level_id + 1
flush(child)
if child.children:
self.update_children(child.children)
def save_request(self, values, row=None): # save(self, row, values):
for k, v in values.items():
if not v:
setattr(row, k, None)
row = super().save_request(values, row)
return row
@view_config(route_name='menu',
renderer='templates/table.pt',
permission='menu')
def view_list(self):
return super().view_list()
@view_config(route_name='menu-view',
renderer='templates/form.pt', permission='menu')
def view_view(self):
return super(ViewMenus, self).view_view()
@view_config(route_name='menu-act', renderer='json',
permission='view')
def view_act(self):
request = self.req
params = request.params
url_dict = request.matchdict
table_alias = aliased(Menus)
if url_dict['act'] == 'grid':
columns = [ColumnDT(Menus.id, mData='id'),
ColumnDT(Menus.kode, mData='kode'),
ColumnDT(Menus.nama, mData='nama'),
ColumnDT(table_alias.nama, mData='parent'),
ColumnDT(Menus.status, mData='status'),
ColumnDT(Menus.level_id, mData='level_id'), ]
query = Menus.query_grid() \
.outerjoin(table_alias, Menus.parent_id == table_alias.id)
query = self.filter_company(query)
row_table = DataTables(request.GET, query, columns)
return row_table.output_result()
elif url_dict['act'] == 'hon':
term = 'term' in params and params['term'] or ''
q = Menus.query(). \
filter(Menus.status == 1,
Menus.nama.ilike('%%%s%%' % term)) \
.order_by(
Menus.nama)
return [dict(id=k.id, value=k.nama, kode=k.kode, nama=k.nama,
level_id=k.level_id) for k in q.all()]
@view_config(route_name='menu-add', renderer='templates/form.pt',
permission='menu')
def view_add(self):
return super(ViewMenus, self).view_add()
@view_config(route_name='menu-edit',
renderer='templates/form.pt', permission='menu')
def view_edit(self):
return super(ViewMenus, self).view_edit()
@view_config(route_name='menu-delete',
renderer='templates/form.pt', permission='menu')
def view_delete(self):
return super(ViewMenus, self).view_delete()
......@@ -41,8 +41,10 @@ from opensipkd.base import get_params
from opensipkd.base.views.user import email_validator, add_member_count
from . import widget_os
from .base_views import need_captcha, need_verify, get_url_captcha
from .user_login import regenerate_security_code, get_login_headers, send_email_security_code, send_email_pending
from opensipkd.models import User, DBSession, Partner, Group, UserGroup, ExternalIdentity
from .user_login import regenerate_security_code, get_login_headers, \
send_email_security_code, send_email_pending
from opensipkd.models import User, DBSession, Partner, Group, UserGroup, \
ExternalIdentity
from ..views import BaseView
_ = TranslationStringFactory('user')
......@@ -137,7 +139,8 @@ def show_error(request, msg):
def reg_buttons():
btn_register = Button(name='save', css_class='btn-success', type="submit", title="Register")
btn_register = Button(name='save', css_class='btn-success', type="submit",
title="Register")
btn_cancel = Button(name='batal', css_class='btn-primary', type="submit")
return btn_cancel, btn_register
......@@ -174,26 +177,30 @@ class Registrasi(BaseView):
def err_email():
exc = colander.Invalid(
form['email'], 'e-mail %s sudah ada yang menggunakan' % value['email'])
form['email'],
'e-mail %s sudah ada yang menggunakan' % value['email'])
raise exc
def err_user():
if 'user_name' in form:
raise colander.Invalid(
form['user_name'], 'User name %s sudah ada yang menggunakan' % value['user_name'])
form['user_name'],
'User name %s sudah ada yang menggunakan' % value[
'user_name'])
else:
raise colander.Invalid(
form['email'], 'User name %s sudah ada yang menggunakan' % value['email'])
form['email'],
'User name %s sudah ada yang menggunakan' % value['email'])
def err_nik():
if "kode" in form:
raise colander.Invalid(
form['kode'], 'NIK %s sudah ada yang menggunakan' % value['kode'])
form['kode'],
'NIK %s sudah ada yang menggunakan' % value['kode'])
else:
raise colander.Invalid(
form['mobile'], 'Mobile %s sudah ada yang menggunakan' % value['kode'])
form['mobile'],
'Mobile %s sudah ada yang menggunakan' % value['kode'])
def err_login():
raise colander.Invalid(
......@@ -221,7 +228,6 @@ class Registrasi(BaseView):
if user.id != is_logged.id:
err_user()
email = value["email"]
user = user_found(email)
if user and not is_logged:
......@@ -258,14 +264,16 @@ class Registrasi(BaseView):
if 'password' in value:
user = form.request.user
if not user or not UserService.check_password(user, value['password']):
if not user or not UserService.check_password(user,
value['password']):
err_login()
def before_add(self):
result = {}
if "id_info" in self.ses and self.ses['id_info']:
result = self.ses["id_info"]
result.update(dict(nama=" ".join([result["given_name"], result["family_name"]])))
result.update(dict(
nama=" ".join([result["given_name"], result["family_name"]])))
if need_captcha():
result.update(dict(captcha=get_url_captcha(self.req)))
return result
......@@ -299,7 +307,8 @@ class Registrasi(BaseView):
DBSession.add(external)
DBSession.flush()
if need_verify():
send_email_pending(self.req, row, 'Welcome new user', 'email-new-user',
send_email_pending(self.req, row, 'Welcome new user',
'email-new-user',
'email-pending.tpl')
ts = _(
'user-added',
......@@ -345,6 +354,7 @@ class Registrasi(BaseView):
DBSession.add(partner)
DBSession.flush()
return row
def cancel_act(self):
forget(self.req)
self.ses.delete()
......@@ -399,4 +409,5 @@ class Registrasi(BaseView):
upload = Upload(path)
values["idcard"] = upload.save(self.req, 'upload')
row = super().save_request(values, row)
self.after_save(row, values)
return row
from pyramid_rpc.jsonrpc import jsonrpc_method
from opensipkd.models import Menus
@jsonrpc_method(method='get_menu', endpoint='rpc')
def get_menu(request, data):
"""
Digunakan untuk login pada aplikasi lain
:param request:
:param data:
{
"user_name": "user_name",
"password": "password"
}
:return:
result: "data":
{
"user_name": "user_name",
"nik": nik,
"nama": nik,
"group": [group],
"departemens": [departemen]
}
error:"error":{}
"""
is_list = type(data) is list
data = is_list and data[0] or data
level_id = data.get("level")
group = data.get("group")
qry = Menus.query()
if level_id:
qry = qry.filter(Menus.level_id == level_id or Menus.level_id == None)
if group:
grp = Menus.query().filter(kode=group).first()
if grp:
qry = qry.filter(
Menus.parent_id == grp.id or Menus.parent_id == None)
if request.user:
qry = qry.filter_by(need_login=True)
else:
qry = qry.filter_by(need_login=False)
resp = [
{
"type": "link",
"label": row.nama,
"className": row.class_name,
"name": row.kode,
"id": row.kode,
"url": row.url,
"icon": row.icon
}
for row in qry.all()]
result = is_list and [resp] or resp
return result
......@@ -6,21 +6,15 @@
</div>
<div class="panel-body">
<div tal:content="structure form"></div>
<div class="jarviswidget jarviswidget-color-blueLight">
<div tal:content="structure table"></div>
</div>
</div>
</div>
</div>
<div metal:fill-slot="scripts">
<script>
$(document).ready(function () {
$(".read-only").attr("readonly", true);
$(".readonly").attr("readonly", true);
$(".date").attr("readonly", true);
$(".date").datepicker({
format: 'dd-mm-yyyy'
});
${structure:scripts}
});
</script>
<div metal:define-slot="scripts"></div>
</div>
......
<html metal:use-macro="load: form_inout.pt">
<div metal:fill-slot="content">
<style>
button {
margin: 0px 3px;
}
</style>
<!-- Tampilan untuk general error -->
<div tal:define="field form" class="alert alert-danger" tal:condition="field.error">
<div class="errorMsgLbl" i18n:translate="">
Terdapat kesalahan pengisian
</div>
<p class="errorMsg">${field.errormsg}</p>
</div>
<!-- END Tampilan untuk general error -->
<div class="panel-body">
<!-- Proses Template Form -->
<form method="post" accept-charset="utf-8" id="deform" class="form-horizontal"
enctype="multipart/form-data"
style="background-color:white;">
<input type="hidden" name="_charset_">
<input type="hidden" value="deform" name="__formid__">
<div class="col-md-6 col-md-offset-3 col-xs-12 well">
<div class="col-md-12">
<!-- Looping Semua Field-->
<div tal:repeat="f form">
<!-- Proses Saat Field hidden-->
<div tal:condition="f.widget.hidden">
${structure:f.serialize()}
</div>
<!-- Proses Saat Field Normal dan bukan Children-->
<div tal:condition="not f.widget.hidden and not f.children"
class="form-group">
<!-- Field Reqired menggunakan class required -->
<label for="${f.oid}"
class="control-label col-md-4 ${f.required and 'required' or ''}"
id="req-${f.oid}">
${f.title}</label>
<div class="col-md-8">
${structure:f.serialize()}
<p id="error-${f.oid}" class="help-block" tal:condition="f.error"
tal:repeat="error f.error.messages()">
${error}</p>
</div>
</div>
<!-- Proses saat Form Adalah Children -->
<div tal:condition="f.children">
<!--div class="panel panel-default" title="">
<div class="panel-heading">${f.title}</div>
<div class="panel-body" -->
<input type="hidden"
name="__start__"
value="${f.name}:mapping"
readonly="readonly">
<div class="form-group" tal:repeat="f2 f.children">
<div id="item-${f2.oid}">
<label for="${f2.oid}" id="req-${f2.oid}"
class="control-label col-md-4 ${f2.required and 'required' or ''}">
${f2.title}</label>
<div class="col-md-8">
${structure:f2.serialize()}
<p id="error-${f2.oid}" class="help-block" tal:condition="f2.error"
tal:repeat="error f2.error.messages()">
${error}</p>
</div>
</div>
</div>
<input type="hidden" name="__end__" value="${f.name}:mapping" readonly="readonly">
<!--/div>
</div>
</div-->
</div>
</div>
<div class="input-group-btn">
<button tal:repeat="fb form.buttons"
type="${fb.type}" class="btn ${fb.css_class} pull-right"
name="${fb.name}">${fb.title}</button>
</div>
<label class="control-label text-danger">** Data di Isi sesuai dengan yang tertera pada KTP</label>
</div>
</div>
</form>
<!-- End Template Form -->
<!-- </div> -->
</div>
</div>
<div metal:fill-slot="scripts">
<script tal:condition="${captcha}">
$(document).ready(function () {
$("#captcha").parent().prepend(
'<img style="height:30px; width:auto; margin-bottom:5px;" ' +
'src="${home}/captcha/${captcha}.png">')
$('#captcha').on('input', function (evt) {
$(this).val(function (_, val) {
return val.toUpperCase();
});
});
});
</script>
</div>
</html>
......@@ -25,6 +25,7 @@ from importlib import import_module
import colander
import requests
from deform import widget, Form, ValidationFailure, Button
from icecream import ic
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.renderers import render_to_response
from pyramid.security import remember, forget
......@@ -39,11 +40,19 @@ from opensipkd.tools import create_now, set_user_log, get_settings
from opensipkd.base.views import _, one_hour, two_minutes, BaseView
from pyramid_mailer.message import Message
from opensipkd.tools.form_api import formfield2dict
log = __import__("logging").getLogger(__name__)
class Login(colander.Schema):
username = colander.SchemaNode(colander.String())
username = colander.SchemaNode(
colander.String(),
widget=widget.TextInputWidget(
placeholder="User Name"),
# validator=colander.Length(min=3, max=3),
oid="username",
)
password = colander.SchemaNode(
colander.String(), widget=widget.PasswordWidget())
......@@ -61,8 +70,26 @@ def get_login_headers(request, user):
return headers
class Views(BaseView):
@view_config(route_name='login', renderer='templates/login.pt')
class LoginUser(object):
def __init__(self, request):
# self.user = user
self.request = request
# self.identity=identity
self.message = "Sukses Login"
self.user = None
def login(self, values, user=None):
self.user = user and user or User.get_by_identity(values["username"])
if not self.user or not UserService.check_password(
self.user, values["password"]):
self.message = "Login Gagal"
set_user_log(self.message, self.request, log, values["username"])
return
return True
class ViewLogin(BaseView):
@view_config(route_name='login', renderer='templates/form.pt')
def view_login(self):
request = self.req
request.session["login"] = True
......@@ -110,11 +137,9 @@ class Views(BaseView):
return HTTPFound(location=request.route_url('login'))
else:
if not user or not UserService.check_password(user, values[
'password']):
msg = "Login Gagal"
set_user_log(msg, request, log, identity)
request.session.flash(msg, "error")
login = LoginUser(self.req)
if not login.login(values, user):
request.session.flash(login.message, "error")
next_url = f"{request.route_url('login')}?next={next_url}"
return HTTPFound(location=next_url)
......@@ -166,6 +191,13 @@ class Views(BaseView):
message = "User anda masih menunggu verifikasi atau lagi di blokir"
request.session.flash(message, "error")
login = ""
if login_tpl == 'templates/login.pt':
return dict(form=form.render(),
message=message,
url=request.route_url('login'),
next_url=next_url,
login=login, )
return render_to_response(
renderer_name=login_tpl,
request=request,
......@@ -238,6 +270,7 @@ def view_change_password(request):
if request.authenticated_userid:
request.session.flash('Anda sudah login', 'error')
return HTTPFound(location=f"{request.route_url('home')}")
schema = ChangePassword(validator=change_password_validator)
btn_save = Button('save', _('Simpan'))
btn_cancel = Button('cancel', _('Batalkan'))
......@@ -259,6 +292,7 @@ def view_change_password(request):
create_now() - user.security_code_date > one_hour:
request.session.flash('Security code expired', 'error')
return HTTPFound(location=request.route_url('login'))
user.security_code = None
UserService.set_password(user, c['new_password'])
DBSession.add(user)
......@@ -328,14 +362,17 @@ def security_code_age(user):
def send_email_security_code(
request, user, time_remain, subject, body_msg_id, body_default_file):
request, user, time_remain, subject, body_msg_id, body_default_file,
**kwargs):
settings = get_settings()
password = kwargs.get("password", "")
if 'mail.sender_name' not in settings \
or 'mail.username' not in settings:
return
url = '{}password/{}'.format(
request.route_url('home'), user.security_code)
url = '{}password/{}?password={}'.format(
request.home, user.security_code, password)
minutes = int(time_remain.seconds / 60)
data = dict(url=url, minutes=minutes)
here = os.path.abspath(os.path.dirname(__file__))
......
......@@ -14,6 +14,7 @@ from pyramid.i18n import TranslationStringFactory
from pyramid.security import remember, forget
from pyramid_rpc.jsonrpc import jsonrpc_method
from .base_views_api import BaseApi
from .user import EmailValidator as EmailValidatorBase
from .user_group import save as save_groups
from .user_login import (ChangePassword, change_password_validator,
......@@ -110,7 +111,7 @@ def login(request, data):
is_list = type(data) is list
data = is_list and data[0] or data
resp = login_(request, data)
resp["token"] = get_user_device(request, resp["id"]).token
# resp["token"] = get_user_device(request, resp["id"]).token
result = is_list and [resp] or resp
return result
......@@ -147,7 +148,6 @@ def get_profile_(user):
mobile=partner.mobile,
nama=partner.nama, )
@jsonrpc_method(method='get-profile', endpoint='rpc-user', permission="view")
@jsonrpc_method(method='get_profile', endpoint='rpc-user', permission="view")
def get_profile(request, data):
......@@ -158,12 +158,12 @@ def get_profile(request, data):
@param data: {"password": password}
@return:
"""
user = request.user
user = request.request.user
is_list = type(data) == list
data = is_list and data[0] or data
print(data)
if not user or not UserService.check_password(user, data['password']):
raise JsonRpcInvalidLoginError
resp = get_profile_(user)
resp = is_list and [resp] or resp
return resp
......
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
import logging
import colander
import deform
from chameleon.utils import Markup
from deform import compat
from deform import field
from . import widget
log = logging.getLogger(__name__)
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,
action_suffix='/grid/act',
buttons=(),
tableid="detable",
sorts='true',
filters='true',
paginates='true',
params="",
# ajax_options="{}",
# autocomplete=None,
# focus="on",
# cols = None,
**kw
):
params = params and f"?{params}" or ""
btn_close_js = "{window.location = '/'; return false;}"
btn_add_js = "{window.location = o%sUri+'/add%s';}" % (tableid, params)
btn_edit_js = """{
if (m%sID) window.location = o%sUri+'/'+m%sID+'/edit%s';
else alert('Pilih Baris');
}""" % (tableid, tableid, tableid, params)
btn_view_js = "{window.location = o%sUri+'/'+m%sID+'/view%s';}" % (tableid, tableid, params)
btn_delete_js = "{window.location = o%sUri+'/'+m%sID+'/delete%s';}" % (tableid, tableid, params)
btn_csv_js = "{window.location = o%sUri+'/csv/act%s';}" % (tableid, params)
btn_pdf_js = "{window.open(o%sUri+'/pdf/act%s');}" % (tableid, params)
action_suffix = f"{action_suffix}{params}"
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>\n
""")
_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 = []
cols2 = []
for f in schema:
d = {'data': f.name}
data = []
if hasattr(f, 'width'):
d["width"] = f.width
data.append(f"width: '{f.width}'")
if hasattr(f, 'aligned'):
d["className"] = f.aligned
data.append(f"className: '{f.aligned}'")
if hasattr(f, 'searchable'):
d["searchable"] = f.searchable
data.append(f"searchable: {f.searchable}")
if hasattr(f, 'visible') :
d["visible"] = f.visible
data.append(f"visible: {f.visible}")
if isinstance(f.widget,deform.widget.HiddenWidget):
d["visible"] = False
if hasattr(f, 'orderable'):
d["orderable"] = f.orderable
data.append(f"orderable: {f.orderable}")
thousand = hasattr(f, 'thousand') and f.thousand or None
separator = thousand and "separator" in thousand and thousand["separator"] or ','
decimal = thousand and "decimal" in thousand and thousand["decimal"] or '.'
point = thousand and "point" in thousand and thousand["point"] or 0
currency = thousand and "currency" in thousand and thousand["currency"] or ""
if thousand or type(f.typ) == colander.Float() or type(f.typ) == colander.Integer():
d["render"] = f"<script>$.fn.dataTable.render.number( '{separator}', '{decimal}', {point}, '{currency}' )</script>"
if 'className' not in d:
d["className"] = "text-right"
# if hasattr(f, "edit_link"):
# s = """function ( data, type, row, meta ) {
# return '<a href="'+data+'">Download</a>';
# }"""
# d["render"] = s
columns.append(d)
cols2.append(data)
# columns.append(dict(title="Action",
# data='id',
# width="40pt",
# orderable=False,
# align="text-center",
# searchable=False))
self.columns = json.dumps(columns)
self.columns = self.columns.replace('"<script>', "").replace('</script>"', "").replace("\n", "")
# self.columns = columns
# self.columns = json.dumps(cols2)
self.url = action
self.url_suffix = action_suffix
self.sorts = sorts
self.paginates = paginates
self.filters = filters
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;
url_suffix url_suffix|field.url_suffix;
sorts sorts|field.sorts;
filters filters|field.filters;
paginates paginates|field.paginates;
"
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>
<!--? <th>Action</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 + "${url_suffix}";
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: false,
scrollCollapse: true,
sort: ${sorts},
info: false,
filter: ${filters},
autoWidth: false,
paginate: ${paginates},
paginationType: "full_numbers",
lengthMenu: [
[10, 25, 50, 100],
[10, 25, 50, 100]
],
columns: ${structure: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"
requirements = (("deform", None),
{"js": "opensipkd.base:static/v3/js/plugin/datatables/jquery.dataTables.min.js"})
......@@ -107,6 +107,8 @@ class DeTable(field.Field):
filters='true',
paginates='true',
params="",
server_side='true',
data=[],
# ajax_options="{}",
# autocomplete=None,
# focus="on",
......@@ -154,6 +156,8 @@ class DeTable(field.Field):
if table_widget is None:
table_widget = widget.TableWidget()
self.widget = table_widget
self.server_side = server_side
self.data = data
columns = []
cols2 = []
for f in schema:
......
......@@ -13,6 +13,8 @@
sorts sorts|field.sorts;
filters filters|field.filters;
paginates paginates|field.paginates;
server_side server_side|field.server_side;
data data|field.data;
"
tal:attributes="style style;
class css_class;
......@@ -30,7 +32,7 @@
<thead>
<tr>
<th tal:repeat="child field">${child.title}</th>
<!--? <th>Action</th>-->
<!--? <th>Action</th>-->
</tr>
</thead>
<tbody>
......@@ -66,12 +68,10 @@
},
"lengthMenu": " _MENU_ baris "
};
o${tableid} = $('#${tableid}').DataTable({
var params = {
dom: '<"row"<"col-md-8"<"toolbar">Bl><"col-md-4"fr>>tip',
processing: true,
serverSide: true,
ajax: o${tableid}Url,
serverSide: ${server_side},
stateSave: false,
scrollCollapse: true,
sort: ${sorts},
......@@ -88,7 +88,17 @@
columns: ${structure:columns},
"language": language,
});
}
if (${server_side}===false)
{
params.data = ${data};
}else{
params.ajax=o${tableid}Url;
}
o${tableid} = $('#${tableid}').DataTable(params);
var tb = tb_array.join(' ');
$("div.toolbar").html(tb);
......
import logging
from icecream import ic
from pyramid.httpexceptions import HTTPForbidden
from pyramid.httpexceptions import HTTPNotFound
from pyramid.renderers import null_renderer
from pyramid.security import NO_PERMISSION_REQUIRED
from pyramid_rpc.jsonrpc import (JsonRpcError, JsonRpcMethodNotFound, JsonRpcParamsInvalid,
JsonRpcInternalError, make_error_response, MethodPredicate, BatchedRequestPredicate,
jsonrpc_renderer, add_jsonrpc_method,
DEFAULT_RENDERER,
batched_request_view, Endpoint, EndpointPredicate)
from pyramid.renderers import null_renderer, render
from pyramid.response import Response
from pyramid.security import NO_PERMISSION_REQUIRED, remember
from pyramid_rpc.jsonrpc import (
JsonRpcError, JsonRpcMethodNotFound, JsonRpcParamsInvalid,
JsonRpcInternalError, make_error_response, MethodPredicate,
BatchedRequestPredicate, jsonrpc_renderer, add_jsonrpc_method,
DEFAULT_RENDERER, batched_request_view, Endpoint,
JsonRpcRequestInvalid, parse_request_GET, parse_request_POST)
from pyramid_rpc.mapper import ViewMapperArgsInvalid, MapplyViewMapper
from opensipkd.base.tools.api import auth_from_rpc
log = logging.getLogger(__name__)
......@@ -23,6 +28,7 @@ class JsonRpcInvalidLogin(JsonRpcError):
code = -32605
message = "Invalid User/Password"
#
# class EndpointPredicate(BaseEndpointPredicate):
# def __call__(self, info, request):
......@@ -67,31 +73,51 @@ class JsonRpcInvalidLogin(JsonRpcError):
# log.debug('id:%s invalid rpc method', request.rpc_id)
# raise JsonRpcRequestInvalid
# env = request.environ
# if 'HTTP_TOKEN' in env:
# try:
# user_device = token_auth(request)
# user = user_device.user
# headers = remember(request, user.id)
# request.headers["Cookie"] = dict(headers)["Set-Cookie"]
# request.headers["token"]=user_device.token
# log.debug(request.headers["Cookie"])
# except JsonRpcInvalidLoginError as e:
# raise JsonRpcInvalidLogin
#
# elif ('HTTP_USERID' in env and 'HTTP_SIGNATURE' in env and
# 'HTTP_KEY' in env):
# try:
# user = rpc_auth(request)
# headers = remember(request, user.id)
# request.headers["Cookie"] = dict(headers)["Set-Cookie"]
# log.debug(request.headers["Cookie"])
# except JsonRpcInvalidLoginError as e:
# raise JsonRpcInvalidLogin
# log.debug('handling id:%s method:%s',
# request.rpc_id, request.rpc_method)
# env = request.environ
# if 'HTTP_TOKEN' in env:
# try:
# user_device = token_auth(request)
# user = user_device.user
# headers = remember(request, user.id)
# request.headers["Cookie"] = dict(headers)["Set-Cookie"]
# request.headers["token"]=user_device.token
# log.debug(request.headers["Cookie"])
# except JsonRpcInvalidLoginError as e:
# raise JsonRpcInvalidLogin
#
# elif ('HTTP_USERID' in env and 'HTTP_SIGNATURE' in env and
# 'HTTP_KEY' in env):
# try:
# user = rpc_auth(request)
# headers = remember(request, user.id)
# request.headers["Cookie"] = dict(headers)["Set-Cookie"]
# log.debug(request.headers["Cookie"])
# except JsonRpcInvalidLoginError as e:
# raise JsonRpcInvalidLogin
# log.debug('handling id:%s method:%s',
# request.rpc_id, request.rpc_method)
def make_error_response(request, error, id=None):
""" Marshal a Python Exception into a ``Response`` object with a
body that is a JSON string suitable for use as a JSON-RPC response
with a content-type of ``application/json`` and return the response.
"""
# we may need to render a parse error, at which point we don't know
# much about the request
renderer = getattr(request, 'rpc_renderer', DEFAULT_RENDERER)
out = {
'jsonrpc': '2.0',
'id': id,
'error': error.as_dict(),
}
ic(out)
body = render(renderer, out, request=request).encode('utf-8')
response = Response(body, charset='utf-8')
response.content_type = 'application/json'
return response
def exception_view(exc, request):
rpc_id = getattr(request, 'rpc_id', None)
......@@ -163,6 +189,71 @@ def add_jsonrpc_endpoint(config, name, *args, **kw):
permission=NO_PERMISSION_REQUIRED)
def setup_request(endpoint, request):
""" Parse a JSON-RPC request body."""
if request.method == 'GET':
parse_request_GET(request)
elif request.method == 'POST':
parse_request_POST(request)
else:
log.debug('unsupported request method "%s"', request.method)
raise JsonRpcRequestInvalid
if hasattr(request, 'batched_rpc_requests'):
log.debug('handling batched rpc request')
# the checks below will look at the subrequests
return
if request.rpc_version != '2.0':
log.debug('id:%s invalid rpc version %s',
request.rpc_id, request.rpc_version)
raise JsonRpcRequestInvalid
if request.rpc_method is None:
log.debug('id:%s invalid rpc method', request.rpc_id)
raise JsonRpcRequestInvalid
log.debug('handling id:%s method:%s',
request.rpc_id, request.rpc_method)
env = request.environ
if 'HTTP_USERID' in env or 'HTTP_SIGNATURE' in env or 'HTTP_KEY' in env:
user = auth_from_rpc(request)
if user:
headers = remember(request, user.id)
for k, v in headers:
if k == "Set-Cookie":
req_headers = [("Cookie", v)]
request.headers.update(req_headers)
log.debug('handling request headers: %s', req_headers)
class EndpointPredicate(object):
def __init__(self, val, config):
self.val = val
def text(self):
return 'jsonrpc endpoint = %s' % self.val
phash = text
def __call__(self, info, request):
if self.val:
# find the endpoint info
key = info['route'].name
endpoint = request.registry.jsonrpc_endpoints[key]
# potentially setup either rpc v1 or v2 from the parsed body
setup_request(endpoint, request)
# update request with endpoint information
request.rpc_endpoint = endpoint
# Always return True so that even if it isn't a valid RPC it
# will fall through to the notfound_view which will still
# return a valid JSON-RPC response.
return True
def includeme(config):
""" Set up standard configurator registrations. Use via:
......
from sqlalchemy import event
from .base import *
from .common import *
from .users import *
from .wilayah import *
from .menus import *
from .partner import *
from .departemen 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__"):
tablename = sub_class.__tablename__
if tablename not in ALL_TABLES:
ALL_TABLES.update({tablename: sub_class})
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:
query = query.add_columns(*[getattr(cls, c) for c in fields])
else:
query = DBSession.query(cls)
if domain:
filter_expressions = []
for d in domain:
field = getattr(cls, d[0])
operator = d[1]
value = d[2]
filter_expressions.append(field.op(operator)(value))
query = query.filter(
*[
e for i, e in enumerate(filter_expressions)
if e is not None]
)
return query
from .wilayah import *
\ No newline at end of file
......@@ -18,7 +18,6 @@ class MySession(Session):
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")
......
from sqlalchemy import (
Column,
Integer,
ForeignKey,
String,
SmallInteger, Boolean,
)
from sqlalchemy.orm import (
relationship,
backref
)
from ..models import DBSession, Base
from ..models import (NamaModel,
TABLE_ARGS)
class Menus(Base, NamaModel):
__tablename__ = 'menus'
__table_args__ = (TABLE_ARGS,)
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('public.menus.id'))
level_id = Column(SmallInteger)
order_id = Column(SmallInteger)
url = Column(String(256))
icon = Column(String(256))
class_name = Column(String(256))
need_login = Column(SmallInteger, server_default="1")
permissions = Column(String(128), server_default="")
children = relationship(
"Menus", backref=backref('parent', remote_side=[id]))
def get_parents(self, start=False):
allparents = []
if start:
allparents.append(self.nama)
p = self.parent
while p:
allparents.append(p.nama)
p = p.parent
return allparents
@property
def parents(self, start=False):
return self.get_parents()
@property
def name_get(self):
allparents = self.get_parents(True)
return '/'.join(reversed(allparents))
@property
def uraian_all(self):
allparents = self.get_parents(True)
return '/'.join(reversed(allparents))
@classmethod
def get_list(cls):
return DBSession.query(cls.id, cls.nama).order_by(cls.nama).all()
@classmethod
def query_grid(cls):
return DBSession.query().select_from(cls)
@classmethod
def get(cls, parent=None):
if not parent:
return cls.query().filter(cls.parent_id == None)
else:
row = cls.query().filter(cls.kode == parent).first()
if row:
return cls.query().filter(cls.parent_id == row.id)
......@@ -8,14 +8,14 @@ Pillow>=9.1.1
lxml>=4.9.0
beautifulsoup4>=4.11.1
soupsieve>=2.3.2.post1
pip>=22.1.2
pip~=18.1
wheel~=0.37.0
rsa>=4.8
pyasn1~=0.4.8
Chameleon>=3.10.1
six~=1.16.0
Mako>=1.2.0
Babel>=2.10.3
Babel~=2.10.2
Beaker~=1.11.0
Pygments>=2.12.0
MarkupSafe>=2.1.1
......@@ -27,7 +27,7 @@ peppercorn~=0.6
iso8601>=1.0.2
google~=3.0.0
cachetools>=5.2.0
certifi>=2022.6.15
certifi~=2022.5.18.1
urllib3~=1.26.6
requests>=2.28.0
google-api-python-client>=2.51.0
......@@ -62,3 +62,9 @@ pikepdf>=5.1.5
packaging>=21.3
pycparser>=2.21
pyramid_rpc
zipp~=3.8.0
papyrus~=2.4
geojson~=2.5.0
GeoAlchemy2~=0.12.1
pybind11~=2.9.2
qrcode~=7.3.1
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!