Commit e63eced9 by Owo Sugiana

Tambah /theme1/user/add yang lebih ringkas

1 parent 0459a542
0.2.1 2022-04-11
----------------
- Tambah route /theme1/user/add untuk form yang lebih ringkas.
0.2 2022-04-09 0.2 2022-04-09
-------------- --------------
- Form User menggunakan ColanderAlchemy - Form User menggunakan ColanderAlchemy
......
...@@ -33,7 +33,8 @@ requires = [ ...@@ -33,7 +33,8 @@ requires = [
'pyramid_beaker', 'pyramid_beaker',
'pyramid_mailer', 'pyramid_mailer',
'sqlalchemy-datatables', 'sqlalchemy-datatables',
'ColanderAlchemy'] 'ColanderAlchemy',
'yaml']
tests_require = [ tests_require = [
'WebTest >= 1.3.1', # py3 compat 'WebTest >= 1.3.1', # py3 compat
......
...@@ -8,8 +8,6 @@ reset-password ...@@ -8,8 +8,6 @@ reset-password
reset-password-sent reset-password-sent
login-by-code-failed login-by-code-failed
user user
user-grid,/user/grid
user-act,/user/{act}/act
user-add,/user/add user-add,/user/add
user-edit,/user/{id} user-edit,/user/{id}
user-delete,/user/{id}/delete user-delete,/user/{id}/delete
...@@ -18,3 +16,7 @@ group-add,/group/add ...@@ -18,3 +16,7 @@ group-add,/group/add
group-edit,/group/{id} group-edit,/group/{id}
group-delete,/group/{id}/delete group-delete,/group/{id}/delete
api api
user-act,/user/{act}/act
theme1-user,/theme1/user
theme1-user-add,/theme1/user/add
theme1-user-edit,/theme1/user/{id}
import os
import yaml
import colander
from deform import field
from deform.widget import MappingWidget
class PanelWidget(MappingWidget):
# templates/user/panel.pt
template = 'panel'
class Panel(field.Field):
widget = PanelWidget()
here = os.path.abspath(os.path.dirname(__file__))
my_templates = os.path.join(here, 'templates', 'user')
search_path = [my_templates]
Panel.set_zpt_renderer(search_path)
here = os.path.abspath(os.path.dirname(__file__))
schema = colander.Schema()
def get_widgets(form, fields, level=0):
r = []
for field_id in fields:
if field_id.find('Group') == 0:
field = get_widgets(form, fields[field_id], level+1)
else:
# templates/user/mapping_item.pt
form[field_id].my_level = level
field = form[field_id].render_template('mapping_item')
r.append(field)
return '\n'.join(r)
def render(form, yml_file):
for button in form.buttons:
if isinstance(button, tuple):
button = button[0]
yml_file = os.path.join(here, yml_file)
with open(yml_file) as f:
y = yaml.safe_load(f)
panels = []
for title in y['Panels']:
panel = Panel(schema)
panel.title = title
fields = y['Panels'][title]
panel.my_widgets = get_widgets(form, fields)
panels.append(panel.render())
return '\n'.join(panels)
<div metal:use-macro="load: ../../../templates/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: ../../../templates/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>
%YAML 1.2
---
Panels:
No Title:
user_name: User Name
email: Email
groups: Grup
Bio Data:
tempat_lahir: Tempat Lahir
tgl_lahir: Tanggal Lahir
Alamat:
Group 1:
alamat: Alamat
Group:
rt: RT
rw: RW
Group 2:
kelurahan: Kelurahan
kecamatan: Kecamatan
kabupaten: Kota / Kabupaten
provinsi: Provinsi
<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;
attributes|field.widget.attributes|{};"
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 tal:replace="structure field.my_widgets"/>
<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;
attributes|button.attributes|{};"
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:use-macro="load: ../../../templates/layout-menu.pt">
<div metal:fill-slot="head"> <div metal:fill-slot="head">
<link rel="stylesheet" type="text/css" href="/static/datatables.min.css"/> <link rel="stylesheet" type="text/css" href="/static/datatables.min.css"/>
</div> </div>
......
<div class="${field.my_level < 2 and 'col-md-6' or 'col-md-2'}">
<div tal:define="error_class error_class|field.widget.error_class;
description description|field.description;
title title|field.title;
oid oid|field.oid;
hidden hidden|field.widget.hidden;
category category|field.widget.category;
structural hidden or category == 'structural';
required required|field.required;"
class="form-group ${field.error and 'has-error' or ''} ${field.widget.item_css_class or ''} ${field.default_item_css_class()}"
title="${description}"
id="item-${oid}"
tal:omit-tag="structural"
i18n:domain="user">
<label for="${oid}"
class="col-md-3 control-label ${required and 'required' or ''}"
tal:condition="not structural"
id="req-${oid}">
${title}
</label>
<div tal:define="input_prepend field.widget.input_prepend | None;
input_append field.widget.input_append | None"
tal:omit-tag="not (input_prepend or input_append)">
<span class="input-group-addon" tal:condition="input_prepend">
${input_prepend}
</span>
<div class="col-md-9">
<span tal:replace="structure field.serialize(cstruct).strip()"/>
<span class="input-group-addon" tal:condition="input_append">
${input_append}
</span>
</div>
</div>
<p class="help-block"
tal:define="errstr 'error-%s' % field.oid"
tal:repeat="msg field.error.messages()"
i18n:translate=""
tal:attributes="id repeat.msg.index==0 and errstr or
('%s-%s' % (errstr, repeat.msg.index))"
tal:condition="field.error and not field.widget.hidden and not field.typ.__class__.__name__=='Mapping'">
${msg}
</p>
<p tal:condition="field.description and not field.widget.hidden" class="help-block">
${field.description}
</p>
</div>
</div>
<div
tal:define="title field.title;
widgets field.my_widgets;"
class="panel">
<div tal:condition="title != 'No Title'" class="panel-heading bg-info">
<h2 class="panel-title text-center">${title}</h2>
</div>
<div class="panel-body">
<div tal:replace="structure widgets"/>
</div>
</div>
import logging import os
from sqlalchemy import ( from sqlalchemy import (
func,
cast, cast,
Text, Text,
) )
from pyramid.view import view_config from pyramid.view import view_config
from pyramid.i18n import TranslationStringFactory from pyramid.i18n import TranslationStringFactory
from deform import (
Form,
ZPTRendererFactory,
)
from datatables import ( from datatables import (
ColumnDT, ColumnDT,
DataTables, DataTables,
) )
from ..models import DBSession from ...models import DBSession
from ..models.ziggurat import User from ...models.ziggurat import User
from ..user import (
deform_templates,
log = logging.getLogger(__name__) BaseView,
)
from .renderer import render
_ = TranslationStringFactory('user') _ = TranslationStringFactory('user')
...@@ -27,9 +32,14 @@ columns = [ ...@@ -27,9 +32,14 @@ columns = [
ColumnDT(User.status), ColumnDT(User.status),
ColumnDT(cast(User.last_login_date, Text))] ColumnDT(cast(User.last_login_date, Text))]
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)
@view_config( @view_config(
route_name='user-grid', renderer='templates/user/grid.pt', route_name='theme1-user', renderer='templates/user/list.pt',
permission='user-edit') permission='user-edit')
def view_list(request): def view_list(request):
return dict(title=_('Users')) return dict(title=_('Users'))
...@@ -38,7 +48,27 @@ def view_list(request): ...@@ -38,7 +48,27 @@ def view_list(request):
@view_config(route_name='user-act', renderer='json', permission='user-edit') @view_config(route_name='user-act', renderer='json', permission='user-edit')
def view_act(request): def view_act(request):
if request.matchdict['act'] == 'grid': if request.matchdict['act'] == 'grid':
log.debug(f'{request.path}')
q = DBSession.query().select_from(User) q = DBSession.query().select_from(User)
dt = DataTables(request.GET, q, columns) dt = DataTables(request.GET, q, columns)
return dt.output_result() return dt.output_result()
class View(BaseView):
def render(self, form): # Override
form.my_widgets = render(form, 'templates/user/edit.yml')
return form.render()
def get_form(self, schema, user=None): # Override
return super().get_form(schema, user, my_renderer)
@view_config(
route_name='theme1-user-add', renderer='templates/user/add.pt',
permission='user-edit')
def add(self):
return super().add()
@view_config(
route_name='theme1-user-edit', renderer='templates/user/edit.pt',
permission='user-edit')
def edit(self):
return super().edit()
...@@ -120,36 +120,6 @@ def get_filter(request): ...@@ -120,36 +120,6 @@ def get_filter(request):
return p 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:
items = request.POST.items()
try:
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
####### #######
# Add # # Add #
####### #######
...@@ -186,17 +156,13 @@ field_status = colander.SchemaNode( ...@@ -186,17 +156,13 @@ field_status = colander.SchemaNode(
FIELDS = MY_FIELDS + [field_status, field_groups] FIELDS = MY_FIELDS + [field_status, field_groups]
edit_schema = SQLAlchemySchemaNode(User, includes=FIELDS) edit_schema = SQLAlchemySchemaNode(User, includes=FIELDS)
status_list = (
def get_form(request, schema, user=None):
status_list = (
(1, _('Active')), (1, _('Active')),
(0, _('Inactive'))) (0, _('Inactive')))
group_list = get_group_list()
schema = schema.bind( btn_save = Button('save', _('Save'))
status_list=status_list, group_list=group_list, user=user) btn_cancel = Button('cancel', _('Cancel'))
btn_save = Button('save', _('Save')) btn_delete = Button('delete', _('Delete'))
btn_cancel = Button('cancel', _('Cancel'))
return Form(schema, buttons=(btn_save, btn_cancel))
def add_member_count(gid): def add_member_count(gid):
...@@ -238,36 +204,6 @@ def insert(request, values): ...@@ -238,36 +204,6 @@ def insert(request, values):
return user, remain return user, remain
@view_config(
route_name='user-add', renderer='templates/user/add.pt',
permission='user-edit')
def view_add(request):
form = get_form(request, my_edit_schema)
resp = dict(title=_('Add user'))
if not request.POST:
resp['form'] = form.render()
return resp
if 'save' not in request.POST:
return HTTPFound(location=request.route_url('user'))
items = request.POST.items()
try:
c = form.validate(items)
except ValidationFailure:
resp['form'] = form.render()
return resp
user, remain = insert(request, dict(c.items()))
send_email_security_code(
request, user, remain, 'Welcome new user', 'email-new-user',
'email-new-user.tpl')
data = dict(email=user.email)
ts = _(
'user-added',
default='${email} has been added and the email has been sent.',
mapping=data)
request.session.flash(ts)
return HTTPFound(location=request.route_url('user'))
######## ########
# Edit # # Edit #
######## ########
...@@ -311,10 +247,52 @@ def update(request, user, values): ...@@ -311,10 +247,52 @@ def update(request, user, values):
add_member_count(gid) add_member_count(gid)
@view_config( class BaseView:
route_name='user-edit', renderer='templates/user/edit.pt', def __init__(self, request):
permission='user-edit') self.request = request
def view_edit(request):
def render(self, form):
return form.render()
def get_form(self, schema, user=None, renderer=None):
group_list = get_group_list()
schema = schema.bind(
status_list=status_list, group_list=group_list, user=user)
if renderer:
return Form(
schema, buttons=(btn_save, btn_cancel), renderer=renderer)
return Form(schema, buttons=(btn_save, btn_cancel))
def add(self):
request = self.request
form = self.get_form(my_edit_schema)
resp = dict(title=_('Add user'))
if not request.POST:
resp['form'] = self.render(form)
return resp
if 'save' not in request.POST:
return HTTPFound(location=request.route_url('user'))
items = request.POST.items()
try:
c = form.validate(items)
except ValidationFailure:
resp['form'] = self.render(form)
return resp
d = null_to_none(dict(c.items()))
user, remain = insert(request, d)
send_email_security_code(
request, user, remain, 'Welcome new user', 'email-new-user',
'email-new-user.tpl')
data = dict(email=user.email)
ts = _(
'user-added',
default='${email} has been added and the email has been sent.',
mapping=data)
request.session.flash(ts)
return HTTPFound(location=request.route_url('user'))
def edit(self):
request = self.request
q = DBSession.query(User).filter_by(id=request.matchdict['id']) q = DBSession.query(User).filter_by(id=request.matchdict['id'])
user = q.first() user = q.first()
if not user: if not user:
...@@ -323,12 +301,14 @@ def view_edit(request): ...@@ -323,12 +301,14 @@ def view_edit(request):
schema = my_edit_schema schema = my_edit_schema
else: else:
schema = edit_schema schema = edit_schema
form = get_form(request, schema, user) form = self.get_form(schema, user)
resp = dict(title=_('Edit user')) resp = dict(title=_('Edit user'))
if not request.POST: if not request.POST:
d = user.to_dict() d = user.to_dict()
d['groups'] = user_group_set(user) d['groups'] = user_group_set(user)
resp['form'] = form.render(appstruct=none_to_null(d)) d = none_to_null(d)
form.set_appstruct(d)
resp['form'] = self.render(form)
return resp return resp
if 'save' not in request.POST: if 'save' not in request.POST:
return HTTPFound(location=request.route_url('user')) return HTTPFound(location=request.route_url('user'))
...@@ -338,27 +318,22 @@ def view_edit(request): ...@@ -338,27 +318,22 @@ def view_edit(request):
except ValidationFailure: except ValidationFailure:
resp['form'] = form.render() resp['form'] = form.render()
return resp return resp
update(request, user, null_to_none(dict(c.items()))) d = null_to_none(dict(c.items()))
update(request, user, d)
data = dict(username=user.user_name) data = dict(username=user.user_name)
ts = _('user-updated', default='${username} profile updated', mapping=data) ts = _(
'user-updated', default='${username} profile updated',
mapping=data)
request.session.flash(ts) request.session.flash(ts)
return HTTPFound(location=request.route_url('user')) return HTTPFound(location=request.route_url('user'))
def delete(self):
########## request = self.request
# Delete #
##########
@view_config(
route_name='user-delete', renderer='templates/user/delete.pt',
permission='user-edit')
def view_delete(request):
q = DBSession.query(User).filter_by(id=request.matchdict['id']) q = DBSession.query(User).filter_by(id=request.matchdict['id'])
user = q.first() user = q.first()
if not user: if not user:
return HTTPNotFound() return HTTPNotFound()
if not request.POST: if not request.POST:
btn_delete = Button('delete', _('Delete'))
btn_cancel = Button('cancel', _('Cancel'))
buttons = (btn_delete, btn_cancel) buttons = (btn_delete, btn_cancel)
form = Form(colander.Schema(), buttons=buttons) form = Form(colander.Schema(), buttons=buttons)
return dict(title=_('Delete user'), user=user, form=form.render()) return dict(title=_('Delete user'), user=user, form=form.render())
...@@ -375,3 +350,56 @@ def view_delete(request): ...@@ -375,3 +350,56 @@ def view_delete(request):
q.delete() q.delete()
request.session.flash(ts) request.session.flash(ts)
return HTTPFound(location=request.route_url('user')) return HTTPFound(location=request.route_url('user'))
def list(self):
request = self.request
form = get_filter_form()
resp = dict(title=_('Users'))
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())
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
class View(BaseView):
@view_config(
route_name='user-add', renderer='templates/user/add.pt',
permission='user-edit')
def add(self):
return super().add()
@view_config(
route_name='user-edit', renderer='templates/user/edit.pt',
permission='user-edit')
def edit(self):
return super().edit()
@view_config(
route_name='user-delete', renderer='templates/user/delete.pt',
permission='user-edit')
def delete(self):
return super().delete()
@view_config(
route_name='user', renderer='templates/user/list.pt',
permission='user-edit')
def list(self):
return super().list()
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!