Commit 1f950e88 by Owo Sugiana

Kali pertama

0 parents
Showing 93 changed files with 4527 additions and 0 deletions
*egg-info
*.pyc
__pycache__
*pytest_cache
test-*
test.ini
0.1 2019-02-04
--------------
- Kali pertama
include *.txt *.ini *.rst
recursive-include web_starter *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak
Daemon ISO8583
==============
Daemon ISO8583 ini bisa sebagai client maupun server. Berikut ini cara
pemasangannya. Nama paketnya mengandung web karena ia bisa dihubungi melalui
web service JsonRPC untuk melakukan inquiry, payment, dan reversal.
Pindahlah ke direktorinya::
$ cd iso8583-web
Buat Python virtual environment::
$ python3 -m venv ../env
Pasang paket yang dibutuhkan::
$ ../env/bin/python setup.py develop-use-pip
Salinlah file konfigurasi::
$ cp iso8583.ini test-pemda.ini
Sesuaikan isi section ``[host_bjb]``. Anda bisa menambahkan host lainnya baik
bank, pemda, bahkan payment gateway dengan menambahkan awalan ``host_`` pada
section.
Kemudian lakukan uji coba::
$ ../env/bin/pip install -e '.[testing]'
$ ../env/bin/pytest iso8583_web/test-conf.py
Pastikan tidak ada pesan ``failure``. Bila ada ``warning`` abaikan saja.
Jalankan daemon-nya, anggap sebagai pemda (biller)::
$ ../env/bin/iso8583 test-pemda.ini
Anda akan mendapat pesan seperti ini::
2019-02-07 20:41:30,179 INFO Web server listen at 0.0.0.0:6543
2019-02-07 20:41:30,180 INFO Connect to 127.0.0.1 port 10002
2019-02-07 20:41:30,180 ERROR [Errno 111] Connection refused
Biarkan ini aktif. Buka konsol lain. Sekarang kita buat simulator BJB::
$ cp iso8583.ini test-bjb.ini
Ubah file ``test-bjb.ini`` pada baris::
[web]
port = 6543
[host_bjb]
ip = 127.0.0.1
port = 10002
listen = false
streamer = bjb_with_suffix
menjadi::
[web]
port = 6544
[host_pemda]
ip = 127.0.0.1
port = 10002
listen = true
streamer = bjb_with_suffix
Simpan, lalu jalankan::
../env/bin/iso8583 test-bjb.ini
# A generic, single database configuration.
[alembic]
# path to migration scripts
script_location = alembic
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =
# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = {db_url}
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
Generic single-database configuration.
\ No newline at end of file
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = None
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
"""alter users date fields with time zone
Revision ID: 074b33635316
Revises:
Create Date: 2018-10-11 12:00:48.568483
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '074b33635316'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
op.alter_column('users', 'last_login_date',
type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False))
op.alter_column('users', 'registered_date',
type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False))
op.alter_column('users', 'security_code_date',
type_=sa.DateTime(timezone=True),
existing_type=sa.DateTime(timezone=False))
def downgrade():
pass
delete from users;
select setval('users_id_seq', 1, false);
drop table external_identities;
drop table users_resources_permissions;
drop table groups_resources_permissions;
drop table groups_permissions;
drop table resources;
drop table users_groups;
drop table users_permissions;
drop table groups;
drop table users;
drop table alembic_version;
###
# app configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
use = egg:iso8583_web
pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = id
pyramid.includes =
pyramid_debugtoolbar
sqlalchemy.url = postgresql://user:pass@localhost/dbname
timezone = Asia/Jakarta
localization = id_ID.UTF-8
mail.host = localhost
mail.port = 25
mail.username = user@example.com
mail.password = FIXME
mail.sender_name = Example Name
# By default, the toolbar only appears for clients from IP addresses
# '127.0.0.1' and '::1'.
# debugtoolbar.hosts = 127.0.0.1 ::1
###
# wsgi server configuration
###
[server:main]
use = egg:waitress#main
listen = localhost:6543
###
# logging configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
keys = root, iso8583_web
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_iso8583_web]
level = DEBUG
handlers =
qualname = iso8583_web
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
###
# logging configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
keys = root, iso8583_web
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_iso8583_web]
level = DEBUG
handlers =
qualname = iso8583_web
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)s %(message)s
[web]
port = 6543
[host_bjb]
ip = 127.0.0.1
port = 10002
listen = false
streamer = bjb_with_suffix
[host_mitracomm]
ip = 127.0.0.1
port = 8583
streamer = mitracomm
active = false
import os
import csv
import deform
from pkg_resources import resource_filename
from pyramid.i18n import get_localizer
from pyramid.threadlocal import get_current_request
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_mailer import mailer_factory_from_settings
from sqlalchemy import engine_from_config
from .models import (
DBSession,
Base,
)
from .security import (
group_finder,
get_user,
)
from .tools.this_framework import get_locale_name
from .views import RemoveSlashNotFoundViewFactory
here = os.path.abspath(os.path.dirname(__file__))
routes_file = os.path.join(here, 'routes.csv')
def set_paths(config):
with open(routes_file) as f:
c = csv.DictReader(f)
for row in c:
path = row['path'] or '/' + row['name']
config.add_route(row['name'], path)
config.scan()
def translator(term):
return get_localizer(get_current_request()).translate(term)
deform_template_dir = resource_filename('deform', 'templates/')
zpt_renderer = deform.ZPTRendererFactory(
[deform_template_dir], translator=translator)
deform.Form.set_default_renderer(zpt_renderer)
def main(global_config, **settings):
""" This function returns a Pyramid WSGI application.
"""
engine = engine_from_config(settings, 'sqlalchemy.')
DBSession.configure(bind=engine)
Base.metadata.bind = engine
session_factory = session_factory_from_settings(settings)
config = Configurator(
settings=settings,
root_factory='iso8583_web.models.ziggurat.RootFactory',
session_factory=session_factory,
locale_negotiator=get_locale_name)
config.include('pyramid_tm')
config.include('pyramid_beaker')
config.include('pyramid_chameleon')
authn_policy = AuthTktAuthenticationPolicy(
'sosecret', callback=group_finder, hashalg='sha512')
config.set_authentication_policy(authn_policy)
authz_policy = ACLAuthorizationPolicy()
config.set_authorization_policy(authz_policy)
config.add_request_method(get_user, 'user', reify=True)
config.add_notfound_view(RemoveSlashNotFoundViewFactory())
config.add_static_view('static', 'static', cache_max_age=3600)
config.add_static_view('deform_static', 'deform:static')
config.add_translation_dirs('locale')
config.registry['mailer'] = mailer_factory_from_settings(settings)
set_paths(config)
return config.make_wsgi_app()
# Translations template for colander.
# Copyright (C) 2016 ORGANIZATION
# This file is distributed under the same license as the colander project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: colander 1.3.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2016-09-18 22:57+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
#: colander/__init__.py:294
msgid "Invalid value"
msgstr ""
#: colander/__init__.py:340
msgid "String does not match expected pattern"
msgstr ""
#: colander/__init__.py:359
msgid "Invalid email address"
msgstr ""
#: colander/__init__.py:387
msgid "${val} is less than minimum value ${min}"
msgstr ""
#: colander/__init__.py:388
msgid "${val} is greater than maximum value ${max}"
msgstr ""
#: colander/__init__.py:435
msgid "Shorter than minimum length ${min}"
msgstr ""
#: colander/__init__.py:436
msgid "Longer than maximum length ${max}"
msgstr ""
#: colander/__init__.py:464
msgid "\"${val}\" is not one of ${choices}"
msgstr ""
#: colander/__init__.py:479
msgid "\"${val}\" must not be one of ${choices}"
msgstr ""
#: colander/__init__.py:501
msgid "One or more of the choices you made was not acceptable"
msgstr ""
#: colander/__init__.py:523 colander/__init__.py:528
msgid "\"${val}\" is not a valid credit card number"
msgstr ""
#: colander/__init__.py:549
msgid "Must be a URL"
msgstr ""
#: colander/__init__.py:553
msgid "Invalid UUID string"
msgstr ""
#: colander/__init__.py:650
msgid "\"${val}\" is not a mapping type: ${err}"
msgstr ""
#: colander/__init__.py:694
msgid "Unrecognized keys in mapping: \"${val}\""
msgstr ""
#: colander/__init__.py:789 colander/__init__.py:1020
msgid "\"${val}\" is not iterable"
msgstr ""
#: colander/__init__.py:797
msgid "\"${val}\" has an incorrect number of elements (expected ${exp}, was ${was})"
msgstr ""
#: colander/__init__.py:936 colander/__init__.py:967
msgid "${cstruct} is not iterable"
msgstr ""
#: colander/__init__.py:1254
msgid "${val} cannot be serialized: ${err}"
msgstr ""
#: colander/__init__.py:1275
msgid "${val} is not a string: ${err}"
msgstr ""
#: colander/__init__.py:1295 colander/__init__.py:1306
msgid "\"${val}\" is not a number"
msgstr ""
#: colander/__init__.py:1454
msgid "${val} is not a string"
msgstr ""
#: colander/__init__.py:1465
msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})"
msgstr ""
#: colander/__init__.py:1525 colander/__init__.py:1542 colander/__init__.py:1552
msgid "relative name \"${val}\" irresolveable without package"
msgstr ""
#: colander/__init__.py:1582
msgid "\"${val}\" has no __name__"
msgstr ""
#: colander/__init__.py:1591
msgid "\"${val}\" is not a string"
msgstr ""
#: colander/__init__.py:1600
msgid "The dotted name \"${name}\" cannot be imported"
msgstr ""
#: colander/__init__.py:1648 colander/__init__.py:1722
msgid "Invalid date"
msgstr ""
#: colander/__init__.py:1662
msgid "\"${val}\" is not a datetime object"
msgstr ""
#: colander/__init__.py:1733
msgid "\"${val}\" is not a date object"
msgstr ""
#: colander/__init__.py:1794
msgid "Invalid time"
msgstr ""
#: colander/__init__.py:1804
msgid "\"${val}\" is not a time object"
msgstr ""
#: colander/tests/test_colander.py:334 colander/tests/test_colander.py:341
msgid "fail ${val}"
msgstr ""
#: colander/tests/test_colander.py:537
msgid "${val}: ${choices}"
msgstr ""
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2016-11-19 13:28+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Generated-By: Lingua 4.11\n"
#: ./deform/schema.py:109
#, python-format
msgid "${value} is not a dictionary"
msgstr ""
#: ./deform/schema.py:116
#, python-format
msgid "${value} has no ${key} key"
msgstr ""
#: ./deform/widget.py:718 ./deform/widget.py:1800
msgid "Incomplete date"
msgstr ""
#: ./deform/widget.py:721
msgid "Incomplete time"
msgstr ""
#: ./deform/widget.py:1253
msgid "Fields did not match"
msgstr ""
#: ./deform/widget.py:1254
msgid "Value"
msgstr ""
#: ./deform/widget.py:1255
msgid "Confirm Value"
msgstr ""
#: ./deform/widget.py:1321
msgid "Password did not match confirm"
msgstr ""
#: ./deform/widget.py:1485
#, python-format
msgid "Add ${subitem_title}"
msgstr ""
#: ./deform/templates/checked_password.pt:15
msgid "Password"
msgstr ""
#: ./deform/templates/checked_password.pt:25
msgid "Confirm Password"
msgstr ""
#: ./deform/templates/dateparts.pt:10
msgid "Year"
msgstr ""
#: ./deform/templates/dateparts.pt:18
msgid "Month"
msgstr ""
#: ./deform/templates/dateparts.pt:26
msgid "Day"
msgstr ""
#: ./deform/templates/datetimeinput.pt:9
msgid "Date"
msgstr ""
#: ./deform/templates/datetimeinput.pt:16
msgid "Time"
msgstr ""
#: ./deform/templates/form.pt:35
msgid "There was a problem with your submission"
msgstr ""
#: ./deform/templates/form.pt:37
msgid "Errors have been highlighted below"
msgstr ""
#: ./deform/templates/mapping.pt:13 ./deform/templates/mapping_accordion.pt:29
msgid "There was a problem with this section"
msgstr ""
#. Default: en
#: ./deform/templates/richtext.pt:30
msgid "language-code"
msgstr ""
#: ./deform/templates/sequence_item.pt:26
msgid "Reorder (via drag and drop)"
msgstr ""
#: ./deform/templates/sequence_item.pt:31
msgid "Remove"
msgstr ""
#: ./deform/templates/readonly/checkbox.pt:8
msgid "True"
msgstr ""
#: ./deform/templates/readonly/checkbox.pt:12
msgid "False"
msgstr ""
#: ./deform/templates/readonly/checked_password.pt:4
msgid "Password not displayed."
msgstr ""
#: ./deform/tests/test_widget.py:1752
#, python-format
msgid "Yo ${subitem_title}"
msgstr ""
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-15 20:15+0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Copyright &copy; Pylons Project
msgid "copyright"
msgstr ""
msgid "welcome"
msgstr ""
msgid "you-are-logged-in"
msgstr ""
msgid "login-link"
msgstr ""
msgid "Home"
msgstr ""
No preview for this file type
# Translations template for colander.
# Copyright (C) 2016 ORGANIZATION
# This file is distributed under the same license as the colander project.
# Owo Sugiana <sugiana@gmail.com>, 2016.
#
msgid ""
msgstr ""
"Project-Id-Version: colander 1.3.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2016-09-18 22:57+0200\n"
"PO-Revision-Date: 2018-10-14 19:52+0700\n"
"Last-Translator: Owo Sugiana <sugiana@gmail.com>\n"
"Language-Team: Indonesian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
"Language: id\n"
#: colander/__init__.py:294
msgid "Invalid value"
msgstr ""
#: colander/__init__.py:340
msgid "String does not match expected pattern"
msgstr ""
#: colander/__init__.py:359
msgid "Invalid email address"
msgstr ""
#: colander/__init__.py:387
msgid "${val} is less than minimum value ${min}"
msgstr ""
#: colander/__init__.py:388
msgid "${val} is greater than maximum value ${max}"
msgstr ""
#: colander/__init__.py:435
msgid "Shorter than minimum length ${min}"
msgstr ""
#: colander/__init__.py:436
msgid "Longer than maximum length ${max}"
msgstr ""
#: colander/__init__.py:464
msgid "\"${val}\" is not one of ${choices}"
msgstr ""
#: colander/__init__.py:479
msgid "\"${val}\" must not be one of ${choices}"
msgstr ""
#: colander/__init__.py:501
msgid "One or more of the choices you made was not acceptable"
msgstr ""
#: colander/__init__.py:523 colander/__init__.py:528
msgid "\"${val}\" is not a valid credit card number"
msgstr ""
#: colander/__init__.py:549
msgid "Must be a URL"
msgstr ""
#: colander/__init__.py:553
msgid "Invalid UUID string"
msgstr ""
#: colander/__init__.py:650
msgid "\"${val}\" is not a mapping type: ${err}"
msgstr ""
#: colander/__init__.py:694
msgid "Unrecognized keys in mapping: \"${val}\""
msgstr ""
#: colander/__init__.py:789 colander/__init__.py:1020
msgid "\"${val}\" is not iterable"
msgstr ""
#: colander/__init__.py:797
msgid ""
"\"${val}\" has an incorrect number of elements (expected ${exp}, was ${was})"
msgstr ""
#: colander/__init__.py:936 colander/__init__.py:967
msgid "${cstruct} is not iterable"
msgstr ""
#: colander/__init__.py:1254
msgid "${val} cannot be serialized: ${err}"
msgstr ""
#: colander/__init__.py:1275
msgid "${val} is not a string: ${err}"
msgstr ""
#: colander/__init__.py:1295 colander/__init__.py:1306
msgid "\"${val}\" is not a number"
msgstr ""
#: colander/__init__.py:1454
msgid "${val} is not a string"
msgstr ""
#: colander/__init__.py:1465
msgid "\"${val}\" is neither in (${false_choices}) nor in (${true_choices})"
msgstr ""
#: colander/__init__.py:1525 colander/__init__.py:1542
#: colander/__init__.py:1552
msgid "relative name \"${val}\" irresolveable without package"
msgstr ""
#: colander/__init__.py:1582
msgid "\"${val}\" has no __name__"
msgstr ""
#: colander/__init__.py:1591
msgid "\"${val}\" is not a string"
msgstr ""
#: colander/__init__.py:1600
msgid "The dotted name \"${name}\" cannot be imported"
msgstr ""
#: colander/__init__.py:1648 colander/__init__.py:1722
msgid "Invalid date"
msgstr ""
#: colander/__init__.py:1662
msgid "\"${val}\" is not a datetime object"
msgstr ""
#: colander/__init__.py:1733
msgid "\"${val}\" is not a date object"
msgstr ""
#: colander/__init__.py:1794
msgid "Invalid time"
msgstr ""
#: colander/__init__.py:1804
msgid "\"${val}\" is not a time object"
msgstr ""
#: colander/tests/test_colander.py:334 colander/tests/test_colander.py:341
msgid "fail ${val}"
msgstr ""
#: colander/tests/test_colander.py:537
msgid "${val}: ${choices}"
msgstr ""
msgid "Required"
msgstr "Dibutuhkan"
No preview for this file type
#
# Indonesian translations for PACKAGE package
# This file is distributed under the same license as the PACKAGE package.
# Owo Sugiana <sugiana@gmail.com>, 2016.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2016-11-19 13:28+0200\n"
"PO-Revision-Date: 2018-10-14 19:45+0700\n"
"Last-Translator: Owo Sugiana <sugiana@gmail.com>\n"
"Language-Team: Indonesian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: id\n"
"Generated-By: Lingua 4.11\n"
#: deform/schema.py:109
#, python-format
msgid "${value} is not a dictionary"
msgstr ""
#: deform/schema.py:116
#, python-format
msgid "${value} has no ${key} key"
msgstr ""
#: deform/widget.py:718 deform/widget.py:1800
msgid "Incomplete date"
msgstr ""
#: deform/widget.py:721
msgid "Incomplete time"
msgstr ""
#: deform/widget.py:1253
msgid "Fields did not match"
msgstr ""
#: deform/widget.py:1254
msgid "Value"
msgstr ""
#: deform/widget.py:1255
msgid "Confirm Value"
msgstr ""
#: deform/widget.py:1321
msgid "Password did not match confirm"
msgstr ""
#: deform/widget.py:1485
#, python-format
msgid "Add ${subitem_title}"
msgstr ""
#: deform/templates/checked_password.pt:15
msgid "Password"
msgstr ""
#: deform/templates/checked_password.pt:25
msgid "Confirm Password"
msgstr ""
#: deform/templates/dateparts.pt:10
msgid "Year"
msgstr ""
#: deform/templates/dateparts.pt:18
msgid "Month"
msgstr ""
#: deform/templates/dateparts.pt:26
msgid "Day"
msgstr ""
#: deform/templates/datetimeinput.pt:9
msgid "Date"
msgstr ""
#: deform/templates/datetimeinput.pt:16
msgid "Time"
msgstr ""
#: deform/templates/form.pt:35
msgid "There was a problem with your submission"
msgstr "Ada masalah dengan apa yang Anda kirim"
#: deform/templates/form.pt:37
msgid "Errors have been highlighted below"
msgstr "Pesan kesalahannya ada di bawah ini"
#: deform/templates/mapping.pt:13 deform/templates/mapping_accordion.pt:29
msgid "There was a problem with this section"
msgstr ""
#. Default: en
#: deform/templates/richtext.pt:30
msgid "language-code"
msgstr ""
#: deform/templates/sequence_item.pt:26
msgid "Reorder (via drag and drop)"
msgstr ""
#: deform/templates/sequence_item.pt:31
msgid "Remove"
msgstr ""
#: deform/templates/readonly/checkbox.pt:8
msgid "True"
msgstr ""
#: deform/templates/readonly/checkbox.pt:12
msgid "False"
msgstr ""
#: deform/templates/readonly/checked_password.pt:4
msgid "Password not displayed."
msgstr ""
#: deform/tests/test_widget.py:1752
#, python-format
msgid "Yo ${subitem_title}"
msgstr ""
No preview for this file type
#
# Indonesian translations for PACKAGE package
# This file is distributed under the same license as the PACKAGE package.
# Owo Sugiana <sugiana@gmail.com>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-15 20:15+0700\n"
"PO-Revision-Date: 2018-10-19 17:11+0700\n"
"Last-Translator: Owo Sugiana <sugiana@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Copyright &copy; Pylons Project
msgid "copyright"
msgstr "Hak cipta &copy; Pylons Project"
msgid "welcome"
msgstr "Selamat datang di <span class=\"font-normal\">Web Starter</span>, sebuah aplikasi Pyramid yang dibentuk oleh<br><span class=\"font-normal\">Cookiecutter</span>."
msgid "you-are-logged-in"
msgstr "Anda sedang masuk sebagai ${username}. Klik <a href=\"/logout\">di sini</a> untuk keluar."
msgid "login-link"
msgstr "Silakan masuk"
msgid "Home"
msgstr "Beranda"
#
# Indonesian translations for PACKAGE package
# This file is distributed under the same license as the PACKAGE package.
# Owo Sugiana <sugiana@gmail.com>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-15 08:41+0700\n"
"PO-Revision-Date: 2018-10-15 08:41+0700\n"
"Last-Translator: Owo Sugiana <sugiana@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Enter new password for ${name}:
msgid "ask-password-1"
msgstr "Masukkan password baru untuk ${name}: "
#. Default: Retype new password for ${name}:
msgid "ask-password-2"
msgstr "Ulangi password baru untuk ${name}: "
msgid "Sorry, passwords do not match"
msgstr "Maaf, kedua password tidak sama"
No preview for this file type
#
# Indonesian translations for PACKAGE package
# This file is distributed under the same license as the PACKAGE package.
# Owo Sugiana <sugiana@gmail.com>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-13 22:28+0700\n"
"PO-Revision-Date: 2018-10-13 22:30+0700\n"
"Last-Translator: Owo Sugiana <sugiana@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Login failed
#: views/login.py:42
msgid "Login failed"
msgstr "Gagal masuk"
#. Default: Login
#: views/login.py:58
msgid "Login"
msgstr "Masuk"
msgid "Submit"
msgstr "Kirim"
msgid "Username"
msgstr "Nama"
msgid "Password"
msgstr "Kata kunci"
msgid "Reset password"
msgstr "Pemulihan kata kunci"
msgid "email-reset-password"
msgstr "Tulis email Anda dan kami akan mengirimkan tautan untuk penetapan ulang kata kunci"
msgid "Send password reset email"
msgstr "Kirim email"
msgid "reset-password-body"
msgstr "Kami menerima permintaan pemulihan kata sandi. Silakan klik tautan berikut:\n\n${url}\n\nTautan ini akan kedaluwarsa dalam ${minutes} menit. Mohon abaikan jika Anda tidak memintanya."
msgid "reset-password-link-sent"
msgstr "Periksa email Anda untuk tautan pemulihan kata kunci. Jika tidak muncul dalam beberapa menit, periksa di bagian spam."
msgid "Invalid email"
msgstr "Email tidak terdaftar"
msgid "Forgot password"
msgstr "Lupa kata kunci"
msgid "change-password-done"
msgstr "Kata kunci Anda telah diubah"
msgid "Save"
msgstr "Simpan"
msgid "Cancel"
msgstr "Batalkan"
msgid "Invalid old password"
msgstr "Kata kunci yang lama tidak benar"
msgid "Retype mismatch"
msgstr "Pengulangan kata kunci yang baru tidak sama"
msgid "Old password"
msgstr "Kata kunci yang lama"
msgid "New password"
msgstr "Kata kunci yang baru"
msgid "Retype new password"
msgstr "Ulangi kata kunci yang baru"
msgid "Change password"
msgstr "Ganti kata kunci"
msgid "Invalid security code"
msgstr "Kode keamananan tidak benar"
No preview for this file type
#
# Indonesian translations for PACKAGE package
# This file is distributed under the same license as the PACKAGE package.
# Owo Sugiana <sugiana@gmail.com>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-28 21:10+0700\n"
"PO-Revision-Date: 2018-10-28 21:11+0700\n"
"Last-Translator: Owo Sugiana <sugiana@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Home
#: views/templates/layout-menu.pt:40
msgid "Home"
msgstr "Beranda"
#. Default: Admin<dynamic element>
#: views/templates/layout-menu.pt:44
msgid "Admin"
msgstr "Admin"
#. Default: Users
#: views/templates/layout-menu.pt:46
msgid "Users"
msgstr "Pengguna"
#. Default: Add user
#: views/templates/layout-menu.pt:47
msgid "Add user"
msgstr "Tambah pengguna"
#. Default: My account <dynamic element>
#: views/templates/layout-menu.pt:54
msgid "My account"
msgstr "Akun saya"
#. Default: Change password
#: views/templates/layout-menu.pt:56
msgid "Change password"
msgstr "Ubah kata kunci"
#. Default: ${username} logout
#: views/templates/layout-menu.pt:57
msgid "username-logout"
msgstr "${username} keluar"
msgid "Users groups"
msgstr "Kelompok pengguna"
msgid "Add users group"
msgstr "Tambah kelompok pengguna"
No preview for this file type
#
# Indonesian translations for PACKAGE package
# This file is distributed under the same license as the PACKAGE package.
# Owo Sugiana <sugiana@gmail.com>, 2018.
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-27 14:20+0700\n"
"PO-Revision-Date: 2018-10-27 14:49+0700\n"
"Last-Translator: Owo Sugiana <sugiana@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#: web_starter/views/user.py:43
msgid "Users"
msgstr "Pengguna"
#: web_starter/views/user.py:78
msgid "Invalid email format"
msgstr "Susunan email tidak benar"
#. Default: Email ${email} already used by user ID ${uid}
#: web_starter/views/user.py:87
msgid "email-already-used"
msgstr "Email ${email} sudah digunakan oleh ID ${uid}"
#. Default: Only a-z, 0-9, and - characters are allowed
#: web_starter/views/user.py:107
msgid "username-only-contain"
msgstr "Hanya boleh karakter a-z, 0-9, dan -"
#. Default: Only a-z or 0-9 at the start and end
#: web_starter/views/user.py:113
msgid "username-first-end-alphanumeric"
msgstr "Awal dan akhir hanya boleh a-z atau 0-9"
#. Default: Username ${username} already used by ID ${uid}
#: web_starter/views/user.py:122
msgid "username-already-used"
msgstr "Nama ${username} sudah digunakan ID ${uid}"
#: web_starter/views/user.py:141
msgid "Email"
msgstr "Email"
#: web_starter/views/user.py:143
msgid "Username"
msgstr "Nama"
#: web_starter/views/user.py:146
msgid "Group"
msgstr "Grup"
#: web_starter/views/user.py:154
msgid "Status"
msgstr "Status"
#: web_starter/views/user.py:165
msgid "Active"
msgstr "Aktif"
#: web_starter/views/user.py:166
msgid "Inactive"
msgstr "Tidak aktif"
#: web_starter/views/user.py:176
msgid "Save"
msgstr "Simpan"
#: web_starter/views/user.py:177 web_starter/views/user.py:299
msgid "Cancel"
msgstr "Batalkan"
#: web_starter/views/user.py:202
msgid "Add user"
msgstr "Tambah pengguna"
#: web_starter/views/user.py:219
msgid "user-added"
msgstr "${email} sudah ditambahkan dan email untuknya sudah dikirim"
#: web_starter/views/user.py:266
msgid "Edit user"
msgstr "Ubah pengguna"
#. Default: ${username} profile updated
#: web_starter/views/user.py:282
msgid "user-updated"
msgstr "Profil ${username} sudah diperbarui"
#: web_starter/views/user.py:298
msgid "Delete"
msgstr "Hapus"
#: web_starter/views/user.py:303
msgid "Delete user"
msgstr "Hapus pengguna"
#. Default: User ${email} ID ${uid} has been deleted
#: web_starter/views/user.py:306
msgid "user-deleted"
msgstr "${email} ID ${uid} sudah dihapus"
msgid "Registered date"
msgstr "Tanggal pendaftaran"
msgid "Last login"
msgstr "Terakhir masuk"
msgid "Edit"
msgstr "Ubah"
msgid "System"
msgstr "Sistem"
msgid "You"
msgstr "Anda"
msgid "Finance"
msgstr "Keuangan"
msgid "Warning"
msgstr "Perhatian"
msgid "warning-delete-user"
msgstr "Hapus ${email} ID ${uid} ?"
msgid "Show"
msgstr "Lihat"
msgid "user-result"
msgstr "Ada ${count} baris"
msgid "No result"
msgstr "Tidak ada hasil"
msgid "Groups"
msgstr "Kelompok"
msgid "Group name"
msgstr "Nama kelompok"
msgid "Description"
msgstr "Keterangan"
msgid "Member count"
msgstr "Jumlah anggota"
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-15 08:41+0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Enter new password for ${name}:
msgid "ask-password-1"
msgstr ""
#. Default: Retype new password for ${name}:
msgid "ask-password-2"
msgstr ""
msgid "Sorry, passwords do not match"
msgstr ""
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-13 22:28+0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Login failed
#: ./views/login.py:42
msgid "login-failed"
msgstr ""
#. Default: Login
#: ./views/login.py:58
msgid "login"
msgstr ""
msgid "Reset password"
msgstr ""
#. Default: Enter your email address and we will send you a link to reset your password.
msgid "email-reset-password"
msgstr ""
msgid "Send password reset email"
msgstr ""
msgid "reset-password-body"
msgstr ""
msgid "reset-password-link-sent"
msgstr ""
msgid "change-password-done"
msgstr ""
msgid "Save"
msgstr ""
msgid "Cancel"
msgstr ""
msgid "Invalid old password"
msgstr ""
msgid "Retype mismatch"
msgstr ""
msgid "Old password"
msgstr ""
msgid "New password"
msgstr ""
msgid "Retype new password"
msgstr ""
msgid "Change password"
msgstr ""
msgid "Invalid security code"
msgstr ""
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-28 21:10+0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#. Default: Home
#: ./views/templates/layout-menu.pt:40
msgid "Home"
msgstr ""
#. Default: Admin<dynamic element>
#: ./views/templates/layout-menu.pt:44
msgid "Admin"
msgstr ""
#. Default: Users
#: ./views/templates/layout-menu.pt:46
msgid "Users"
msgstr ""
#. Default: Add user
#: ./views/templates/layout-menu.pt:47
msgid "Add user"
msgstr ""
#. Default: My account <dynamic element>
#: ./views/templates/layout-menu.pt:54
msgid "My account"
msgstr ""
#. Default: Change password
#: ./views/templates/layout-menu.pt:56
msgid "Change password"
msgstr ""
#. Default: ${username} logout
#: ./views/templates/layout-menu.pt:57
msgid "username-logout"
msgstr ""
msgid "Users groups"
msgstr ""
msgid "Add users group"
msgstr ""
#
# SOME DESCRIPTIVE TITLE
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE 1.0\n"
"POT-Creation-Date: 2018-10-27 14:20+0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Lingua 4.13\n"
#: ./web_starter/views/user.py:43
msgid "Users"
msgstr ""
#: ./web_starter/views/user.py:78
msgid "Invalid email format"
msgstr ""
#. Default: Email ${email} already used by user ID ${uid}
#: ./web_starter/views/user.py:87
msgid "email-already-used"
msgstr ""
#. Default: Only a-z, 0-9, and - characters are allowed
#: ./web_starter/views/user.py:107
msgid "username-only-contain"
msgstr ""
#. Default: Only a-z or 0-9 at the start and end
#: ./web_starter/views/user.py:113
msgid "username-first-end-alphanumeric"
msgstr ""
#. Default: Username ${username} already used by ID ${uid}
#: ./web_starter/views/user.py:122
msgid "username-already-used"
msgstr ""
#: ./web_starter/views/user.py:141
msgid "Email"
msgstr ""
#: ./web_starter/views/user.py:143
msgid "Username"
msgstr ""
#: ./web_starter/views/user.py:146
msgid "Group"
msgstr ""
#: ./web_starter/views/user.py:154
msgid "Status"
msgstr ""
#: ./web_starter/views/user.py:165
msgid "Active"
msgstr ""
#: ./web_starter/views/user.py:166
msgid "Inactive"
msgstr ""
#: ./web_starter/views/user.py:176
msgid "Save"
msgstr ""
#: ./web_starter/views/user.py:177 ./web_starter/views/user.py:299
msgid "Cancel"
msgstr ""
#: ./web_starter/views/user.py:202
msgid "Add user"
msgstr ""
#: ./web_starter/views/user.py:219
msgid "user-added"
msgstr ""
#: ./web_starter/views/user.py:266
msgid "Edit user"
msgstr ""
#. Default: ${username} profile updated
#: ./web_starter/views/user.py:282
msgid "user-updated"
msgstr ""
#: ./web_starter/views/user.py:298
msgid "Delete"
msgstr ""
#: ./web_starter/views/user.py:303
msgid "Delete user"
msgstr ""
#. Default: User ${email} ID ${uid} has been deleted
#: ./web_starter/views/user.py:306
msgid "user-deleted"
msgstr ""
msgid "Registered date"
msgstr ""
msgid "Last login"
msgstr ""
msgid "Edit"
msgstr ""
msgid "System"
msgstr ""
msgid "You"
msgstr ""
msgid "Finance"
msgstr ""
msgid "Warning"
msgstr ""
msgid "warning-delete-user"
msgstr ""
msgid "Show"
msgstr ""
msgid "user-result"
msgstr ""
msgid "No result"
msgstr ""
msgid "Groups"
msgstr ""
msgid "Group name"
msgstr ""
msgid "Description"
msgstr ""
msgid "Member count"
msgstr ""
from sqlalchemy import (
Column,
Integer,
DateTime,
String,
Boolean,
)
from sqlalchemy.orm import (
sessionmaker,
scoped_session,
)
from sqlalchemy.ext.declarative import declarative_base
from zope.sqlalchemy import ZopeTransactionExtension
from ..tools.waktu import create_now
DBSession = scoped_session(
sessionmaker(extension=ZopeTransactionExtension()))
Base = declarative_base()
class CommonModel(object):
def to_dict(self):
values = {}
for column in self.__table__.columns:
values[column.name] = getattr(self, column.name)
return values
def to_dict_without_none(self):
values = {}
for column in self.__table__.columns:
val = getattr(self, column.name)
if val is not None:
values[column.name] = val
return values
class BaseModel(CommonModel):
id = Column(Integer, nullable=False, primary_key=True)
class LogModel(BaseModel):
created = Column(
DateTime(timezone=True), nullable=False, default=create_now)
class IsoModel(LogModel):
# Nama bank (bjb, btn) atau forwarder (mitracomm)
forwarder = Column(String(16), nullable=False)
ip = Column(String(15), nullable=False)
mti = Column(String(4), nullable=False)
is_send = Column(Boolean, nullable=False, default=True)
class HistoryModel(LogModel):
updated = Column(
DateTime(timezone=True), nullable=False, default=create_now)
def save(self):
self.updated = create_now()
if not self.id:
self.created = self.updated
from sqlalchemy import (
Column,
String,
Date,
DateTime,
BigInteger,
Integer,
Text,
Float,
Boolean,
ForeignKey,
UniqueConstraint,
)
from . import (
Base,
BaseModel,
LogModel,
IsoModel,
HistoryModel,
)
# h2h pbb / h2h bphtb / h2h padl / h2h webr
class Jenis(BaseModel, Base):
__tablename__ = 'log_jenis'
nama = Column(String(16), nullable=False, unique=True)
# INFO / ERROR / WARNING
class Kategori(BaseModel, Base):
__tablename__ = 'log_kategori'
nama = Column(String(7), nullable=False, unique=True)
class Log(LogModel, Base):
__tablename__ = 'log'
jenis_id = Column(Integer, ForeignKey(Jenis.id), nullable=False)
line = Column(Text, nullable=False)
# line_id berisi md5 dari line
line_id = Column(String(32), nullable=False)
tgl = Column(DateTime(timezone=True), nullable=False)
kategori_id = Column(
Integer, ForeignKey(Kategori.id), nullable=False)
__table_args__ = (UniqueConstraint('jenis_id', 'line_id'),)
class Iso(IsoModel, Base):
__tablename__ = 'log_iso'
id = Column(Integer, ForeignKey(Log.id), primary_key=True)
tgl = Column(DateTime(timezone=True), nullable=False)
jenis_id = Column(Integer, ForeignKey(Jenis.id), nullable=False)
kategori_id = Column(
Integer, ForeignKey(Kategori.id), nullable=False)
bit_002 = Column(Text)
bit_003 = Column(Text)
bit_004 = Column(Text)
bit_005 = Column(Text)
bit_006 = Column(Text)
bit_007 = Column(Text)
bit_008 = Column(Text)
bit_009 = Column(Text)
bit_010 = Column(Text)
bit_011 = Column(Text)
bit_012 = Column(Text)
bit_013 = Column(Text)
bit_014 = Column(Text)
bit_015 = Column(Text)
bit_016 = Column(Text)
bit_017 = Column(Text)
bit_018 = Column(Text)
bit_019 = Column(Text)
bit_020 = Column(Text)
bit_021 = Column(Text)
bit_022 = Column(Text)
bit_023 = Column(Text)
bit_024 = Column(Text)
bit_025 = Column(Text)
bit_026 = Column(Text)
bit_027 = Column(Text)
bit_028 = Column(Text)
bit_029 = Column(Text)
bit_030 = Column(Text)
bit_031 = Column(Text)
bit_032 = Column(Text)
bit_033 = Column(Text)
bit_034 = Column(Text)
bit_035 = Column(Text)
bit_036 = Column(Text)
bit_037 = Column(Text)
bit_038 = Column(Text)
bit_039 = Column(Text)
bit_040 = Column(Text)
bit_041 = Column(Text)
bit_042 = Column(Text)
bit_043 = Column(Text)
bit_044 = Column(Text)
bit_045 = Column(Text)
bit_046 = Column(Text)
bit_047 = Column(Text)
bit_048 = Column(Text)
bit_049 = Column(Text)
bit_050 = Column(Text)
bit_051 = Column(Text)
bit_052 = Column(Text)
bit_053 = Column(Text)
bit_054 = Column(Text)
bit_055 = Column(Text)
bit_056 = Column(Text)
bit_057 = Column(Text)
bit_058 = Column(Text)
bit_059 = Column(Text)
bit_060 = Column(Text)
bit_061 = Column(Text)
bit_062 = Column(Text)
bit_063 = Column(Text)
bit_064 = Column(Text)
bit_065 = Column(Text)
bit_066 = Column(Text)
bit_067 = Column(Text)
bit_068 = Column(Text)
bit_069 = Column(Text)
bit_070 = Column(Text)
bit_071 = Column(Text)
bit_072 = Column(Text)
bit_073 = Column(Text)
bit_074 = Column(Text)
bit_075 = Column(Text)
bit_076 = Column(Text)
bit_077 = Column(Text)
bit_078 = Column(Text)
bit_079 = Column(Text)
bit_080 = Column(Text)
bit_081 = Column(Text)
bit_082 = Column(Text)
bit_083 = Column(Text)
bit_084 = Column(Text)
bit_085 = Column(Text)
bit_086 = Column(Text)
bit_087 = Column(Text)
bit_088 = Column(Text)
bit_089 = Column(Text)
bit_090 = Column(Text)
bit_091 = Column(Text)
bit_092 = Column(Text)
bit_093 = Column(Text)
bit_094 = Column(Text)
bit_095 = Column(Text)
bit_096 = Column(Text)
bit_097 = Column(Text)
bit_098 = Column(Text)
bit_099 = Column(Text)
bit_100 = Column(Text)
bit_101 = Column(Text)
bit_102 = Column(Text)
bit_103 = Column(Text)
bit_104 = Column(Text)
bit_105 = Column(Text)
bit_106 = Column(Text)
bit_107 = Column(Text)
bit_108 = Column(Text)
bit_109 = Column(Text)
bit_110 = Column(Text)
bit_111 = Column(Text)
bit_112 = Column(Text)
bit_113 = Column(Text)
bit_114 = Column(Text)
bit_115 = Column(Text)
bit_116 = Column(Text)
bit_117 = Column(Text)
bit_118 = Column(Text)
bit_119 = Column(Text)
bit_120 = Column(Text)
bit_121 = Column(Text)
bit_122 = Column(Text)
bit_123 = Column(Text)
bit_124 = Column(Text)
bit_125 = Column(Text)
bit_126 = Column(Text)
bit_127 = Column(Text)
bit_128 = Column(Text)
class Conf(HistoryModel, Base):
__tablename__ = 'log_conf'
nama = Column(String(64), nullable=False, unique=True)
nilai = Column(Text)
nilai_int = Column(Integer)
nilai_float = Column(Float)
nilai_bool = Column(Boolean)
class Method(BaseModel, Base):
__tablename__ = 'iso_method'
nama = Column(String(16), nullable=False, unique=True)
class Bank(BaseModel, Base):
__tablename__ = 'bank'
nama = Column(String(32), nullable=False, unique=True)
class Summary(BaseModel, Base):
__tablename__ = 'iso_summary'
__table_args__ = (
UniqueConstraint('jenis_id', 'tgl', 'method_id', 'bank_id'),
)
jenis_id = Column(Integer, ForeignKey(Jenis.id), nullable=False)
tgl = Column(Date, nullable=False)
method_id = Column(Integer, ForeignKey(Method.id), nullable=False)
bank_id = Column(Integer, ForeignKey(Bank.id), nullable=False)
trx_count = Column(Integer, nullable=False, default=0)
trx_amount = Column(Float, nullable=False, default=0)
from pyramid.security import (
Allow,
Authenticated,
ALL_PERMISSIONS,
)
from sqlalchemy import PrimaryKeyConstraint
import ziggurat_foundations.models
from ziggurat_foundations.models.base import BaseModel
from ziggurat_foundations.models.external_identity import ExternalIdentityMixin
from ziggurat_foundations.models.group import GroupMixin
from ziggurat_foundations.models.group_permission import GroupPermissionMixin
from ziggurat_foundations.models.group_resource_permission import GroupResourcePermissionMixin
from ziggurat_foundations.models.resource import ResourceMixin
from ziggurat_foundations.models.user import UserMixin
from ziggurat_foundations.models.user_group import UserGroupMixin
from ziggurat_foundations.models.user_permission import UserPermissionMixin
from ziggurat_foundations.models.user_resource_permission import UserResourcePermissionMixin
from ziggurat_foundations import ziggurat_model_init
from . import (
Base,
DBSession,
CommonModel,
)
# this is needed for scoped session approach like in pylons 1.0
ziggurat_foundations.models.DBSession = DBSession
# optional for folks who pass request.db to model methods
# Base is sqlalchemy's Base = declarative_base() from your project
class Group(GroupMixin, Base, CommonModel):
pass
class GroupPermission(GroupPermissionMixin, Base):
pass
class UserGroup(UserGroupMixin, Base):
pass
class GroupResourcePermission(GroupResourcePermissionMixin, Base):
__table_args__ = (
PrimaryKeyConstraint(
"group_id",
"resource_id",
"perm_name"),)
class Resource(ResourceMixin, Base):
# ... your own properties....
# example implementation of ACLS for pyramid application
@property
def __acl__(self):
acls = []
if self.owner_user_id:
acls.extend([(Allow, self.owner_user_id, ALL_PERMISSIONS,), ])
if self.owner_group_id:
acls.extend([(Allow, "group:%s" % self.owner_group_id,
ALL_PERMISSIONS,), ])
return acls
class UserPermission(UserPermissionMixin, Base):
pass
class UserResourcePermission(UserResourcePermissionMixin, Base):
pass
class User(UserMixin, Base, CommonModel):
# ... your own properties....
pass
class ExternalIdentity(ExternalIdentityMixin, Base):
pass
# you can define multiple resource derived models to build a complex
# application like CMS, forum or other permission based solution
#class Entry(Resource):
# """
# Resource of `entry` type
# """
# __tablename__ = 'entries'
# __mapper_args__ = {'polymorphic_identity': 'entry'}
# resource_id = sa.Column(sa.Integer(),
# sa.ForeignKey('resources.resource_id',
# onupdate='CASCADE',
# ondelete='CASCADE', ),
# primary_key=True, )
# ... your own properties....
# some_property = sa.Column(sa.UnicodeText())
class RootFactory:
def __init__(self, request):
self.__acl__ = [
(Allow, Authenticated, 'view'),
(Allow, 'group:1', ALL_PERMISSIONS),
]
for gp in DBSession.query(GroupPermission):
acl_name = 'group:{}'.format(gp.group_id)
self.__acl__.append((Allow, acl_name, gp.perm_name))
#ziggurat_model_init(User, Group, UserGroup, GroupPermission, passwordmanager=None)
ziggurat_model_init(User, Group, UserGroup, GroupPermission, UserPermission,
UserResourcePermission, GroupResourcePermission, Resource,
ExternalIdentity, passwordmanager=None)
from opensipkd.tcp.connection import join_ip_port
try:
from configparser import (
ConfigParser,
NoOptionError,
)
except ImportError:
from ConfigParser import (
ConfigParser,
NoOptionError,
)
listen_ports = []
ip_conf = {}
allowed_ips = []
web = {}
def get_conf(ip, port):
key = join_ip_port(ip, port)
return ip_conf[key]
def get_web_port():
return web['port']
def get_str(conf, section, option, default):
try:
return conf.get(section, option)
except NoOptionError:
return default
def get_boolean(conf, section, option, default):
try:
return conf.getboolean(section, option)
except NoOptionError:
return default
def get_int(conf, section, option, default):
try:
return conf.getint(section, option)
except NoOptionError:
return default
MSG_DUPLICATE = 'IP {ip} port {port} ganda. Perbaiki konfigurasi.'
def validate_ip_port(ip_port):
if ip_port in ip_conf:
cfg = ip_conf[ip_port]
msg = MSG_DUPLICATE.format(ip=cfg['ip'], port=cfg['port'])
raise Exception(msg)
def get_module_object(name):
module_obj = __import__(name)
sub_obj = None
for sub in name.split('.')[1:]:
if sub_obj:
sub_obj = getattr(sub_obj, sub)
else:
sub_obj = getattr(module_obj, sub)
return sub_obj
def get_streamer_class(name):
obj = get_module_object('opensipkd.streamer.' + name)
return obj.Streamer
def read_conf(conf_file):
conf = ConfigParser()
conf.read(conf_file)
for section in conf.sections():
if section == 'web':
web['port'] = conf.getint(section, 'port')
continue
if section.find('host_') < 0:
continue
try:
active = conf.getboolean(section, 'active')
except NoOptionError:
active = True
if not active:
continue
ip = conf.get(section, 'ip')
port = conf.getint(section, 'port')
ip_port = join_ip_port(ip, port)
validate_ip_port(ip_port)
cfg = dict()
cfg['ip'] = ip
cfg['port'] = port
cfg['name'] = section.split('_')[1]
cfg['streamer'] = get_str(conf, section, 'streamer', 'none')
cfg['streamer_cls'] = get_streamer_class(cfg['streamer'])
cfg['module'] = get_str(conf, section, 'module', 'opensipkd.iso8583.network')
cfg['module_obj'] = get_module_object(cfg['module'])
cfg['ip'] = conf.get(section, 'ip')
cfg['port'] = conf.getint(section, 'port')
cfg['listen'] = get_boolean(conf, section, 'listen', True)
cfg['echo'] = get_boolean(conf, section, 'echo', not cfg['listen'])
cfg['timeout'] = get_int(conf, section, 'timeout', 60)
if cfg['listen']:
if port not in listen_ports:
listen_ports.append(port)
if ip not in allowed_ips:
allowed_ips.append(ip)
ip_conf[ip_port] = dict(cfg)
name,path
home,/
login
logout
change-password
change-password-done
reset-password
reset-password-sent
login-by-code-failed
user
user-add,/user/add
user-edit,/user/{id}
user-delete,/user/{id}/delete
group
group-add,/group/add
group-edit,/group/{id}
group-delete,/group/{id}/delete
log
id,nama
2,BRI
8,Mandiri
9,BNI
13,Permata
14,BCA
22,Niaga
110,BJB
group_name
Superuser
Finance
id,nama
1,Inquiry
2,Payment
3,Reversal
id,nama
1,H2H PBB
2,H2H BPHTB
3,H2H PADL
4,H2H Web Register
5,H2H Perijinan
7,H2H T-SAMSAT
id,nama
1,Info
2,Error
3,Warning
email,status,user_name
admin@local,1,admin
import os
import sys
import csv
import subprocess
import transaction
from getpass import getpass
from sqlalchemy import engine_from_config
from ziggurat_foundations.models.services.user import UserService
from pyramid.paster import (
get_appsettings,
setup_logging,
)
from pyramid.i18n import (
Localizer,
TranslationStringFactory,
Translations,
)
from ..models import (
DBSession,
Base,
)
from ..models.ziggurat import (
Group,
GroupPermission,
UserGroup,
User,
)
from ..models.log import (
Bank,
Method,
Jenis,
Kategori,
)
domain = 'initialize_db'
_ = TranslationStringFactory(domain)
my_registry = dict()
class MyLocalizer:
def __init__(self):
settings = my_registry['settings']
locale_name = settings['pyramid.default_locale_name']
here = os.path.abspath(os.path.dirname(__file__))
locale_dir = os.path.join(here, '..', 'locale')
translations = Translations.load(locale_dir, [locale_name], domain)
self.localizer = Localizer(locale_name, translations)
def translate(self, ts):
return self.localizer.translate(ts)
def usage(argv):
cmd = os.path.basename(argv[0])
print('usage: %s <config_uri>\n'
'(example: "%s development.ini")' % (cmd, cmd))
sys.exit(1)
def read_file(filename):
f = open(filename)
s = f.read()
f.close()
return s
def alembic_run(ini_file, url):
bin_path = os.path.split(sys.executable)[0]
alembic_bin = os.path.join(bin_path, 'alembic')
command = (alembic_bin, 'upgrade', 'head')
s = read_file(ini_file)
s = s.replace('{db_url}', url)
f = open('alembic.ini', 'w')
f.write(s)
f.close()
subprocess.call(command)
os.remove('alembic.ini')
def get_file(filename):
base_dir = os.path.split(__file__)[0]
fullpath = os.path.join(base_dir, 'data', filename)
return open(fullpath)
def ask_password(name):
localizer = MyLocalizer()
data = dict(name=name)
t_msg1 = _(
'ask-password-1', default='Enter new password for ${name}: ',
mapping=data)
t_msg2 = _(
'ask-password-2', default='Retype new password for ${name}: ',
mapping=data)
msg1 = localizer.translate(t_msg1)
msg2 = localizer.translate(t_msg2)
while True:
pass1 = getpass(msg1)
if not pass1:
continue
pass2 = getpass(msg2)
if pass1 == pass2:
return pass1
ts = _('Sorry, passwords do not match')
print(localizer.translate(ts))
def restore_csv(table, filename):
q = DBSession.query(table)
if q.first():
return
with get_file(filename) as f:
reader = csv.DictReader(f)
for cf in reader:
row = table()
for fieldname in cf:
val = cf[fieldname]
if not val:
continue
setattr(row, fieldname, val)
DBSession.add(row)
return True
def append_csv(table, filename, keys):
with get_file(filename) as f:
reader = csv.DictReader(f)
filter_ = dict()
for cf in reader:
for key in keys:
filter_[key] = cf[key]
q = DBSession.query(table).filter_by(**filter_)
found = q.first()
if found:
continue
row = table()
for fieldname in cf:
val = cf[fieldname]
if not val:
continue
setattr(row, fieldname, val)
DBSession.add(row)
def main(argv=sys.argv):
if len(argv) != 2:
usage(argv)
config_uri = argv[1]
setup_logging(config_uri)
settings = get_appsettings(config_uri)
my_registry['settings'] = settings
engine = engine_from_config(settings, 'sqlalchemy.')
Base.metadata.bind = engine
Base.metadata.create_all()
alembic_run('alembic.ini.tpl', settings['sqlalchemy.url'])
with transaction.manager:
if restore_csv(User, 'users.csv'):
DBSession.flush()
q = DBSession.query(User).filter_by(id=1)
user = q.first()
password = ask_password(user.user_name)
UserService.set_password(user, password)
append_csv(Group, 'groups.csv', ['group_name'])
restore_csv(UserGroup, 'users_groups.csv')
restore_csv(Bank, 'bank.csv')
restore_csv(Method, 'iso_method.csv')
restore_csv(Jenis, 'jenis.csv')
restore_csv(Kategori, 'kategori.csv')
from .models import DBSession
from .models.ziggurat import (
User,
UserGroup,
)
def group_finder(login, request):
q = DBSession.query(User).filter_by(id=login)
u = q.first()
if not u or not u.status:
return # None means logout
r = []
q = DBSession.query(UserGroup).filter_by(user_id=u.id)
for ug in q:
acl_name = 'group:{gid}'.format(gid=ug.group_id)
r.append(acl_name)
return r
def get_user(request):
uid = request.authenticated_userid
if uid:
q = DBSession.query(User).filter_by(id=uid)
return q.first()
.grid-container {
display: grid;
grid-template-columns: auto auto auto;
}
body {
padding-top: 40px;
padding-bottom: 40px;
background-color: #eee;
}
.form-signin {
max-width: 330px;
padding: 15px;
margin: 0 auto;
}
.form-signin .form-signin-heading,
.form-signin .checkbox {
margin-bottom: 10px;
}
.form-signin .checkbox {
font-weight: normal;
}
.form-signin .form-control {
position: relative;
font-size: 16px;
height: auto;
padding: 10px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.form-signin .form-control:focus {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.panel {
margin-top: 20px;
}
.alert {
margin-top: 20px;
}
@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700);
body {
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 300;
color: #ffffff;
background: #bc2131;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 300;
}
p {
font-weight: 300;
}
.font-normal {
font-weight: 400;
}
.font-semi-bold {
font-weight: 600;
}
.font-bold {
font-weight: 700;
}
.starter-template {
margin-top: 250px;
}
.starter-template .content {
margin-left: 10px;
}
.starter-template .content h1 {
margin-top: 10px;
font-size: 60px;
}
.starter-template .content h1 .smaller {
font-size: 40px;
color: #f2b7bd;
}
.starter-template .content .lead {
font-size: 25px;
color: #f2b7bd;
}
.starter-template .content .lead .font-normal {
color: #ffffff;
}
.starter-template .links {
float: right;
right: 0;
margin-top: 125px;
}
.starter-template .links ul {
display: block;
padding: 0;
margin: 0;
}
.starter-template .links ul li {
list-style: none;
display: inline;
margin: 0 10px;
}
.starter-template .links ul li:first-child {
margin-left: 0;
}
.starter-template .links ul li:last-child {
margin-right: 0;
}
.starter-template .links ul li.current-version {
color: #f2b7bd;
font-weight: 400;
}
.starter-template .links ul li a, a {
color: #f2b7bd;
text-decoration: underline;
}
.starter-template .links ul li a:hover, a:hover {
color: #ffffff;
text-decoration: underline;
}
.starter-template .links ul li .icon-muted {
color: #eb8b95;
margin-right: 5px;
}
.starter-template .links ul li:hover .icon-muted {
color: #ffffff;
}
.starter-template .copyright {
margin-top: 10px;
font-size: 0.9em;
color: #f2b7bd;
text-transform: lowercase;
float: right;
right: 0;
}
@media (max-width: 1199px) {
.starter-template .content h1 {
font-size: 45px;
}
.starter-template .content h1 .smaller {
font-size: 30px;
}
.starter-template .content .lead {
font-size: 20px;
}
}
@media (max-width: 991px) {
.starter-template {
margin-top: 0;
}
.starter-template .logo {
margin: 40px auto;
}
.starter-template .content {
margin-left: 0;
text-align: center;
}
.starter-template .content h1 {
margin-bottom: 20px;
}
.starter-template .links {
float: none;
text-align: center;
margin-top: 60px;
}
.starter-template .copyright {
float: none;
text-align: center;
}
}
@media (max-width: 767px) {
.starter-template .content h1 .smaller {
font-size: 25px;
display: block;
}
.starter-template .content .lead {
font-size: 16px;
}
.starter-template .links {
margin-top: 40px;
}
.starter-template .links ul li {
display: block;
margin: 0;
}
.starter-template .links ul li .icon-muted {
display: none;
}
.starter-template .copyright {
margin-top: 20px;
}
}
import unittest
from pyramid import testing
class ViewTests(unittest.TestCase):
def setUp(self):
self.config = testing.setUp()
def tearDown(self):
testing.tearDown()
def test_my_view(self):
from .views import my_view
request = testing.DummyRequest()
info = my_view(request)
self.assertEqual(info['project'], 'ISO8583 Web')
class FunctionalTests(unittest.TestCase):
def setUp(self):
from iso8583_web import main
app = main({})
from webtest import TestApp
self.testapp = TestApp(app)
def test_root(self):
res = self.testapp.get('/', status=200)
self.assertTrue(b'Pyramid' in res.body)
# items = request.POST.items()
def to_dict(items):
d = dict()
values = None
for item in items:
print(item)
key, value = item
if key == '__start__':
fieldname = value
values = []
elif key == '__end__':
d[fieldname] = values
values = None
elif isinstance(values, list):
values.append(value)
else:
d[key] = value
return d
import sys
from datetime import (
date,
datetime,
)
from .waktu import (
dmy,
dmyhms,
)
def to_str(v):
if isinstance(v, date):
if isinstance(v, datetime):
return dmyhms(v)
return dmy(v)
if v == 0:
return '0'
if isinstance(v, str) or \
(sys.version_info.major == 2 and isinstance(v, unicode)):
return v.strip()
elif isinstance(v, bool):
return v and '1' or '0'
return v and str(v) or ''
def dict_to_str(d):
r = {}
for key in d:
val = d[key]
r[key] = to_str(val)
return r
from pyramid.threadlocal import get_current_registry
from pyramid.events import (
subscriber,
BeforeRender,
)
from pyramid.i18n import default_locale_negotiator
LOCALE_NAMES = {
'en': 'English',
'id': 'Indonesia',
}
LOCALE_IDS = list(LOCALE_NAMES.keys())
def get_settings():
return get_current_registry().settings
def get_locale_should_be(request):
if 'HTTP_ACCEPT_LANGUAGE' not in request.environ:
return 'id'
# id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7
vals = request.environ['HTTP_ACCEPT_LANGUAGE'].split(',')
for val in vals:
for locale_id in LOCALE_IDS:
if val.find(locale_id) == 0:
return locale_id
def get_locale_name(request):
return default_locale_negotiator(request) or \
get_locale_should_be(request)
def get_locale_title(name):
if name in LOCALE_NAMES:
return LOCALE_NAMES[name]
return name
def get_locale_title_(request):
name = get_locale_name(request)
return get_locale_title(name)
@subscriber(BeforeRender)
def add_global(event):
event['locale_name'] = get_locale_title_
event['locale_should_be'] = get_locale_should_be
import calendar
from datetime import (
date,
datetime,
)
import pytz
from .this_framework import get_settings
def get_timezone():
settings = get_settings()
return pytz.timezone(settings['timezone'])
def create_datetime(year, month, day, hour=0, minute=7, second=0,
microsecond=0):
tz = get_timezone()
return datetime(year, month, day, hour, minute, second,
microsecond, tzinfo=tz)
def create_date(year, month, day):
return create_datetime(year, month, day)
def as_timezone(tz_date):
localtz = get_timezone()
if not tz_date.tzinfo:
tz_date = create_datetime(tz_date.year, tz_date.month, tz_date.day,
tz_date.hour, tz_date.minute, tz_date.second,
tz_date.microsecond)
return tz_date.astimezone(localtz)
def create_now():
tz = get_timezone()
return datetime.now(tz)
def date_from_str(value):
separator = None
value = value.split()[0] # dd-mm-yyyy HH:MM:SS
for s in ['-', '/']:
if value.find(s) > -1:
separator = s
break
if separator:
t = map(lambda x: int(x), value.split(separator))
y, m, d = t[2], t[1], t[0]
if d > 999: # yyyy-mm-dd
y, d = d, y
else:
y, m, d = int(value[:4]), int(value[4:6]), int(value[6:])
return date(y, m, d)
def split_time(s):
t = s.split(':') # HH:MM:SS
hour = int(t[0])
minutes = seconds = 0
if t[1:]:
minutes = int(t[1])
if t[2:]:
seconds = int(t[2])
return hour, minutes, seconds
# dd-mm-yyyy
# yyyy-mm-dd
# dd/mm/yyyy
# yyyy/mm/dd
# yyyymmdd
def split_date(s):
separator = None
for sep in ['-', '/']:
if s.find(sep) > -1:
separator = sep
break
if separator:
d, m, y = [int(x) for x in s.split(separator)]
if d > 999: # yyyy-mm-dd
y, d = d, y
else:
y, m, d = int(value[:4]), int(value[4:6]), int(value[6:])
return y, m, d
def split_datetime(s):
t = s.split() # dd-mm-yyyy HH:MM:SS
year, month, day = split_date(t[0])
if t[1:]:
hours, minutes, seconds = split_time(t[1])
else:
hours = minutes = seconds = 0
return year, month, day, hours, minutes, seconds
def date_from_str(s):
y, m, d, hh, mm, ss = split_datetime(s)
return date(y, m, d)
def datetime_from_str(s):
y, m, d, hh, mm, ss = split_datetime(s)
return create_datetime(y, m, d, hh, mm, ss)
def dmy(tgl):
return tgl.strftime('%d-%m-%Y')
def dmyhms(t):
return t.strftime('%d-%m-%Y %H:%M:%S')
def next_month(year, month):
if month == 12:
month = 1
year += 1
else:
month += 1
return year, month
def best_date(year, month, day):
try:
return date(year, month, day)
except ValueError:
last_day = calendar.monthrange(year, month)[1]
return date(year, month, last_day)
def next_month_day(year, month, day):
year, month = next_month(year, month)
return best_date(year, month, day)
from pyramid.view import view_config
from pyramid.httpexceptions import (
default_exceptionresponse_view,
HTTPFound,
)
from pyramid.interfaces import IRoutesMapper
from pyramid.response import Response
from pyramid.i18n import TranslationStringFactory
_ = TranslationStringFactory('home')
# http://stackoverflow.com/questions/9845669/pyramid-inverse-to-add-notfound-viewappend-slash-true
class RemoveSlashNotFoundViewFactory:
def __init__(self, notfound_view=None):
if notfound_view is None:
notfound_view = default_exceptionresponse_view
self.notfound_view = notfound_view
def __call__(self, context, request):
if not isinstance(context, Exception):
# backwards compat for an append_notslash_view registered via
# config.set_notfound_view instead of as a proper exception view
context = getattr(request, 'exception', None) or context
path = request.path
registry = request.registry
mapper = registry.queryUtility(IRoutesMapper)
if mapper is not None and path.endswith('/'):
noslash_path = path.rstrip('/')
for route in mapper.get_routes():
if route.match(noslash_path) is not None:
qs = request.query_string
if qs:
noslash_path += '?' + qs
return HTTPFound(location=noslash_path)
return self.notfound_view(context, request)
@view_config(route_name='home', renderer='templates/home.pt')
def my_view(request):
if '_LOCALE_' in request.GET:
resp = Response()
resp.set_cookie('_LOCALE_', request.GET['_LOCALE_'], 31536000)
return HTTPFound(
location=request.route_url('home'), headers=resp.headers)
return dict(project='ISO8583 Web', title=_('Home'))
You have registered on our site. Please click the link below to change the
password.
${url}
This link will expire in ${minutes} minutes.
If you did not request this, please ignore it.
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from pyramid.i18n import TranslationStringFactory
import colander
from deform import (
Form,
Button,
ValidationFailure,
)
from deform.widget import (
TextAreaWidget,
HiddenWidget,
CheckboxChoiceWidget,
)
from ..models import DBSession
from ..models.ziggurat import (
Group,
GroupPermission,
)
_ = TranslationStringFactory('user')
########
# List #
########
@view_config(
route_name='group', renderer='templates/group/list.pt',
permission='user-edit')
def view_list(request):
q = DBSession.query(Group).order_by(Group.group_name)
return dict(groups=q, title=_('Groups'))
#######
# Add #
#######
def clean_name(s):
s = s.strip()
while s.find(' ') > -1:
s = s.replace(' ', ' ')
return s
class GroupNameValidator:
def __init__(self, group):
self.group = group
def __call__(self, node, value):
group_name = clean_name(value)
if self.group and self.group.group_name.lower() == group_name.lower():
return
q = DBSession.query(Group).\
filter(Group.group_name.ilike(group_name))
found = q.first()
if not found:
return
data = dict(group_name=group_name, gid=found.id)
ts = _(
'group-name-already-used',
default='Group name ${group_name} already used by ID ${gid}',
mapping=data)
raise colander.Invalid(node, ts)
@colander.deferred
def deferred_group_name_validator(node, kw):
return GroupNameValidator(kw['group'])
@colander.deferred
def deferred_permissions(node, kw):
values = kw.get('permission_list', [])
return CheckboxChoiceWidget(values=values)
class AddSchema(colander.Schema):
group_name = colander.SchemaNode(
colander.String(), title=_('Group name'),
validator=deferred_group_name_validator)
description = colander.SchemaNode(
colander.String(),
missing=colander.drop,
widget=TextAreaWidget(rows=5),
title=_('Description'))
permissions = colander.SchemaNode(
colander.Set(), widget=deferred_permissions,
title=_('Permissions'))
class EditSchema(AddSchema):
id = colander.SchemaNode(
colander.String(), widget=HiddenWidget(readonly=True))
PERMISSIONS = [
('edit-user', _('User management'))
]
def get_form(request, class_form, group=None):
schema = class_form()
schema = schema.bind(permission_list=PERMISSIONS, group=group)
btn_save = Button('save', _('Save'))
btn_cancel = Button('cancel', _('Cance'))
buttons = (btn_save, btn_cancel)
return Form(schema, buttons=buttons)
def insert(values):
group = Group()
group.group_name = clean_name(values['group_name'])
group.description = values['description']
DBSession.add(group)
DBSession.flush()
for perm_name in values['permissions']:
gp = GroupPermission()
gp.group_id = group.id
gp.perm_name = perm_name
DBSession.add(gp)
return group
@view_config(
route_name='group-add', renderer='templates/group/add.pt',
permission='user-edit')
def view_add(request):
form = get_form(request, AddSchema)
resp = dict(title=_('Add goup'))
if not request.POST:
resp['form'] = form.render()
return resp
if 'save' not in request.POST:
return HTTPFound(location=request.route_url('group'))
items = request.POST.items()
try:
c = form.validate(items)
except ValidationFailure:
resp['form'] = form.render()
return resp
group = insert(dict(c.items()))
data = dict(group_name=group.group_name)
ts = _(
'group-added',
default='${group_name} group has been added.',
mapping=data)
request.session.flash(ts)
return HTTPFound(location=request.route_url('group'))
########
# Edit #
########
def group_permission_set(group):
q = DBSession.query(GroupPermission).filter_by(group_id=group.id)
r = []
for gp in q:
r.append(gp.perm_name)
return set(r)
def update(group, values):
group.group_name = clean_name(values['group_name'])
group.description = values['description']
DBSession.add(group)
existing = group_permission_set(group)
unused = existing - values['permissions']
if unused:
q = DBSession.query(GroupPermission).filter_by(group_id=group.id).\
filter(GroupPermission.perm_name.in_(unused))
q.delete(synchronize_session=False)
new = values['permissions'] - existing
for perm_name in new:
gp = GroupPermission()
gp.group_id = group.id
gp.perm_name = perm_name
DBSession.add(gp)
@view_config(
route_name='group-edit', renderer='templates/group/edit.pt',
permission='user-edit')
def view_edit(request):
q = DBSession.query(Group).filter_by(id=request.matchdict['id'])
group = q.first()
if not group:
return HTTPNotFound()
form = get_form(request, EditSchema, group)
resp = dict(title=_('Edit group'))
if not request.POST:
d = group.to_dict_without_none()
d['permissions'] = group_permission_set(group)
resp['form'] = form.render(appstruct=d)
return resp
if 'save' not in request.POST:
return HTTPFound(location=request.route_url('group'))
resp['form'] = form.render()
items = request.POST.items()
try:
c = form.validate(items)
except ValidationFailure:
resp['form'] = form.render()
return resp
update(group, dict(c.items()))
data = dict(group_name=group.group_name)
ts = _('group-updated', default='${group_name} group profile updated', mapping=data)
request.session.flash(ts)
return HTTPFound(location=request.route_url('group'))
##########
# Delete #
##########
@view_config(
route_name='group-delete', renderer='templates/group/delete.pt',
permission='user-edit')
def view_delete(request):
q = DBSession.query(Group).filter_by(id=request.matchdict['id'])
group = q.first()
if not group:
return HTTPNotFound()
if not request.POST:
btn_delete = Button('delete', _('Delete'))
btn_cancel = Button('cancel', _('Cancel'))
buttons = (btn_delete, btn_cancel)
form = Form(colander.Schema(), buttons=buttons)
return dict(title=_('Delete group'), form=form.render(), group=group)
if 'delete' not in request.POST:
return HTTPFound(location=request.route_url('group'))
data = dict(group_name=group.group_name)
ts = _(
'group-deleted',
default='{group_name} group has been deleted.',
mapping=data)
q.delete()
request.session.flash(ts)
return HTTPFound(location=request.route_url('group'))
import os
from datetime import timedelta
from pkg_resources import resource_filename
from sqlalchemy import func
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from paginate_sqlalchemy import SqlalchemyOrmPage
import colander
from deform import (
Form,
widget,
Button,
ZPTRendererFactory,
ValidationFailure,
)
from ..models import DBSession
from ..models.log import (
Log,
Jenis,
)
from ..tools.waktu import (
datetime_from_str,
create_now,
)
from ..tools.string import dict_to_str
LIMIT = 100
def get_page(request):
page = request.GET.get('page')
return page and int(page) or 1
def get_jenis_list():
q = DBSession.query(Jenis).order_by(Jenis.nama)
values = []
for row in q:
row = (str(row.id), row.nama)
values.append(row)
return values
@colander.deferred
def jenis_widget(node, kw):
return widget.SelectWidget(values=kw['jenis_list'])
class FilterSchema(colander.Schema):
awal = colander.SchemaNode(colander.String())
akhir = colander.SchemaNode(colander.String(), missing=colander.drop)
jenis = colander.SchemaNode(
colander.String(), missing=colander.drop, widget=jenis_widget)
keterangan = colander.SchemaNode(colander.String(), missing=colander.drop)
deform_templates = resource_filename('deform', 'templates')
here = os.path.abspath(os.path.dirname(__file__))
my_templates = os.path.join(here, 'templates', 'log')
search_path = [my_templates, deform_templates]
my_renderer = ZPTRendererFactory(search_path)
def get_form():
schema = FilterSchema()
schema = schema.bind(jenis_list=get_jenis_list())
btn_lihat = Button('lihat', 'Lihat')
btn_terbaru = Button('terbaru', 'Terbaru')
buttons = (btn_lihat, btn_terbaru)
return Form(schema, buttons=buttons, renderer=my_renderer)
@view_config(
route_name='log', renderer='templates/log/list.pt', permission='view')
def view_list(request):
def url_maker(page):
d['page'] = page
d['lihat'] = 1
return request.route_url('log', _query=d)
form = get_form()
resp = dict(title='Log file')
if request.POST:
items = request.POST.items()
try:
c = form.validate(items)
except ValidationFailure as e:
resp['form'] = e.render()
return resp
p = dict(c.items())
if 'terbaru' in request.POST:
p['awal'] = default_awal(p['jenis'])
p['lihat'] = 1
return route_list(request, p)
if 'awal' not in request.GET:
p = default_filter()
return route_list(request, p)
p = get_filter(request)
d = dict_to_str(p)
resp['form'] = form.render(appstruct=d)
if 'lihat' not in request.GET:
return resp
q = get_query(p)
resp['count'] = count = q.count()
q = q.order_by(Log.tgl)
page = get_page(request)
resp['rows'] = SqlalchemyOrmPage(
q, page=page, items_per_page=10, item_count=count,
url_maker=url_maker)
return resp
def default_filter():
jenis = get_last_jenis_trx()
awal = default_awal(jenis)
p = dict(jenis=jenis, awal=awal)
return p
def default_awal(jenis):
q = DBSession.query(Log).filter_by(jenis_id=jenis)
jml = q.count()
if jml >= LIMIT:
offset = jml - LIMIT
else:
offset = 0
q = DBSession.query(Log.tgl).filter_by(jenis_id=jenis).\
order_by(Log.tgl).offset(offset).limit(1)
row = q.first()
if row:
return row.tgl
return create_now()
def get_filter(request):
p = dict(jenis=int(request.params.get('jenis')))
p['awal'] = datetime_from_str(request.params['awal'])
akhir = request.params.get('akhir')
if akhir:
p['akhir'] = datetime_from_str(akhir)
keterangan = request.params.get('keterangan')
if keterangan:
p['keterangan'] = keterangan
return p
def get_query(p):
q = DBSession.query(Log).filter_by(jenis_id=p['jenis']).\
filter(Log.tgl >= p['awal'])
if 'akhir' in p:
q = q.filter(Log.tgl <= p['akhir'])
if 'keterangan' in p:
q = q.filter(Log.line.ilike('%'+p['keterangan']+'%'))
return q
def get_last_jenis_trx():
q = DBSession.query(Log).order_by(Log.tgl.desc())
r = q.first()
if r:
return r.jenis_id
q = DBSession.query(Jenis).order_by(Jenis.id)
r = q.first()
return r.id
def route_list(request, p=dict()):
q = dict_to_str(p)
return HTTPFound(location=request.route_url('log', _query=q))
import os
from datetime import (
datetime,
timedelta,
)
from urllib.parse import urlparse
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.httpexceptions import (
HTTPFound,
HTTPForbidden,
)
from pyramid.security import (
remember,
forget,
authenticated_userid,
)
from pyramid.i18n import TranslationStringFactory
import colander
from deform import (
Form,
ValidationFailure,
widget,
Button,
)
from ziggurat_foundations.models.services.user import UserService
from pyramid_mailer.message import Message
from ..tools.waktu import create_now
from ..tools.this_framework import get_settings
from ..models import DBSession
from ..models.ziggurat import User
_ = TranslationStringFactory('login')
class Login(colander.Schema):
username = colander.SchemaNode(colander.String(), title=_('Username'))
password = colander.SchemaNode(
colander.String(), widget=widget.PasswordWidget(),
title=_('Password'),
description=_('Forgot password'))
# http://deformdemo.repoze.org/interfield/
def login_validator(form, value):
user = form.user
if not user or \
not user.status or \
not user.user_password or \
not UserService.check_password(user, value['password']):
raise colander.Invalid(form, _('Login failed'))
def login_ok(request, user, route='log'):
headers = remember(request, user.id)
user.last_login_date = create_now()
DBSession.add(user)
return HTTPFound(location=request.route_url(route), headers=headers)
def get_user_by_identity(request):
identity = request.POST.get('username')
if identity.find('@') > -1:
q = DBSession.query(User).filter_by(email=identity)
else:
q = DBSession.query(User).filter_by(user_name=identity)
return q.first()
one_hour = timedelta(1/24)
two_minutes = timedelta(1/24/60)
def login_by_code(request):
q = DBSession.query(User).filter_by(security_code=request.GET['code'])
user = q.first()
if not user or \
create_now() - user.security_code_date > one_hour:
return HTTPFound(location=request.route_url('login-by-code-failed'))
user.security_code = None
DBSession.add(user)
DBSession.flush()
return login_ok(request, user, 'change-password')
def login_default_response():
return dict(title=_('Login'), label_forgot_password=_('Forgot password'))
@view_config(route_name='login', renderer='templates/login.pt')
def view_login(request):
if authenticated_userid(request):
return HTTPFound(location=request.route_url('home'))
if '_LOCALE_' in request.GET:
resp = Response()
resp.set_cookie('_LOCALE_', request.GET['_LOCALE_'], 31536000)
return HTTPFound(
location=request.route_url('login'), headers=resp.headers)
if 'code' in request.GET:
return login_by_code(request)
resp = login_default_response()
schema = Login(validator=login_validator)
btn_submit = Button('submit', _('Submit'))
form = Form(schema, buttons=(btn_submit,))
if 'submit' not in request.POST:
resp['form'] = form.render()
return resp
controls = request.POST.items()
schema.user = user = get_user_by_identity(request)
try:
c = form.validate(controls)
except ValidationFailure:
resp['form'] = form.render()
return resp
return login_ok(request, user)
@view_config(route_name='logout')
def view_logout(request):
headers = forget(request)
return HTTPFound(location = request.route_url('home'),
headers = headers)
@view_config(route_name='login-by-code-failed', renderer='templates/login-by-code-failed.pt')
def view_login_by_code_failed(request):
return dict(title='Login by code failed')
###################
# Change password #
###################
class ChangePassword(colander.Schema):
new_password = colander.SchemaNode(
colander.String(), title=_('New password'),
widget=widget.PasswordWidget())
retype_password = colander.SchemaNode(
colander.String(), title=_('Retype new password'),
widget=widget.PasswordWidget())
def password_validator(form, value):
if value['new_password'] != value['retype_password']:
raise colander.Invalid(form, _('Retype mismatch'))
@view_config(
route_name='change-password', renderer='templates/change-password.pt',
permission='view')
def view_change_password(request):
schema = ChangePassword(validator=password_validator)
btn_submit = Button('save', _('Save'))
btn_cancel = Button('cancel', _('Cancel'))
form = Form(schema, buttons=(btn_submit, btn_cancel))
resp = dict(title=_('Change password'))
if not request.POST:
resp['form'] = form.render()
return resp
if 'save' not in request.POST:
return HTTPFound(location=request.route_url('home'))
schema.request = request
controls = request.POST.items()
try:
c = form.validate(controls)
except ValidationFailure as e:
resp['form'] = form.render()
return resp
UserService.set_password(request.user, c['new_password'])
DBSession.add(request.user)
return HTTPFound(location=request.route_url('change-password-done'))
@view_config(
route_name='change-password-done',
renderer='templates/change-password-done.pt', permission='view')
def view_change_password_done(request):
return dict(title=_('Change password'))
##################
# Reset password #
##################
class ResetPassword(colander.Schema):
email = colander.SchemaNode(
colander.String(), title=_('Email'),
description=_(
'email-reset-password',
default='Enter your email address and we will send you '\
'a link to reset your password.')
)
def reset_password_validator(form, value):
user = form.user
if not user or not user.status:
raise colander.Invalid(form, _('Invalid email'))
def security_code_age(user):
return create_now() - user.security_code_date
def send_email_security_code(
request, user, time_remain, subject, body_msg_id, body_default_file):
settings = get_settings()
up = urlparse(request.url)
url = '{}://{}/login?code={}'.format(
up.scheme, up.netloc, user.security_code)
minutes = int(time_remain.seconds / 60)
data = dict(url=url, minutes=minutes)
here = os.path.abspath(os.path.dirname(__file__))
body_file = os.path.join(here, body_default_file)
with open(body_file) as f:
body_tpl = f.read()
body = _(body_msg_id, default=body_tpl, mapping=data)
body = request.localizer.translate(body)
sender = '{} <{}>'.format(
settings['mail.sender_name'], settings['mail.username'])
subject = request.localizer.translate(_(subject))
message = Message(
subject=subject, sender=sender, recipients=[user.email], body=body)
mailer = request.registry['mailer']
mailer.send(message)
def regenerate_security_code(user):
age = security_code_age(user)
remain = one_hour - age
if user.security_code and age < one_hour and remain > two_minutes:
return remain
UserService.regenerate_security_code(user)
user.security_code_date = create_now()
DBSession.add(user)
return one_hour
@view_config(route_name='reset-password', renderer='templates/reset-password.pt')
def view_reset_password(request):
if authenticated_userid(request):
return HTTPFound(location=request.route_url('home'))
resp = dict(title=_('Reset password'))
schema = ResetPassword(validator=reset_password_validator)
btn_submit = Button('submit', _('Send password reset email'))
form = Form(schema, buttons=(btn_submit,))
if 'submit' in request.POST:
controls = request.POST.items()
identity = request.POST.get('email')
q = DBSession.query(User).filter_by(email=identity)
schema.user = user = q.first()
try:
c = form.validate(controls)
except ValidationFailure:
resp['form'] = form.render()
return resp
remain = regenerate_security_code(user)
send_email_security_code(
request, user, remain, 'Reset password', 'reset-password-body',
'reset-password-body.tpl')
return HTTPFound(location=request.route_url('reset-password-sent'))
resp['form'] = form.render()
return resp
@view_config(
route_name='reset-password-sent', renderer='templates/reset-password-sent.pt')
def view_reset_password_sent(request):
return dict(title=_('Reset password'))
We accepted password recovery requests. Please click the following link:
${url}
This link will expire in ${minutes} minutes.
If you did not request this, please ignore it.
<div metal:use-macro="load: layout.pt">
<div metal:fill-slot="content" i18n:domain="login">
<p i18n:translate="change-password-done">
Your password has been changed
</p>
</div>
</div>
<div metal:use-macro="load: layout-form.pt">
<div metal:fill-slot="content">
<div tal:content="structure form" />
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<h1 i18n:translate="Add user group">Add user group</h1>
<div tal:content="structure form"/>
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title" i18n:translate="Warning">Warning</h3>
</div>
<div class="panel-body" i18n:translate="warning-delete-group">
Delete
<span tal:replace="group.group_name" i18n:name="group_name"/>
group that has
<span tal:replace="group.member_count" i18n:name="member_count"/>
members ?
</div>
</div>
<div tal:content="structure form"/>
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<h1 i18n:translate="Edit group">Edit group</h1>
<div tal:content="structure form"/>
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<h1 i18n:translate="">Groups</h1>
<table class="table table-striped table-hover">
<thead>
<tr>
<th i18n:translate="">Group name</th>
<th i18n:translate="">Description</th>
<th i18n:translate="">Member count</th>
<th colspan="2"/>
</tr>
</thead>
<tbody>
<tr tal:repeat="group groups">
<td i18n:translate="" tal:content="group.group_name"/>
<td i18n:translate="" tal:content="group.description"/>
<td tal:content="group.member_count" align="center"/>
<td><a href="/group/${group.id}" i18n:translate="">Edit</a></td>
<td><a href="/group/${group.id}/delete" i18n:translate="">Delete</a></td>
</tr>
</tbody>
</table>
</div>
</div>
<div metal:use-macro="load: layout.pt">
<div metal:fill-slot="content" i18n:domain="home">
<div class="content">
<h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
<p i18n:translate="welcome" class="lead">Welcome to <span
class="font-normal">Web Starter</span>, a&nbsp;Pyramid
application generated&nbsp;by<br><span class="font-normal">Cookiecutter</span>.</p>
<p tal:condition="not request.user">
<a href="/login" i18n:translate="login-link">Login please</a>
</p>
<p i18n:translate="you-are-logged-in" tal:condition="request.user">You are logged in as
<span tal:replace="request.user.user_name" i18n:name="username" />. Click <a href="/logout">here</a> to
logout.</p>
</div>
</div>
</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" href="/static/pyramid-16x16.png">
<title tal:content="title"/>
<!-- Bootstrap core CSS -->
<link href="/deform_static/css/bootstrap.min.css" rel="stylesheet"/>
<link href="/deform_static/css/form.css" rel="stylesheet"/>
<!-- Custom styles for this template -->
<link href="/static/signin.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div tal:condition="request.session.peek_flash()">
<div class="alert alert-success" tal:repeat="message request.session.pop_flash()">
${message}
</div>
</div>
<div tal:condition="request.session.peek_flash('error')">
<div class="alert alert-danger" tal:repeat="message request.session.pop_flash('error')">
${message}
</div>
</div>
<div metal:define-slot="content"/>
</div> <!-- /container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="/deform_static/scripts/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="/deform_static/scripts/bootstrap.min.js"></script>
<script type="text/javascript" src="/deform_static/scripts/deform.js"></script>
<div metal:define-slot="content-script"/>
</body>
</html>
<!DOCTYPE html>
<html lang="en" i18n:domain="menu">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" href="/static/pyramid-16x16.png">
<title tal:content="title"/>
<!-- Bootstrap core CSS -->
<link href="/deform_static/css/bootstrap.min.css" rel="stylesheet"/>
<link href="/deform_static/css/form.css" rel="stylesheet"/>
<!-- Custom styles for this template -->
<link href="/static/signin.css" rel="stylesheet">
<div metal:define-slot="head"/>
</head>
<body>
<!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top" role="navigation"
i18n:domain="menu">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"></a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li tal:attributes="class request.path == '/' and 'active'">
<a href="/" i18n:translate="Home">Home</a></li>
<li class="dropdown"
tal:attributes="class request.matched_route.name in ['user',
'user-add', 'user-edit', 'user-delete', 'group', 'group-edit',
'group-delete'] and 'active'">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
i18n:translate="Admin">Admin<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/user" i18n:translate="Users">Users</a></li>
<li><a href="/user/add" i18n:translate="Add user">Add user</a></li>
<li><a href="/group" i18n:translate="Users groups">Users groups</a></li>
<li><a href="/group/add" i18n:translate="Add users group">Add users group</a></li>
</ul>
</li>
<li class="dropdown" tal:attributes="class request.path in
['/change-password'] and 'active'">
<a href="#" class="dropdown-toggle" data-toggle="dropdown"
i18n:translate="My account">My account <b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/change-password" i18n:translate="Change password">Change password</a></li>
<li><a href="/logout" i18n:translate="username-logout">
<span tal:replace="request.user.user_name" i18n:name="username"/> logout
</a>
</li>
</ul>
</li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container">
<div tal:condition="request.session.peek_flash()">
<div class="alert alert-success" tal:repeat="message request.session.pop_flash()">
${message}
</div>
</div>
<div tal:condition="request.session.peek_flash('error')">
<div class="alert alert-danger" tal:repeat="message request.session.pop_flash('error')">
${message}
</div>
</div>
<div metal:define-slot="content"/>
</div> <!-- /container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script type="text/javascript" src="/deform_static/scripts/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="/deform_static/scripts/bootstrap.min.js"></script>
<script type="text/javascript" src="/deform_static/scripts/deform.js"></script>
<div metal:define-slot="content-script"/>
</body>
</html>
<!DOCTYPE html metal:define-macro="layout">
<html lang="${request.locale_name}"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="home">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="pyramid web application">
<meta name="author" content="Pylons Project">
<link rel="shortcut icon" href="/static/pyramid-16x16.png">
<title>Web Starter - <span tal:replace="title"/></title>
<!-- Bootstrap core CSS -->
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
<link href="/static/theme.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
<script src="//oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js" integrity="sha384-f1r2UzjsxZ9T4V1f2zBO/evUqSEOpeaUUZcMTz1Up63bl4ruYnFYeM+BxI4NhyI0" crossorigin="anonymous"></script>
<![endif]-->
</head>
<body>
<div class="starter-template">
<div class="container">
<div class="row">
<div class="col-md-2">
<img class="logo img-responsive"
src="/static/pyramid.png" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div metal:define-slot="content">No content</div>
</div>
</div>
<div class="row">
<div class="links">
<ul>
<li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
<li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://webchat.freenode.net/?channels=pyramid">IRC Channel</a></li>
<li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
</ul>
</div>
</div>
<div class="row">
<div class="copyright" i18n:translate="copyright">
Copyright &copy; Pylons Project
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="//oss.maxcdn.com/libs/jquery/1.10.2/jquery.min.js" integrity="sha384-aBL3Lzi6c9LNDGvpHkZrrm3ZVsIwohDD7CDozL0pk8FwCrfmV7H9w8j3L7ikEv6h" crossorigin="anonymous"></script>
<script src="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/js/bootstrap.min.js" integrity="sha384-s1ITto93iSMDxlp/79qhWHi+LsIi9Gx6yL+cOKDuymvihkfol83TYbLbOw+W/wv4" crossorigin="anonymous"></script>
</body>
</html>
<form
tal:define="style style|field.widget.style;
css_class css_class|string:${field.widget.css_class or field.css_class or ''};
item_template item_template|field.widget.item_template;
autocomplete autocomplete|field.autocomplete;
title title|field.title;
errormsg errormsg|field.errormsg;
description description|field.description;
buttons buttons|field.buttons;
use_ajax use_ajax|field.use_ajax;
ajax_options ajax_options|field.ajax_options;
formid formid|field.formid;
action action|field.action or None;
method method|field.method;"
tal:attributes="autocomplete autocomplete;
style style;
class css_class;
action action;"
id="${formid}"
method="${method}"
enctype="multipart/form-data"
accept-charset="utf-8"
i18n:domain="deform"
>
<fieldset class="deform-form-fieldset">
<legend tal:condition="title">${title}</legend>
<input type="hidden" name="_charset_" />
<input type="hidden" name="__formid__" value="${formid}"/>
<div class="alert alert-danger" tal:condition="field.error">
<div class="error-msg-lbl" i18n:translate=""
>There was a problem with your submission</div>
<div class="error-msg-detail" i18n:translate=""
>Errors have been highlighted below</div>
<p class="error-msg">${field.errormsg}</p>
</div>
<p class="section first" tal:condition="description">
${description}
</p>
<div class="grid-container">
<div tal:repeat="child field"
tal:replace="structure child.render_template(item_template)"/>
</div>
<div class="form-group deform-form-buttons">
<tal:loop tal:repeat="button buttons">
<button
tal:define="btn_disposition repeat.button.start and 'btn-primary' or 'btn-default';"
tal:attributes="disabled button.disabled if button.disabled else None"
id="${formid+button.name}"
name="${button.name}"
type="${button.type}"
class="btn ${button.css_class or btn_disposition}"
value="${button.value}"
tal:condition="button.type != 'link'">
<span tal:condition="button.icon" class="glyphicon glyphicon-${button.icon}"></span>
${button.title}
</button>
<a
tal:define="btn_disposition repeat.button.start and 'btn-primary' or 'btn-default';
btn_href button.value|''"
class="btn ${button.css_class or btn_disposition}"
id="${field.formid + button.name}"
href="${btn_href}"
tal:condition="button.type == 'link'">
<span tal:condition="button.icon" class="glyphicon glyphicon-${button.icon}"></span>
${button.title}
</a>
</tal:loop>
</div>
</fieldset>
<script type="text/javascript" tal:condition="use_ajax">
deform.addCallback(
'${formid}',
function(oid) {
var target = '#' + oid;
var options = {
target: target,
replaceTarget: true,
success: function() {
deform.processCallbacks();
deform.focusFirstInput(target);
},
beforeSerialize: function() {
// See http://bit.ly/1agBs9Z (hack to fix tinymce-related ajax bug)
if ('tinymce' in window) {
$(tinymce.get()).each(
function(i, el) {
var content = el.getContent();
var editor_input = document.getElementById(el.id);
editor_input.value = content;
});
}
}
};
var extra_options = ${ajax_options} || {};
$('#' + oid).ajaxForm($.extend(options, extra_options));
}
);
</script>
</form>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="head">
<link href="/static/grid.css" rel="stylesheet"/>
</div>
<div metal:fill-slot="content">
<h1>
Log File
</h1>
<div tal:content="structure form"/>
<div tal:condition="request.GET.get('lihat')">
<div tal:condition="not count" class="alert alert-danger">
Tidak ada hasil.
</div>
<div tal:condition="count">
<div class="alert alert-success">
Ada ${count} hasil.
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>
ID
</th>
<th>
Keterangan
</th>
</tr>
</thead>
<tbody>
<tr tal:repeat="row rows">
<td>
${row.id}
</td>
<td tal:condition="row.line.find('\n') < 0">
${row.line}
</td>
<td tal:condition="row.line.find('\n') > -1">
<pre class="errormsg">${row.line}</pre>
</td>
</tr>
</tbody>
</table>
<center tal:content="structure rows.pager(format='$link_previous ~4~ $link_next')"/>
</div><!-- condition count -->
</div><!-- condition lihat -->
</div>
</div>
<div metal:use-macro="load: layout.pt">
<div metal:fill-slot="content" i18n:domain="login">
<p i18n:translate="Invalid security code">
Invalid security code.
</p>
</div>
</div>
<div metal:use-macro="load: layout-form.pt">
<div metal:fill-slot="content">
<div tal:condition="form" tal:content="structure form"/>
<p tal:condition="request.locale_name != 'en'">
<a href="?_LOCALE_=en">English</a>
</p>
<p tal:condition="request.locale_name != 'id'">
<a href="?_LOCALE_=id">Indonesia</a>
</p>
</div>
<div metal:fill-slot="content-script">
<script type="text/javascript">
$(window).on('load', function() {
$("p.help-block").html('<a href="/reset-password">${label_forgot_password}</a>');
});
</script>
</div>
</div>
<div metal:use-macro="load: layout.pt">
<div metal:fill-slot="content" i18n:domain="login">
<p i18n:translate="reset-password-link-sent">
Check your email for a link to reset your password. If it
doesn’t appear within a few minutes, check your spam folder. The
reset password link has been sent. Please check your email.
</p>
</div>
</div>
<div metal:use-macro="load: layout-form.pt">
<div metal:fill-slot="content">
<div tal:content="structure form" />
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<h1 i18n:translate="Add user">Add User</h1>
<div tal:content="structure form"/>
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<div class="panel panel-danger">
<div class="panel-heading">
<h3 class="panel-title" i18n:translate="Warning">Warning</h3>
</div>
<div class="panel-body" i18n:translate="warning-delete-user">
Delete <span tal:replace="user.email" i18n:name="email"/>
ID <span tal:replace="user.id" i18n:name="uid"/> ?
</div>
</div>
<div tal:content="structure form"/>
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<h1 i18n:translate="Edit user">Edit user</h1>
<div tal:content="structure form"/>
</div>
</div>
<div metal:use-macro="load: ../layout-menu.pt">
<div metal:fill-slot="content" i18n:domain="user">
<h1 i18n:translate="">Users</h1>
<form method="post" action="/user">
<table class="table table-striped table-hover">
<thead>
<tr>
<th i18n:translate="">Group</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<select id="gid" name="gid" class="form-control">
<option/>
<option
i18n:translate=""
tal:repeat="group groups"
tal:attributes="value group.id; selected
str(group.id) == request.GET.get('gid')">
${group.group_name}
</option>
</select>
</td>
<td>
<input type="submit" name="submit" value="Show"
class="btn btn-primary"
i18n:attributes="value">
</td>
</tr>
</tbody>
</table>
</form>
<p tal:condition="not count" i18n:translate="">No result</p>
<p tal:condition="count" i18n:translate="user-result">
<span tal:replace="count" i18n:name="count"/> rows
</p>
<table class="table table-striped table-hover" tal:condition="count">
<thead>
<tr>
<th i18n:translate="">Email</th>
<th i18n:translate="">Username</th>
<th i18n:translate="">Status</th>
<th i18n:translate="">Last login</th>
<th i18n:translate="">Registered date</th>
<th colspan="2"/>
</tr>
</thead>
<tbody>
<tr tal:repeat="user users">
<td tal:content="user.email"/>
<td tal:content="user.user_name"/>
<td tal:condition="user.status" i18n:translate="">Active</td>
<td tal:condition="not user.status"/>
<td tal:condition="user.last_login_date"
tal:content="user.last_login_date.strftime('%d-%m-%Y
%H:%M:%S %z')"/>
<td tal:condition="not user.last_login_date"/>
<td tal:content="user.registered_date.strftime('%d-%m-%Y %H:%M:%S %z')"/>
<td>
<a href="/user/${user.id}" i18n:translate="">Edit</a>
</td>
<td tal:condition="user.id > 1 and user.id != request.user.id">
<a href="/user/${user.id}/delete" i18n:translate="">Delete</a>
</td>
<td tal:condition="user.id <= 1" i18n:translate="">System</td>
<td tal:condition="user.id > 1 and user.id == request.user.id" i18n:translate="">You</td>
</tr>
</tbody>
</table>
</div>
</div>
NAME="$1"
msgfmt -o web_starter/locale/id/LC_MESSAGES/$NAME.mo web_starter/locale/id/LC_MESSAGES/$NAME.po
###
# app configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/environment.html
###
[app:main]
use = egg:iso8583_web
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = id
sqlalchemy.url = postgresql://user:pass@localhost/dbname
timezone = Asia/Jakarta
localization = id_ID.UTF-8
mail.host = localhost
mail.port = 25
mail.username = user@example.com
mail.password = FIXME
mail.sender_name = Example Name
###
# wsgi server configuration
###
[server:main]
use = egg:waitress#main
listen = *:6543
###
# logging configuration
# https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/logging.html
###
[loggers]
keys = root, iso8583_web
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
[logger_iso8583_web]
level = WARN
handlers =
qualname = iso8583_web
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s
[pytest]
testpaths = iso8583_web
python_files = *.py
import os
import sys
import subprocess
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README.rst')) as f:
README = f.read()
with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
line = CHANGES.splitlines()[0]
version = line.split()[0]
requires = [
'plaster_pastedeploy',
'pyramid',
'pyramid_chameleon',
'pyramid_debugtoolbar',
'pyramid_tm',
'waitress',
'zope.sqlalchemy',
'psycopg2-binary',
'pytz',
'ziggurat-foundations',
'alembic',
'colander',
'deform',
'pyramid_beaker',
'pyramid_mailer',
'requests',
]
customs_require = [
'http://repo.opensipkd.com/pip/opensipkd-base-0.2.tar.gz',
'http://repo.opensipkd.com/pip/opensipkd-hitung-0.1.tar.gz',
'http://repo.opensipkd.com/pip/opensipkd-iso8583-0.1.tar.gz',
]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
'pytest',
'pytest-cov',
]
def pip_install(package, upgrade=False):
cmd = [pip, 'install']
if upgrade:
cmd += ['--upgrade']
if sys.argv[2:]:
option = sys.argv[2] # Bisa untuk proxy
cmd += [option]
cmd += [package]
if subprocess.call(cmd) != 0:
sys.exit()
if sys.argv[1:] and sys.argv[1] == 'develop-use-pip':
bin_ = os.path.split(sys.executable)[0]
pip = os.path.join(bin_, 'pip')
pip_install('pip', True)
pip_install('setuptools', True)
requires_ = requires + customs_require
for package in requires_:
if sys.argv[2:]:
cmd = [pip, 'install', sys.argv[2], package]
else:
cmd = [pip, 'install', package]
if subprocess.call(cmd) != 0:
sys.exit()
cmd = [sys.executable, sys.argv[0], 'develop']
subprocess.call(cmd)
sys.exit()
setup(
name='iso8583_web',
version=version,
description='Web untuk menampilkan log ISO8583',
long_description=README + '\n\n' + CHANGES,
classifiers=[
'Programming Language :: Python',
'Framework :: Pyramid',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
],
author='',
author_email='',
url='',
keywords='web pyramid pylons',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
extras_require={
'testing': tests_require,
},
install_requires=requires,
entry_points={
'paste.app_factory': [
'main = iso8583_web:main',
],
'console_scripts': [
'initialize_iso8583_web_db = iso8583_web.scripts.initialize_db:main',
'iso8583 = iso8583_web.scripts.forwarder:main',
]
},
)
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!