Commit 63871500 by Tatang S

Merge branch 'latest' of https://git.opensipkd.com/aa.gusti/opensipkd-base into latest

2 parents df0feac0 b58ade88
This diff could not be displayed because it is too large.
3.0.2 13-07-2023
Perubahan Colander>2.0
Perubahan SQLAlchemy>2.0
beaker>=1.12.1
3.0.1 25-04-2022
Penambahan Feature ```detable```
......
......@@ -197,8 +197,30 @@ Tambahkan blok berikut ini dibawah ini file
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Port $server_port;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 60s;
proxy_send_timeout 90s;
proxy_read_timeout 90s;
proxy_buffering off;
proxy_temp_file_write_size 64k;
proxy_pass http://127.0.0.1:6543/;
proxy_redirect off;
}
```
Other Configuration
```
[server:main]
use = egg:waitress#main
host = 0.0.0.0
port = 6543
;port = %(http_port)s digunakan jika port akan menggunakan parameter
trusted_proxy = 10.8.50.23
trusted_proxy_count = 1
trusted_proxy_headers = x-forwarded-for x-forwarded-host x-forwarded-proto x-forwarded-port
clear_untrusted_proxy_headers = yes
url_scheme = https # HTTP or https
```
......@@ -2,8 +2,6 @@ import locale
import logging
import re
import colander
try:
from urllib import (urlencode, quote, quote_plus, )
except ImportError:
......@@ -21,6 +19,8 @@ from .security import (
group_finder,
get_user, MySecurityPolicy,
)
from pyramid.csrf import get_csrf_token
from opensipkd.models import (
DBSession,
Base,
......@@ -149,6 +149,7 @@ def add_global(event):
event['change_unit'] = change_unit
event['get_params'] = get_params
event['get_urls'] = get_urls
event['get_csrf_token'] = get_csrf_token
def get_params(params, alternate=None, settings=None):
......@@ -180,6 +181,13 @@ def get_ini(request, var):
return
def get_password_strength(request):
settings = get_settings()
if 'password_strength' in settings and settings['password_strength']:
return settings['password_strength']
return True
def get_ini_params(request, params=None, alternate=None, settings=None):
"""
Digunakan untuk mengambil nilai dari konfigurasi sesuai params yang disebut
......@@ -196,6 +204,10 @@ def get_ini_params(request, params=None, alternate=None, settings=None):
def get_id_card_folder(ext=None):
folder = get_params("partner_idcard_folder", '/tmp/idcard')
if ext:
if ext and os.sep != '/':
ext = ext.replace('/', '\\')
if not os.path.exists(folder + ext):
os.makedirs(folder + ext)
return folder + ext
return folder
......@@ -427,7 +439,7 @@ def main(global_config, **settings):
None: {"js": "opensipkd.base:static/jquery/jquery.maskMoney.min.js"}}
engine = engine_from_config(
settings, 'sqlalchemy.', client_encoding='utf8', convert_unicode=True)
settings, 'sqlalchemy.', client_encoding='utf8') # , convert_unicode=True
DBSession.configure(bind=engine)
LogDBSession.configure(bind=engine)
Base.metadata.bind = engine
......@@ -444,6 +456,7 @@ def main(global_config, **settings):
config = Configurator(settings=settings,
root_factory='opensipkd.models.RootFactory',
session_factory=session_factory)
config.set_default_csrf_options(require_csrf=False)
modules = get_modules(settings)
from importlib import import_module
for module in modules:
......@@ -484,7 +497,9 @@ def main(global_config, **settings):
config.add_request_method(disable_responsive, 'disable_responsive',
reify=True)
config.add_request_method(get_ini, 'get_ini', reify=True)
config.add_translation_dirs('opensipkd.base:locale/')
config.add_request_method(get_csrf_token, 'get_csrf_token', reify=True)
# config.add_translation_dirs('opensipkd.base:locale/')
config.add_static_view('static', 'opensipkd.base:static',
cache_max_age=3600)
......
......@@ -243,7 +243,9 @@ def reset_sequence_(cls, seq):
q = DBSession.query(cls)
if not q.first():
sql = "SELECT setval('{}', 1, false)".format(seq)
DBSession.bind.execute(sql)
# DBSession.bind.execute(sql)
#sqlalchemy 2
DBSession.execute(text(sql))
def reset_sequences():
......
$(document).ready(function () {
$('#parent_nm').bind('typeahead:selected', function (obj, datum, name) {
$('#parent_id').val(datum.id);
});
$('#parent_nm').on('input',
function (e) {
let val = $('#parent_nm').val();
if (val === null || val === "") {
$('#parent_id').val("");
}
});
});
\ No newline at end of file
$(document).ready(function () {
$('#parent_nm').bind('typeahead:selected', function (obj, datum, name) {
$('#parent_id').val(datum.id);
$('#parent_kd').val(datum.kode);
console.log(datum.kode);
});
$('#parent_nm').bind('typeahead:selected', function (obj, datum, name) {
$('#parent_id').val(datum.id);
$('#parent_kd').val(datum.kode);
console.log(datum.kode);
});
$('#parent_nm').on('input',
function (e) {
let val = $('#parent_nm').val();
if (val === null || val === "") {
$('#parent_id').val("");
$('#parent_kd').val("");
}
});
});
\ No newline at end of file
......@@ -22,11 +22,13 @@ from .base_views import BaseView
from opensipkd.models import (
DBSession, UserService, )
from .common import DataTables, ColumnDT
from pyramid.csrf import new_csrf_token
_ = TranslationStringFactory('login')
log = logging.getLogger(__name__)
@view_config(context=HTTPNotFound, renderer='templates/404.pt')
def not_found(request):
path = request.path
......@@ -63,6 +65,7 @@ class Home(BaseView):
# session = request.session
modules = request.menus
modules_default = get_params('modules_default')
submodules_default = get_params('submenus')
# request.session['modules'] = modules
# request.session['modules_default'] = modules_default
log.info(request.session.peek_flash())
......@@ -78,10 +81,10 @@ class Home(BaseView):
if home_tpl:
return render_to_response(
home_tpl,
dict(modules=modules, logo=logo),
dict(modules=modules, logo=logo, submodules=[]),
request=request
)
return dict(modules=modules, logo=logo)
return dict(modules=modules, logo=logo, submodules=[])
@view_config(context=HTTPForbidden, renderer='templates/403.pt')
......@@ -149,3 +152,5 @@ two_minutes = timedelta(1.0 / 24 / 60)
def deferred_jenis(node, kw):
values = kw.get('daftar_jenis', [])
return widget.RadioChoiceWidget(values=values)
......@@ -2,31 +2,25 @@ import logging
import os
import re
from datetime import datetime
from email.utils import parseaddr
import colander
from datatables import ColumnDT
from dateutil.relativedelta import relativedelta
from opensipkd.base.views.upload import tmpstore
from opensipkd.tools.captcha import get_captcha
from opensipkd.tools.report import csv_response, file_response
from deform import (widget, Form, ValidationFailure, FileData, )
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from .common import DataTables
from .. import DBSession, get_params, get_urls
from opensipkd.tools import dmy, date_from_str, get_settings, get_ext, \
from opensipkd.base.views.upload import tmpstore
from opensipkd.tools import dmy, get_settings, get_ext, \
date_from_str, get_random_string
import colander
from deform import (widget, Form, ValidationFailure, Button, FileData, )
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, \
btn_add, btn_csv, \
btn_pdf
from opensipkd.models import User, Menus
from opensipkd.tools.captcha import get_captcha
from opensipkd.tools.report import csv_response, file_response
from .common import DataTables
from .. import DBSession, get_params, get_urls
from ..scripts.initializedb import append_csv
from ..tools.api import auth_from_rpc
from ...detable import DeTable
log = logging.getLogger(__name__)
......@@ -71,7 +65,7 @@ class BaseView(object):
dt_awal = date_from_str(
'{d}-{m}-{y}'.format(y=self.tahun, m=self.bulan, d='01'))
dt_akhir = dt_awal + \
relativedelta(months=1) - relativedelta(days=1)
relativedelta(months=1) - relativedelta(days=1)
self.ses['awal'] = dmy(dt_awal)
self.ses['akhir'] = dmy(dt_akhir)
......@@ -82,7 +76,7 @@ class BaseView(object):
if 'posted' in self.params and self.params['posted']:
posted = self.params['posted']
self.posted = ((posted == 'true' or posted == '1') and 1) or (
(posted == 'false' or posted == '0') and 0) or 0
(posted == 'false' or posted == '0') and 0) or 0
self.ses['posted'] = self.posted
self.awal = 'awal' in self.ses and self.ses['awal'] or dmy(now)
......@@ -167,13 +161,15 @@ class BaseView(object):
def delete_msg(self, row):
return f'Data ID {row.id} sudah dihapus.'
def route_list(self, msg=None, error="", **kwargs):
def route_list(self, **kwargs):
msg = kwargs.get("msg")
error = kwargs.get("error", "")
list_url = kwargs.get("list_url", None)
if msg:
self.ses.flash(msg, error)
list_url = kwargs.get("list_url", None)
if not list_url:
list_url = self.req.route_url(self.list_route)
log.error(list_url)
if self.headers:
return HTTPFound(location=get_urls(list_url),
headers=self.headers)
......@@ -342,10 +338,10 @@ class BaseView(object):
def cancel_act(self, **kwargs):
return self.route_list(**kwargs)
def after_add(self, **kwargs):
def after_add(self, row=None, **kwargs):
return self.route_list(**kwargs)
def after_edit(self, **kwargs):
def after_edit(self, row=None, **kwargs):
return self.route_list(**kwargs)
def after_view(self, **kwargs):
......
import logging
import colander
from datatables import ColumnDT
from deform import (widget, )
from pyramid.view import (view_config, )
from sqlalchemy.orm import aliased
from opensipkd.models import ResProvinsi, ResDati2, ResDesa, User
from .partner_base import PartnerSchema, NamaSchema
from opensipkd.models import DBSession, ResCompany, ResKecamatan, Partner
from opensipkd.models import ResProvinsi, ResDati2, ResDesa, User
from . import DataTables
from .partner_base import PartnerSchema
from .. import get_urls
from ..views import BaseView
log = logging.getLogger(__name__)
SESS_ADD_FAILED = 'Tambah pemda gagal'
SESS_EDIT_FAILED = 'Edit pemda gagal'
......@@ -20,14 +27,34 @@ def company_widget(node, kw):
class AddSchema(PartnerSchema):
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/company.js"})
),
oid="parent_nm", title="Induk")
def after_bind(self, node, kw):
self["email"].missing = colander.drop
request = kw["request"]
self["parent_nm"].widget = widget.AutocompleteInputWidget(
size=60, min_length=3,
requirements=(("typeahead", None), ("deform", None),
{"js": "opensipkd.base:static/js/form/company.js"}),
values=get_urls(f"{request.route_url('company')}/hon/act"))
class EditSchema(AddSchema):
id = colander.SchemaNode(colander.String(), missing=colander.drop,
widget=widget.HiddenWidget(readonly=True),
visible=False,)
visible=False, )
partner_id = colander.SchemaNode(colander.Integer(),
widget=widget.HiddenWidget(),
missing=colander.drop,
......@@ -35,7 +62,7 @@ class EditSchema(AddSchema):
class ListSchema(colander.Schema):
id = colander.SchemaNode(colander.String(),title="Action")
id = colander.SchemaNode(colander.String(), title="Action")
kode = colander.SchemaNode(
colander.String(),
validator=colander.Length(max=32),
......@@ -46,6 +73,14 @@ class ListSchema(colander.Schema):
colander.String(),
validator=colander.Length(max=64),
oid="nama")
parent_nm = colander.SchemaNode(
colander.String(),
validator=colander.Length(max=64),
oid="parent_nm",
field="alias.nama"
)
status = colander.SchemaNode(
colander.Integer(),
widget=widget.CheckboxWidget(),
......@@ -116,16 +151,19 @@ class ViewCompany(BaseView):
found = User.get_by_identity(value.get('email'))
if found:
err_user()
value["status"]="status" in value and value["status"] and 1 or 0
value["is_vendor"]="is_vendor" in value and value["is_vendor"] and 1 or 0
value["is_customer"]="is_customer" in value and value["is_customer"] and 1 or 0
value["status"] = "status" in value and value["status"] and 1 or 0
value["is_vendor"] = "is_vendor" in value and value["is_vendor"] and 1 or 0
value["is_customer"] = "is_customer" in value and value["is_customer"] and 1 or 0
def get_bindings(self, row=None):
provinsi_list = ResProvinsi.get_list()
partner = row and row.partner or None
dati2_list = partner and partner.provinsi_id and ResDati2.get_list(partner.provinsi_id) or []
kecamatan_list = partner and partner.dati2_id and ResKecamatan.get_list(partner.dati2_id) or []
desa_list = partner and partner.kecamatan_id and ResDesa.get_list(partner.kecamatan_id) or []
dati2_list = partner and partner.provinsi_id and ResDati2.get_list(
partner.provinsi_id) or []
kecamatan_list = partner and partner.dati2_id and ResKecamatan.get_list(
partner.dati2_id) or []
desa_list = partner and partner.kecamatan_id and ResDesa.get_list(
partner.kecamatan_id) or []
return dict(provinsi_list=provinsi_list,
dati2_list=dati2_list,
kecamatan_list=kecamatan_list,
......@@ -143,10 +181,46 @@ class ViewCompany(BaseView):
def view_list(self):
return super().view_list()
def filter_company(self, query):
if self.req.user.company_id:
return query.filter(
self.table.id == self.req.user.company_id)
return query
@view_config(route_name='company-act', renderer='json',
permission='view')
def view_act(self):
return super().view_act()
request = self.req
ses = request.session
params = request.params
url_dict = request.matchdict
alias = aliased(ResCompany)
if url_dict['act'] == 'grid':
columns = [ColumnDT(self.table.id, mData='id'),
ColumnDT(self.table.kode, mData='kode'),
ColumnDT(self.table.nama, mData='nama'),
ColumnDT(alias.nama, mData='parent_nm'),
ColumnDT(self.table.status, mData='status'),
# ColumnDT(Departemen.level_id, mData='level_id'),
]
query = DBSession.query().select_from(ResCompany).outerjoin(
alias, ResCompany.parent_id == 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 = DBSession.query(self.table) \
.filter(self.table.status == 1,
self.table.nama.ilike('%%%s%%' % term)) \
.order_by(self.table.nama)
rows = q.all()
r = []
for k in rows:
d = dict(id=k.id, value=k.nama, kode=k.kode, nama=k.nama)
r.append(d)
log.error(r)
return r
@view_config(route_name='company-add',
renderer='templates/form.pt', permission='company')
......@@ -154,12 +228,17 @@ class ViewCompany(BaseView):
return super(ViewCompany, self).view_add()
def get_values(self, row, istime=False):
d = super().get_values(row,istime)
d = super().get_values(row, istime)
partner = row.partner
if partner :
if partner:
p = partner.to_dict()
del p["id"]
d.update(p)
if "parent_id" in d and d["parent_id"]:
parent = ResCompany.query_id(d["parent_id"]).first()
if parent:
d["parent_nm"] = parent.nama
return d
@view_config(route_name='company-edit',
......@@ -173,6 +252,11 @@ class ViewCompany(BaseView):
return super(ViewCompany, self).view_delete()
def save_request(self, values, row=None):
# Save Partner First
parent_id = "parent_id" in values and values["parent_id"] or None
if parent_id:
del values["parent_id"]
if 'partner_id' in values:
part = Partner.query_id(values['partner_id']).first()
values["id"] = part.id
......@@ -185,8 +269,12 @@ class ViewCompany(BaseView):
part.from_dict(values)
DBSession.add(part)
DBSession.flush()
# Save Company
if part:
values["partner_id"] = part.id
if parent_id:
values["parent_id"] = parent_id
if "id" in self.req.matchdict:
values["id"] = self.req.matchdict["id"]
......@@ -197,4 +285,3 @@ class ViewCompany(BaseView):
DBSession.add(part)
DBSession.flush()
return row
......@@ -5,14 +5,15 @@ from datetime import datetime
import colander
from deform import (widget, )
from opensipkd.tools import (get_ext, get_random_string, get_settings)
from pyramid.view import (view_config, )
from sqlalchemy import func
from sqlalchemy.orm import aliased
from opensipkd.models import DBSession, Departemen, Partner, PartnerDepartemen, ResCompany
from opensipkd.tools import (get_ext, get_random_string, get_settings)
from .company import company_widget
from .upload import AddSchema as UploadSchema
from opensipkd.models import DBSession, Departemen, Partner, PartnerDepartemen
from .. import get_params
from ..views import ColumnDT, DataTables, BaseView, get_urls
SESS_ADD_FAILED = 'Tambah departemen gagal'
......@@ -45,10 +46,11 @@ class AddSchema(colander.Schema):
widget=widget.AutocompleteInputWidget(
size=60, min_length=3,
requirements=(("typeahead", None), ("deform", None),
{"js": "opensipkd.base:static/js/form/departemen.js"})
{"js": "opensipkd.base:static/js/form/departemen.js"}),
# options={"allowClear": True}
),
oid="parent_nm", title="Induk")
parent_kd = colander.SchemaNode(colander.String(),
widget=widget.TextInputWidget(css_class="readonly"),
missing=colander.drop, oid="parent_kd", title="Kode Induk")
......@@ -75,19 +77,13 @@ class AddSchema(colander.Schema):
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('departemen')}/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/departemen.js"}),
values=get_urls(f"{request.route_url('departemen')}/hon/act"))
values=get_urls(f"{request.route_url('departemen')}/hon/act"),
)
if request.user.company_id:
self["company_id"].widget = widget.HiddenWidget()
self["company_id"].default = request.user.company_id
......@@ -100,12 +96,13 @@ class EditSchema(AddSchema):
class ListSchema(colander.Schema):
id = colander.SchemaNode(colander.String(), title="Action", visible=False)
kode = colander.SchemaNode(colander.String(), title="Kode", width='100pt')
kode = colander.SchemaNode(colander.String(), title="Kode")
nama = colander.SchemaNode(colander.String(), title="Nama")
status = colander.SchemaNode(colander.Boolean(), title="Status", width='50pt',
widget=widget.CheckboxWidget())
level_id = colander.SchemaNode(colander.Integer(), title="Level", width='50pt')
parent = colander.SchemaNode(colander.String(), title="Induk", width='200pt')
level_id = colander.SchemaNode(colander.Integer(), title="Level", width='40pt')
parent = colander.SchemaNode(colander.String(), title="Induk")
company_nm = colander.SchemaNode(colander.String(), title="Company")
class ViewDepartemen(BaseView):
......@@ -166,14 +163,22 @@ class ViewDepartemen(BaseView):
if child.children:
self.update_children(child.children)
def save_request(self, values, row=None): #save(self, row, values):
def save_request(self, values, row=None): # save(self, row, values):
for k, v in values.items():
if not v:
setattr(row, k, None)
values["level_id"] = 1
if "parent_id" in values and values["parent_id"]:
qry_parent = self.table.query_id(values["parent_id"])
parent = qry_parent.first()
if parent and parent.level_id:
values["level_id"] = parent.level_id + 1
if "parent_id" not in values:
values["parent_id"] = None
row = super().save_request(values, row)
return row
@view_config(route_name='departemen-view',
renderer='templates/form.pt', permission='departemen')
def view_view(self):
......@@ -199,9 +204,12 @@ class ViewDepartemen(BaseView):
ColumnDT(Departemen.nama, mData='nama'),
ColumnDT(dep_alias.nama, mData='parent'),
ColumnDT(Departemen.status, mData='status'),
ColumnDT(Departemen.level_id, mData='level_id'), ]
ColumnDT(Departemen.level_id, mData='level_id'),
ColumnDT(ResCompany.nama, mData='company_nm'), ]
query = DBSession.query().select_from(Departemen).outerjoin(
dep_alias, Departemen.parent_id == dep_alias.id)
dep_alias, Departemen.parent_id == dep_alias.id).outerjoin(
ResCompany, self.table.company_id == ResCompany.id
)
query = self.filter_company(query)
row_table = DataTables(request.GET, query, columns)
return row_table.output_result()
......@@ -244,7 +252,7 @@ class ViewDepartemen(BaseView):
# todo Check ulang untuk hon
term = 'term' in params and params['term'] or ''
settings = get_settings()
level_id = self.req.get_params('departemen_chg_id', 0)
level_id = get_params('departemen_chg_id', 0)
q = DBSession.query(Departemen).filter(Departemen.status == 1,
Departemen.nama.ilike(
'%%%s%%' %
......@@ -300,6 +308,9 @@ class ViewDepartemen(BaseView):
r.append(d)
return r
def get_bindings(self, row=None):
return {"company_list": ResCompany.get_list()}
@view_config(route_name='departemen-add', renderer='templates/form.pt',
permission='departemen')
def view_add(self):
......
......@@ -25,7 +25,7 @@ class AddSchema(colander.Schema):
cur_departemen_nm = colander.SchemaNode(
colander.String(),
widget=widget.TextInputWidget(readonly=True),
title="Departemen Kini",
title="Departemen Aktif",
missing=colander.drop)
departemen_id = colander.SchemaNode(
colander.Integer(),
......@@ -44,6 +44,7 @@ class AddSchema(colander.Schema):
level_id = colander.SchemaNode(
colander.String(),
# widget = widget.HiddenWidget(),
missing=colander.drop,
oid="level_id",
title="Level")
callback = colander.SchemaNode(
......@@ -57,7 +58,7 @@ class ChangeDepartemen(BaseView):
########
# List #
########
@view_config(route_name='departemen-chg', renderer='templates/departemen/chg.pt')
@view_config(route_name='departemen-chg', renderer='templates/departemen_chg.pt')
def view_departemen_chg(self):
request = self.req
ses = request.session
......
......@@ -30,11 +30,13 @@ SESS_EDIT_FAILED = 'Edit partner gagal'
class AddSchema(PartnerSchema):
is_vendor = colander.SchemaNode(
colander.Boolean(),
colander.Integer(),
widget=widget.CheckboxWidget(true_val="1", false_val="0"),
oid="is_vendor",
title="Vendor")
is_customer = colander.SchemaNode(
colander.Boolean(),
colander.Integer(),
widget=widget.CheckboxWidget(true_val="1", false_val="0"),
oid="is_customer",
title="Customer")
company_id = colander.SchemaNode(
......
import colander
import json
from deform import (Form, widget, ValidationFailure, )
from pyramid.httpexceptions import (HTTPFound, )
from deform import (widget, )
from pyramid.view import (view_config, )
from sqlalchemy import or_
from sqlalchemy.orm import aliased
......@@ -11,7 +9,7 @@ from opensipkd.models import DBSession as PartnerDBSession, DBSession, \
from opensipkd.models import Departemen, Jabatan
from opensipkd.models import Partner, PartnerDepartemen
from opensipkd.tools import dmy, date_from_str
from opensipkd.tools.buttons import btn_cancel, btn_save, btn_delete, btn_close
from . import widget_os
from ..views import ColumnDT, DataTables, BaseView
SESS_ADD_FAILED = 'Tambah posisi partner gagal'
......@@ -19,20 +17,26 @@ SESS_EDIT_FAILED = 'Edit posisi partner gagal'
class AddSchema(colander.Schema):
nama_widget = widget.AutocompleteInputWidget(
size=60,
values='/partner/hon/act',
min_length=2,
style="z-index: 100000 !important;")
departemen_widget = widget.AutocompleteInputWidget(
size=60,
values='/departemen/hon/act',
min_length=1)
min_length=2,
style="z-index: 100001 !important;")
nama_widget = widget.AutocompleteInputWidget(
size=60,
values='/partner/hon/act',
min_length=1)
jabatan_widget = widget.AutocompleteInputWidget(
size=60,
values='/jabatan/hon/act',
min_length=1)
min_length=2,
style="z-index: 99999 !important;")
partner_id = colander.SchemaNode(
colander.Integer(),
......@@ -66,11 +70,12 @@ class AddSchema(colander.Schema):
mulai = colander.SchemaNode(
colander.String(),
oid="mulai",
widget=widget.DateInputWidget(format="dd-mm-yyyy"))
widget=widget_os.DateInputWidget(css_class="date", format="dd-mm-yyyy"))
selesai = colander.SchemaNode(
colander.String(),
oid="selesai",
widget=widget.DateInputWidget(format="dd-mm-yyyy"))
widget=widget_os.DateInputWidget(css_class="date", format="dd-mm-yyyy",
style="z-index: 9999 !important;"))
class EditSchema(AddSchema):
......
<div metal:use-macro="load: main.pt">
<div metal:fill-slot="content">
<div tal:content="structure form"/>
<div class="panel panel-default">
<div class="panel-body">
<div class="col-md-12">
<div tal:content="structure form"/>
</div>
</div>
</div>
</div>
</div>
<div metal:use-macro="load: main.pt">
<div metal:fill-slot="content">
<div tal:content="structure form"/>
<div class="panel panel-default">
<div class="panel-body">
<div class="col-md-12">
<div tal:content="structure form"/>
</div>
</div>
</div>
</div>
</div>
<html metal:use-macro="load: base3.1.pt">
<div metal:fill-slot="content">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">${request.title}</h3>
</div>
<div class="panel-body">
<form method="post" accept-charset="utf-8" id="myform" class="form-horizontal" enctype="multipart/form-data">
<div class="alert alert-danger" tal:condition="form and form.error and True or False">
<div class="error-msg-lbl">Kesalahan Pengisian Form</div>
<p class="error-msg">${form.errormsg}</p>
</div>
<div class="form-group" tal:repeat="f form">
<div id="item-${f.oid}" style="${f.widget.hidden and 'display:none;' or 'display:block;'}">
<label for="${f.oid}" class="control-label col-md-2 ${f.required and 'required' or ''} " id="req-${f.oid}">
${f.title}</label>
<div class="col-md-3">
${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>
</div>
<div class="col-md-4">
<label class="control-label col-md-3"></label>
<button type="submit" class="btn btn-primary" id="simpan" name="simpan">Simpan</button>
<button type="submit" class="btn btn-warning" id="batal" name="batal">Tutup</button>
</div>
</form>
</div> <!--panel-body-->
</div>
</div>
<div metal:fill-slot="scripts">
<script src="${home}/deform_static/scripts/typeahead.min.js"></script>
<script>
$(document).ready(function () {
$('#departemen_nm').typeahead({"minLength": 1, "remote": "/departemen/hon_level/act?term=%QUERY", "limit": 8});
$('#departemen_nm').bind('typeahead:selected', function (obj, datum, name) {
$('#departemen_id').val(datum.id);
$('#departemen_kd').val(datum.kode);
$('#level_id').val(datum.level_id);
});
$('#departemen_kd').attr("readonly", true);
$('#level_id').attr("readonly", true);
});
</script>
</div>
</html>
\ No newline at end of file
<html metal:use-macro="load: ./base3.1.pt">
<html metal:use-macro="load: ./base3.1.pt"
tal:define="scripts scripts|scripts" >
<div metal:fill-slot="content">
<div class="panel panel-default">
<div class="panel-heading">
......@@ -12,15 +13,15 @@
<div metal:fill-slot="scripts">
<script>
// $(document).ready(function () {
$(document).ready(function () {
// $(".read-only").attr("readonly", true);
// $(".readonly").attr("readonly", true);
// $(".date").attr("readonly", true);
// $(".date").datepicker({
// format: 'dd-mm-yyyy'
// });
<!--? ${structure:scripts}-->
// });
$(".readonly").attr("readonly", true);
$(".date").attr("readonly", true);
// $(".date").datepicker({
// format: 'dd-mm-yyyy'
// });
${structure:scripts}
});
</script>
<div metal:define-slot="scripts"></div>
</div>
......
......@@ -57,7 +57,7 @@
<img alt="" class="icon-modul" src="${home}/static/icon/${modul.replace('/','_')}.png">
<span>${modules[modul]}</span>
</a>
<a tal:condition="modul.find('://')>-1" target="_blank"
<a tal:condition="modul.find('://')>-1"
href="${modul}" class="box">
<img alt="" class="icon-modul"
src="${home}/static/icon/${modul.replace('://','').replace('https','').replace('http','')}.png">
......
<div metal:use-macro="load: main.pt">
<div metal:fill-slot="content">
<div tal:content="structure form"/>
<div class="panel panel-default">
<div class="panel-body">
<div class="col-md-12">
<div tal:content="structure form"/>
</div>
</div>
</div>
</div>
</div>
......@@ -3,20 +3,17 @@
<!-- content -->
<div metal:fill-slot="content">
<div class="row">
<div class="row">
<div class="panel panel-default">
<div class="panel panel-default">
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
<blockquote>
<p>Untuk Background, beri nama background pada file<br>Untuk Logo beri nama logo pada file</p>
</blockquote>
<div tal:content="structure form"/>
</div>
</div>
<div class="panel-body">
<div class="col-md-10 col-md-offset-1">
<div tal:content="structure form"/>
</div>
</div>
</div>
</div>
</div>
<!-- end content -->
......
import os
import colander
from deform import (
Form,
widget,
FileData,
)
from deform import (Form, widget, FileData, )
from deform.interfaces import FileUploadTempStore
from opensipkd.tools import (
get_ext,
dict_to_str,
)
from pyramid.httpexceptions import HTTPFound
from pyramid.view import view_config
from opensipkd.tools import (get_ext, dict_to_str, )
from .view_tools import CSRFSchema
from .. import get_urls
# from unggah import DbUpload
def route_list(request, p={}):
q = dict_to_str(p)
return HTTPFound(location=get_urls(request.route_url('upload-logo', _query=q)))
......@@ -43,46 +38,46 @@ def route_list(request, p={}):
tmpstore = FileUploadTempStore()
class AddSchema(colander.Schema):
class AddSchema(CSRFSchema):
upload = colander.SchemaNode(
FileData(),
widget=widget.FileUploadWidget(tmpstore),
title='Unggah')
typ = colander.SchemaNode(
image_for = colander.SchemaNode(
colander.String(),
widget=widget.SelectWidget(values=(('img', "Image"), ('icon', "Icon"))),
title='Jenis')
widget=widget.SelectWidget(values=(('oth', "Other"), ('logo', "Logo"),
('bg', "Background"))),
title='Peruntukan')
def get_form(schema_cls):
def get_form(request, schema_cls):
schema = schema_cls()
schema = schema.bind(request=request)
return Form(schema, buttons=('simpan', 'batal'))
@view_config(route_name='upload-logo',
renderer='templates/upload.pt',
permission='upload-logo')
permission='upload-logo', require_csrf=True)
def view_file(request):
form = get_form(AddSchema)
form = get_form(request, AddSchema)
if request.POST:
if 'simpan' in request.POST:
input_file = request.POST['upload'].file
filename = request.POST['upload'].filename.lower()
ext = get_ext(filename).lower()
if ext.lower() not in ['.png', '.ico']:
request.session.flash('File harus format png', 'error')
request.session.flash("File harus format 'png' atau 'ico'", 'error')
return dict(form=form.render())
_here = os.path.dirname(__file__)
static_path = os.path.join(os.path.dirname(_here), 'static')
if filename.startswith('logo'):
fname = filename
if request.POST["image_for"] == "logo":
fname = f"logo{ext}"
elif filename.startswith('background'):
elif request.POST["image_for"] == "bg":
fname = f"background{ext}"
else:
fname = filename
folder = os.path.join(static_path, request.POST['typ'])
typ = ext == '.png' and "img" or 'icon'
folder = os.path.join(static_path, typ)
if not os.path.exists(folder):
os.makedirs(folder)
......@@ -94,6 +89,7 @@ def view_file(request):
if not data:
break
output_file.write(data)
request.session.flash(f"Sukses upload {fname}")
return route_list(request)
return dict(form=form.render())
......@@ -124,9 +124,11 @@ class Views(BaseView):
values["email"] = values['email'].lower()
values["user_name"] = re.sub(' ', '', values['user_name']) # .lower()
values["security_code_date"] = create_now()
company_id = request.user and request.user.company_id or "company_id" in values and \
values["company_id"] or None
values["company_id"] = company_id
# company_id = request.user and request.user.company_id or "company_id" in values and \
# values["company_id"] or None
# values["company_id"] = company_id
if "company_id" not in values:
values["company_id"]=None
if 'is_api_key' in values:
values["api_key"] = generate_api_key()
insert = not row
......
......@@ -25,30 +25,28 @@ from datetime import timedelta, datetime
from importlib import import_module
import colander
import requests
from deform import widget, Form, ValidationFailure, Button
from icecream import ic
from pyramid.csrf import new_csrf_token
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from pyramid.renderers import render_to_response
from pyramid.security import remember, forget
from pyramid.view import view_config
from pyramid_mailer.message import Message
from ziggurat_foundations.models.services.external_identity import \
ExternalIdentityService
from ziggurat_foundations.models.services.user import UserService
from opensipkd.base import DBSession, get_params
from opensipkd.base.views import _, one_hour, two_minutes, BaseView
from opensipkd.models import User, ExternalIdentity, Partner
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.buttons import btn_cancel
from opensipkd.tools.form_api import formfield2dict
from .. import get_urls
from .view_tools import CSRFSchema
log = __import__("logging").getLogger(__name__)
class Login(colander.Schema):
class Login(CSRFSchema):
username = colander.SchemaNode(
colander.String(),
widget=widget.TextInputWidget(
......@@ -59,6 +57,15 @@ class Login(colander.Schema):
password = colander.SchemaNode(
colander.String(), widget=widget.PasswordWidget())
# def after_bind(self, schema, kwargs):
# request = kwargs["request"]
# csrf_token = new_csrf_token(request)
# log.error(csrf_token)
# self["csrf_token"] = colander.SchemaNode(
# colander.String(), widget=widget.HiddenWidget(),
# default=csrf_token
# )
# http://deformdemo.repoze.org/interfield/
def login_validator(form, value):
......@@ -161,7 +168,7 @@ def oauth2_login(request, params=None):
class ViewLogin(BaseView):
@view_config(route_name='login', renderer='templates/form.pt')
@view_config(route_name='login', renderer='templates/form.pt', require_csrf=True)
def view_login(self):
request = self.req
request.session["login"] = True
......@@ -174,7 +181,8 @@ class ViewLogin(BaseView):
request.session.flash('Anda sudah login', 'error')
return HTTPFound(location=get_urls(f"{request.route_url('home')}"))
schema = Login(validator=login_validator)
schema = Login()
schema = schema.bind(request=self.req)
form = Form(schema, buttons=('login',))
message = ""
if 'login' in request.POST:
......@@ -190,6 +198,7 @@ class ViewLogin(BaseView):
return HTTPFound(location=get_urls(request.route_url('login')))
values = dict(c)
# start cek external module
pckgs = get_params('external-uim')
if user:
......@@ -234,7 +243,7 @@ class ViewLogin(BaseView):
request.session.flash(str(e), "error")
return render_to_response(
login_tpl, dict(
form=form.render(),
form=form,
message=message,
url=get_urls(request.route_url('login')),
next_url=next_url,
......@@ -245,19 +254,19 @@ class ViewLogin(BaseView):
return HTTPFound(location=get_urls(request.route_url('login')))
if user and user.status == 1:
return redirect_login(request, user)
# values = {"csrf_token": new_csrf_token(request)}
login = ""
if login_tpl == 'templates/login.pt':
return dict(form=form.render(),
message=message,
url=get_urls(request.route_url('login')),
next_url=next_url,
login=login, )
# if login_tpl == 'templates/login.pt':
# return dict(form=form.render(),
# message=message,
# url=get_urls(request.route_url('login')),
# next_url=next_url,
# login=login, )
return render_to_response(
renderer_name=login_tpl,
request=request,
value=dict(form=form.render(),
value=dict(form=form,
message=message,
url=get_urls(request.route_url('login')),
next_url=next_url,
......@@ -290,7 +299,7 @@ btn_home = Button("home", css_class="btn-success")
class Logout(BaseView):
@view_config(route_name='logout', renderer="templates/logout.pt")
@view_config(route_name='logout', renderer="templates/logout.pt", require_csrf=False)
def view_logout(self):
request = self.req
if not request.user:
......@@ -311,6 +320,7 @@ class Logout(BaseView):
if "g_state" in request.cookies:
request.response.delete_cookie("g_state", '/')
form.set_appstruct({"message": "Sukses Logout"})
request.session["login"] = False
return dict(form=form.render())
......@@ -319,23 +329,24 @@ class ChangePassword(colander.Schema):
new_password = colander.SchemaNode(
colander.String(), widget=widget.CheckedPasswordWidget())
# retype_password = colander.SchemaNode(
# colander.String(), widget=widget.PasswordWidget())
# colander.String(), widget=widget.PasswordWidget())
# password = colander.SchemaNode(colander.String(),
# widget=widget.PasswordWidget(),
# title=_("Old Password"))
# widget=widget.PasswordWidget(),
# title=_("Old Password"))
def change_password_validator(form, value):
exc = colander.Invalid(form, '')
user = form.request.user
pass
# exc = colander.Invalid(form, '')
# user = form.request.user
# if not UserService.check_password(user, value["password"]):
# exc["password"] = 'Login Failed'
# raise exc
# exc["password"] = 'Login Failed'
# raise exc
# if value['new_password'] != value['retype_password']:
# exc["new_password"] = 'Retype mismatch.'
# exc["retype_password"] = 'Retype mismatch.'
# raise exc
# exc["new_password"] = 'Retype mismatch.'
# exc["retype_password"] = 'Retype mismatch.'
# raise exc
@view_config(route_name='change-password',
......@@ -449,7 +460,7 @@ def send_email_security_code(
or 'mail.username' not in settings:
return
url = '{}password/{}?password={}'.format(
url = '{}/password/{}?password={}'.format(
request.home, user.security_code, password)
minutes = int(time_remain.seconds / 60)
......
import colander
from pyramid.csrf import new_csrf_token, get_csrf_token
from opensipkd.base.views import widget
class CSRFSchema(colander.Schema):
def after_bind(self, schema, kwargs):
request = kwargs["request"]
csrf_token = get_csrf_token(request)
if not csrf_token:
csrf_token = new_csrf_token(request)
self["csrf_token"] = colander.SchemaNode(
colander.String(), widget=widget.HiddenWidget(),
default=csrf_token
)
import json
import logging
from colander import SchemaNode, null, Mapping, Invalid, string_types
from deform.widget import Widget, _StrippedString, Select2Widget, default_resources, \
ResourceRegistry, default_resource_registry
from deform.form import Button
from iso8601.iso8601 import ISO8601_REGEX
from deform.i18n import _
from colander import SchemaNode, null, Mapping, Invalid # , string_types
from colander import compat
from deform import widget
from deform.form import Button
from deform.i18n import _
from deform.widget import Widget, _StrippedString, Select2Widget, \
DateInputWidget as WidgetDateInputWidget
from deform.widget import string_types
from iso8601.iso8601 import ISO8601_REGEX
_logging = logging.getLogger(__name__)
......@@ -388,7 +390,7 @@ class MapWidget(Widget):
{
"js": "opensipkd.base:static/js/gmap.js",
"css": "deform:static/select2/select2.css",
},)
},)
def __init__(self, **kw):
super().__init__(**kw)
......@@ -709,3 +711,7 @@ class TextInputWidget(widget.TextInputWidget):
if isinstance(self.button, compat.string_types):
self.button = Button(self.button, type="button")
class DateInputWidget(WidgetDateInputWidget):
type_name = "text"
......@@ -18,9 +18,11 @@
'${oid}',
function deform_cb(oid) {
if (!Modernizr.inputtypes['date'] ||"${type_name}" != "date" || window.forceDateTimePolyfill){
console.log(${options_json});
$('#' + oid).pickadate(${options_json});
}
}
);
</script>
</div>
<tal:block tal:define="oid oid|field.oid;
css_class css_class|field.widget.css_class;
style style|field.widget.style;
preview_url cstruct.get('preview_url')|cstruct.get('base64')|'';
preview_url cstruct.get('preview_url')|'';
ext str(cstruct.get('filename').split('.')[-1:][0]).lower()|[];
fname str(cstruct.get('filename'))|'';
delete cstruct.get('delete')|'';">
delete cstruct.get('delete')|'';
maxsize field.widget.size|5242880;">
${field.start_mapping()}
<tal:block tal:condition="preview_url and ext in ['jpg','jpeg','png','bmp','gif']">
<img id="preview-${oid}" alt="" src="${structure: preview_url}" style="width:100px;height:auto;"
onload="window.URL.revokeObjectURL(this.src)"></img>
<br>
</tal:block>
<tal:block tal:condition="not preview_url or ext not in ['jpg','jpeg','png','bmp','gif']">
<img id="preview-${oid}" alt="" src="" style="width:100px;height:auto;"
onload="window.URL.revokeObjectURL(this.src)"></img>
<br>
</tal:block>
<a id="label-${oid}" tal:condition="preview_url" class="label label-default" href="${structure: preview_url}"
<img id="preview-${oid}" alt="" src="${preview_url}" style="width:100px;height:auto;display:block;"
onload="window.URL.revokeObjectURL(this.src);"></img>
<a id="label-${oid}" tal:condition="preview_url" class="label label-default" href="${preview_url}"
target="_blank"><i class="fa fa-search"></i> View</a>
<button id="labeldelete-${oid}" type="button" tal:condition="delete" class="label label-danger" href="#"
target="_blank"
onclick="var daft = document.getElementById('daftar_file_hapus').value.replace(/\[|\]/g,'').split(',');
daft.push('${fname}');
document.getElementById('daftar_file_hapus').value=daft;
document.getElementById('${oid}-close').click();">
<i class="fa fa-remove"></i> Delete</button>
<input type="file" name="upload" id="${oid}"
tal:attributes="style style;
accept accept|field.widget.accept;
data-filename fname;
attributes|field.widget.attributes|{};"
onchange="document.getElementById('preview-'+this.id).src = window.URL.createObjectURL(this.files[0]);
document.getElementById('labeldelete-'+this.id).remove();
document.getElementById('label-'+this.id).remove();"/>
attributes|field.widget.attributes|{};"/>
<input tal:define="uid cstruct.get('uid')"
tal:condition="uid"
type="hidden" name="uid" value="${uid}"/>
......@@ -41,6 +24,15 @@
deform.addCallback('${oid}', function (oid) {
$('#' + oid).upload();
});
document.getElementById("${oid}").onchange = function() {
if(this.files[0].size > ${maxsize}){
alert("File is too big!");
this.value = "";
document.getElementById('preview-'+this.id).src = '';
}
document.getElementById('preview-'+this.id).src = window.URL.createObjectURL(this.files[0]);
document.getElementById('labeldelete-'+this.id).remove();
document.getElementById('label-'+this.id).remove();
};
</script>
</tal:block>
<tal:block tal:define="name name|field.name;
oid oid|field.oid;">
<div class="input">
<input
type="password"
name="${name}"
onkeyup="checkPasswordStrength${oid}();"
value="${field.widget.redisplay and cstruct or ''}"
tal:attributes="style style|field.widget.style;
class string: form-control ${css_class|field.widget.css_class or ''};
attributes|field.widget.attributes|{};"
id="${oid}"/>
<div class="checkbox">
<tal:block tal:define="
name name|field.name;
oid oid|field.oid;
">
<div class="input">
<input
type="password"
name="${name}"
value="${field.widget.redisplay and cstruct or ''}"
tal:attributes="style style|field.widget.style;
class string: form-control ${css_class|field.widget.css_class or ''};
attributes|field.widget.attributes|{};"
id="${oid}"/>
<!--? onkeyup="checkPasswordStrength${oid}();"-->
<div class="checkbox">
<label>
<input type="checkbox" id="view${oid}">
<span>Show Password</span>
<input type="checkbox" id="view${oid}">
<span>Show Password</span>
</label>
</div>
<div id="${oid}-password-strength-status"></div>
</div>
<div id="${oid}-password-strength-status"></div>
</div>
<style>
#password-strength-status {
padding: 5px 10px;
border-radius: 4px;
margin-top: 5px;
}
</style>
<script type="text/javascript">
$('#view${oid}').change(function(){
if ($(this).prop('checked')==true){
$('#${oid}').attr('type','text');
}
else {
$('#${oid}').attr('type','password');
}
<style>
#password-strength-status {
padding: 5px 10px;
border-radius: 4px;
margin-top: 5px;
}
</style>
<script type="text/javascript">
$('#view${oid}').change(function () {
if ($(this).prop('checked') == true) {
$('#${oid}').attr('type', 'text');
} else {
$('#${oid}').attr('type', 'password');
}
});
function checkPasswordStrength${oid}() {
var number = /([0-9])/;
var alphabets = /([a-zA-Z])/;
var special_characters = /([~,!,@,#,$,%,^,&,*,-,_,+,=,?,>,<,\),\(,{,},\[,\]])/;
var password = $('#${oid}').val().trim();
if (password.length <= 8) {
$('#${oid}-password-strength-status').removeClass();
$('#${oid}-password-strength-status').addClass('label label-danger');
$('#${oid}-password-strength-status').html("Weak (should be atleast 8 characters.)");
var number = /([0-9])/;
var alphabets = /([a-zA-Z])/;
var special_characters = /([~,!,@,#,$,%,^,&,*,-,_,+,=,?,>,<,\),\(,{,},\[,\]])/;
var password = $('#${oid}').val().trim();
if (password.length <= 8) {
$('#${oid}-password-strength-status').removeClass();
$('#${oid}-password-strength-status').addClass('label label-danger');
$('#${oid}-password-strength-status').html("Weak (should be atleast 8 characters.)");
} else {
if (password.match(number) && password.match(alphabets) && password.match(special_characters)) {
$('#${oid}-password-strength-status').removeClass();
$('#${oid}-password-strength-status').addClass('label label-success');
$('#${oid}-password-strength-status').html("Strong");
} else {
if (password.match(number) && password.match(alphabets) && password.match(special_characters)) {
$('#${oid}-password-strength-status').removeClass();
$('#${oid}-password-strength-status').addClass('label label-success');
$('#${oid}-password-strength-status').html("Strong");
}
else {
$('#${oid}-password-strength-status').removeClass();
$('#${oid}-password-strength-status').addClass('label label-warning');
$('#${oid}-password-strength-status').html("Medium (should include alphabets, numbers and special characters.)");
}
$('#${oid}-password-strength-status').removeClass();
$('#${oid}-password-strength-status').addClass('label label-warning');
$('#${oid}-password-strength-status').html("Medium (should include alphabets, numbers and special characters.)");
}
}
}
</script>
</script>
</tal:block>
......@@ -99,7 +99,6 @@
<input type="hidden" name="__end__" value="${field.name}:sequence"/>
<!-- /sequence -->
<input tal:define="templates subfields[0][1].widget.template|''" tal:condition="templates=='file_upload'" id="daftar_file_hapus" name="daftar_file_hapus" type="hidden">
</div>
</div>
......
......@@ -12,8 +12,8 @@ from sqlalchemy import inspect as sa_inspect
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)
print("Session:", clause, params, mapper, kw)
return Session.execute(self, clause, params) #, mapper
session_factory = sessionmaker(class_=MySession)
......
from sqlalchemy import Column, String, SmallInteger, Integer, DateTime, ForeignKey
from sqlalchemy.orm import relationship, backref
from .users import User
from .meta import Base
from .base import NamaModel, DefaultModel, DBSession, KodeModel
from .meta import Base
from .partner import Partner
from .users import User
class Route(Base, NamaModel):
......@@ -42,7 +42,14 @@ class UserDeviceModel(Base, KodeModel):
expired = Column(DateTime(timezone=True))
user = relationship(User, backref=backref("devices"))
class ResCompany(Base, NamaModel):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
partner_id = Column(Integer, ForeignKey(Partner.id))
partner = relationship(Partner, backref=backref("company"))
partner = relationship("Partner", backref=backref("company"))
parent_id = Column(Integer, ForeignKey("company.id"))
children = relationship("ResCompany")
parent = relationship(
"ResCompany", remote_side=[id], primaryjoin="ResCompany.parent_id==ResCompany.id"
)
......@@ -10,6 +10,7 @@ from sqlalchemy.orm import (
backref
)
from . import ResCompany
from ..models import DBSession, Base
from ..models import (NamaModel,
TABLE_ARGS)
......@@ -26,7 +27,7 @@ class Departemen(Base, NamaModel):
level_id = Column(SmallInteger)
children = relationship(
"Departemen", backref=backref('parent', remote_side=[id]))
company_id = Column(Integer)
company_id = Column(Integer, ForeignKey(ResCompany.id))
def get_parents(self, start=False):
allparents = []
......
......@@ -16,7 +16,6 @@ psycopg2-binary
alembic>=0.3.4
pytz
sqlalchemy-datatables
z3c.rml
py3o.template
wheezy.captcha
google-api-python-client
......
......@@ -10,7 +10,9 @@ with open(os.path.join(here, 'CHANGES.txt')) as f:
line = CHANGES.splitlines()[0]
version = line.split()[0]
requires = [
'sqlalchemy==1.4.50',
'wheel',
'colander==1.8.3',
'pyramid',
'pyramid_tm',
'SQLAlchemy',
......@@ -21,7 +23,7 @@ requires = [
'ziggurat-foundations',
'zope.sqlalchemy',
'pytz',
'deform >= 2.0a2',
'deform',
'psycopg2-binary',
'pyramid_chameleon',
'pyramid_rpc',
......@@ -33,8 +35,8 @@ requires = [
'google-api-python-client',
'google',
'pyjwt',
'z3c.rml',
'opensipkd-tools @git+https://git.opensipkd.com/aa.gusti/opensipkd-tools.git',
# 'z3c.rml',
# 'opensipkd-tools @git+https://git.opensipkd.com/aa.gusti/opensipkd-tools.git',
]
dev_requires = [
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!