Commit 0acf5321 by Owo Sugiana

user/list.pt filter form menggunakan Schema

1 parent d286170d
*.egg
*.egg-info
*.pyc
*$py.class
*~
.coverage
coverage.xml
build/
dist/
.tox/
nosetests.xml
env*/
tmp/
Data.fs*
*.sublime-project
*.sublime-workspace
.*.sw?
.sw?
.DS_Store
coverage
test
test*
0.1.3 2018-11-04
0.1.4 2019-02-01
----------------
- Versi otomatis sesuai CHANGES.txt.
- user/list.pt untuk filter kini menggunakan Schema.
0.1.3 2018-11-04
----------------
- Add user group management.
0.1.2 2018-10-29
----------------
- Add user management.
- Add menu.
- Additional argument for setup.py develop-use-pip (ex. --proxy).
0.1.1
-----
- Forgot password.
0.1
---
- Initial version.
Web Starter
===========
Change directory into your newly created project:
cd web-starter
Create a Python virtual environment:
python3 -m venv ../env
Upgrade packaging tools:
../env/bin/pip install --upgrade pip setuptools
Install required package:
../env/bin/python setup.py develop-use-pip
Set sqlalchemy.url on development.ini and create tables:
../env/bin/initialize_web_starter_db development.ini
Run your project:
../env/bin/pserve --reload development.ini
Web Starter
===========
Change directory into your newly created project::
$ cd web-starter
Create a Python virtual environment::
$ python3 -m venv ../env
Upgrade packaging tools::
$ ../env/bin/pip install --upgrade pip setuptools
Install required package::
$ ../env/bin/python setup.py develop-use-pip
Copy configuration file::
$ cp development.ini test.ini
Set ``sqlalchemy.url`` on ``test.ini`` and create tables::
$ ../env/bin/initialize_web_starter_db test.ini
Run your project::
$ ../env/bin/pserve --reload test.ini
Web Baru (Indonesian)
---------------------
Web Starter dirancang untuk disalin menjadi direktori baru. Misalkan Anda ingin
membuat aplikasi SMS dengan bekal ini maka salinlah direktorinya::
$ cd ..
$ cp -r web-starter web-sms
$ cd web-sms
$ rm -rf .git
$ rm .gitignore
$ mv web_starter web_sms
Carilah kata ``web_starter`` di seluruh file Python::
$ find -name "*.py" | xargs grep web_starter
Ubahlah filenya dan ganti ``web_starter`` menjadi ``web_sms``. Lakukan hal yang
sama pada file konfigurasi ``*.ini``.
Web Starter
===========
Getting Started
---------------
- Change directory into your newly created project.
cd web-starter
- Create a Python virtual environment.
python3 -m venv ../env
- Upgrade packaging tools.
../env/bin/pip install --upgrade pip setuptools
- Install required package.
../env/bin/python setup.py develop-use-pip
- Set sqlalchemy.url on development.ini and create tables.
../env/bin/initialize_web_starter_db development.ini
- Run your project.
../env/bin/pserve --reload development.ini
......@@ -42,7 +42,7 @@ listen = localhost:6543
###
[loggers]
keys = root, web_starter
keys = root, sqlalchemy, web_starter
[handlers]
keys = console
......@@ -54,6 +54,11 @@ keys = generic
level = INFO
handlers = console
[logger_sqlalchemy]
level = INFO
handlers =
qualname = sqlalchemy.engine
[logger_web_starter]
level = DEBUG
handlers =
......
import os
import sys
import subprocess
from setuptools import setup, find_packages
from setuptools import (
setup,
find_packages,
)
here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'README.txt')) as f:
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',
......@@ -27,6 +34,10 @@ requires = [
'pyramid_mailer',
]
customs_require = [
# 'http://vpn.opensipkd.com/2019/01/opensipkd-jsonrpc-0.1.tgz',
]
tests_require = [
'WebTest >= 1.3.1', # py3 compat
'pytest',
......@@ -34,22 +45,41 @@ tests_require = [
]
def run(cmd):
if subprocess.call(cmd) != 0:
sys.exit()
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]
run(cmd)
if sys.argv[1:] and sys.argv[1] == 'develop-use-pip':
bin_ = os.path.split(sys.executable)[0]
pip = os.path.join(bin_, 'pip')
for package in requires:
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]
subprocess.call(cmd)
run(cmd)
cmd = [sys.executable, sys.argv[0], 'develop']
subprocess.call(cmd)
sys.exit()
setup(
name='web_starter',
version='0.1.3',
version=version,
description='Web Starter',
long_description=README + '\n\n' + CHANGES,
classifiers=[
......
.grid-container {
display: grid;
grid-template-columns: auto auto auto;
}
......@@ -17,6 +17,7 @@
<!-- Custom styles for this template -->
<link href="/static/signin.css" rel="stylesheet">
<div metal:define-slot="head"/>
</head>
<body>
......
......@@ -8,7 +8,7 @@
<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="${request.static_url('web_starter:static/pyramid-16x16.png')}">
<link rel="shortcut icon" href="/static/pyramid-16x16.png">
<title>Web Starter - <span tal:replace="title"/></title>
......@@ -16,7 +16,7 @@
<link href="//oss.maxcdn.com/libs/twitter-bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom styles for this scaffold -->
<link href="${request.static_url('web_starter:static/theme.css')}" rel="stylesheet">
<link href="/static/theme.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
......@@ -32,7 +32,7 @@
<div class="row">
<div class="col-md-2">
<img class="logo img-responsive"
src="${request.static_url('web_starter:static/pyramid.png')}" alt="pyramid web framework">
src="/static/pyramid.png" alt="pyramid web framework">
</div>
<div class="col-md-10">
<div metal:define-slot="content">No content</div>
......
<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="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 metal:fill-slot="head">
<link href="/static/grid.css" rel="stylesheet"/>
</div>
<div metal:fill-slot="content" i18n:domain="user">
<h1 i18n:translate="">Users</h1>
<div tal:content="structure form"/>
<div tal:condition="request.GET.get('show')">
<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>
</div>
import os
import re
from email.utils import parseaddr
from sqlalchemy import func
from pkg_resources import resource_filename
from sqlalchemy import (
func,
or_,
)
from pyramid.view import view_config
from pyramid.httpexceptions import (
HTTPFound,
......@@ -12,6 +17,7 @@ from deform import (
Form,
ValidationFailure,
Button,
ZPTRendererFactory,
)
from deform.widget import (
SelectWidget,
......@@ -38,40 +44,96 @@ _ = TranslationStringFactory('user')
########
# List #
########
########
@colander.deferred
def group_widget(node, kw):
return SelectWidget(values=kw['group_list'])
class FilterSchema(colander.Schema):
group = colander.SchemaNode(
colander.String(), missing=colander.drop, widget=group_widget)
name = colander.SchemaNode(colander.String(), missing=colander.drop)
def get_group_list():
r = []
q = DBSession.query(Group).order_by(Group.group_name)
for row in q:
group = (str(row.id), _(row.group_name))
r.append(group)
return r
def query_filter(request, q):
return q.filter(
deform_templates = resource_filename('deform', 'templates')
here = os.path.abspath(os.path.dirname(__file__))
my_templates = os.path.join(here, 'templates', 'user')
search_path = [my_templates, deform_templates]
my_renderer = ZPTRendererFactory(search_path)
def get_filter_form():
group_list = get_group_list()
group_list = [('', '')] + group_list
schema = FilterSchema()
schema = schema.bind(group_list=group_list)
btn_show = Button('show', _('Show'))
buttons = (btn_show,)
return Form(schema, buttons=buttons, renderer=my_renderer)
def query_filter(q, p):
if 'group' in p:
q = q.filter(
User.id==UserGroup.user_id,
UserGroup.group_id==request.GET['gid'])
UserGroup.group_id==p['group'])
if 'name' in p:
pattern = '%{}%'.format(p['name'])
q = q.filter(
or_(
User.email.ilike(pattern),
User.user_name.ilike(pattern)))
return q
def get_filter(request):
p = dict()
if request.GET.get('group'):
try:
p['group'] = int(request.GET.get('group'))
except ValueError:
pass
if request.GET.get('name'):
p['name'] = request.GET.get('name')
return p
@view_config(
route_name='user', renderer='templates/user/list.pt',
permission='user-edit')
def view_list(request):
form = get_filter_form()
resp = dict(title=_('Users'))
if request.POST:
p = dict(gid=request.POST['gid'])
return HTTPFound(location=request.route_url('user', _query=p))
if 'gid' in request.GET and request.GET['gid']:
items = request.POST.items()
try:
int(request.GET['gid'])
except ValueError:
return HTTPNotFound()
q_count = DBSession.query(func.count())
q_count = query_filter(request, q_count)
count = q_count.scalar()
if count:
q_user = DBSession.query(User)
q_user = query_filter(request, q_user)
else:
q_count = DBSession.query(func.count(User.id))
count = q_count.scalar()
if count:
q_user = DBSession.query(User)
q_group = DBSession.query(Group).order_by(Group.group_name)
resp = dict(title=_('Users'), count=count, groups=q_group)
c = form.validate(items)
except ValidationFailure as e:
resp['form'] = e.render()
return resp
p = dict(c.items())
p['show'] = 1
return HTTPFound(location=request.route_url('user', _query=p))
p = get_filter(request)
resp['form'] = form.render(appstruct=p)
if 'show' not in request.GET:
return resp
q_count = DBSession.query(func.count())
q_count = query_filter(q_count, p)
resp['count'] = count = q_count.scalar()
if count:
q_user = DBSession.query(User)
q_user = query_filter(q_user, p)
resp['users'] = q_user.order_by(User.email)
return resp
......@@ -189,11 +251,7 @@ def get_form(request, class_form, user=None):
status_list = (
(1, _('Active')),
(0, _('Inactive')))
group_list = []
q = DBSession.query(Group).order_by(Group.group_name)
for row in q:
group = (str(row.id), _(row.group_name))
group_list.append(group)
group_list = get_group_list()
schema = class_form()
schema = schema.bind(
status_list=status_list, group_list=group_list, user=user)
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!