refactor users views etc.

This commit is contained in:
Lance Edgar 2012-07-14 10:40:08 -05:00
parent 4c2319b759
commit b3b4e40bcf
9 changed files with 192 additions and 252 deletions

View file

@ -13,7 +13,6 @@
div.crud { div.crud {
font-size: 10pt; font-size: 10pt;
margin: auto; margin: auto;
width: 950px;
} }
@ -41,7 +40,7 @@ div.crud div.field-couple {
div.crud div.field-couple label { div.crud div.field-couple label {
display: block; display: block;
float: left; float: left;
width: 135px; width: 140px;
font-weight: bold; font-weight: bold;
margin-top: 2px; margin-top: 2px;
white-space: nowrap; white-space: nowrap;

View file

@ -3,9 +3,9 @@
<%def name="title()">People</%def> <%def name="title()">People</%def>
<%def name="menu()"> <%def name="context_menu_items()">
% if request.has_perm('people.create'): % if request.has_perm('people.create'):
<p>${h.link_to("Create a new Person", url('person.new'))}</p> <li>${h.link_to("Create a new Person", url('person.new'))}</li>
% endif % endif
</%def> </%def>

View file

@ -3,8 +3,8 @@
<%def name="title()">Roles</%def> <%def name="title()">Roles</%def>
<%def name="menu()"> <%def name="context_menu_items()">
<p>${h.link_to("Create a new Role", url('role.new'))}</p> <li>${h.link_to("Create a new Role", url('role.new'))}</li>
</%def> </%def>
${parent.body()} ${parent.body()}

View file

@ -3,8 +3,8 @@
<%def name="crud_name()">User</%def> <%def name="crud_name()">User</%def>
<%def name="menu()"> <%def name="context_menu_items()">
<p>${h.link_to("Back to Users", url('users.list'))}</p> <li>${h.link_to("Back to Users", url('users.list'))}</li>
</%def> </%def>
${parent.body()} ${parent.body()}

View file

@ -0,0 +1,2 @@
<%inherit file="/users/crud.mako" />
${parent.body()}

View file

@ -3,8 +3,8 @@
<%def name="title()">Users</%def> <%def name="title()">Users</%def>
<%def name="menu()"> <%def name="context_menu_items()">
<p>${h.link_to("Create a new User", url('user.new'))}</p> <li>${h.link_to("Create a new User", url('user.new'))}</li>
</%def> </%def>
${parent.body()} ${parent.body()}

View file

@ -0,0 +1,2 @@
<%inherit file="/users/crud.mako" />
${parent.body()}

View file

@ -28,8 +28,8 @@
# from pyramid.renderers import render_to_response # from pyramid.renderers import render_to_response
# from pyramid.httpexceptions import HTTPException, HTTPFound, HTTPOk, HTTPUnauthorized # from pyramid.httpexceptions import HTTPException, HTTPFound, HTTPOk, HTTPUnauthorized
import transaction # import transaction
from pyramid.httpexceptions import HTTPFound, HTTPException from pyramid.httpexceptions import HTTPFound
# import sqlahelper # import sqlahelper
@ -97,6 +97,9 @@ class Crud(object):
def post_sync(self, fs): def post_sync(self, fs):
pass pass
def validation_failed(self, fs):
pass
def crud(self, obj=None): def crud(self, obj=None):
if obj is None: if obj is None:
obj = self.mapped_class obj = self.mapped_class
@ -126,7 +129,6 @@ class Crud(object):
result = None result = None
with transaction.manager:
fs.sync() fs.sync()
result = self.post_sync(fs) result = self.post_sync(fs)
if not result: if not result:
@ -144,6 +146,8 @@ class Crud(object):
return HTTPFound(location=self.home_url) return HTTPFound(location=self.home_url)
self.validation_failed(fs)
# TODO: This probably needs attention. # TODO: This probably needs attention.
if not fs.edit: if not fs.edit:
fs.allow_continue = True fs.allow_continue = True
@ -163,7 +167,6 @@ class Crud(object):
uuid = self.request.matchdict['uuid'] uuid = self.request.matchdict['uuid']
obj = Session.query(self.mapped_class).get(uuid) if uuid else None obj = Session.query(self.mapped_class).get(uuid) if uuid else None
assert obj assert obj
with transaction.manager:
Session.delete(obj) Session.delete(obj)
return HTTPFound(location=self.home_url) return HTTPFound(location=self.home_url)
@ -201,109 +204,109 @@ class Crud(object):
permission=kw['permission'], http_cache=0) permission=kw['permission'], http_cache=0)
def crud(request, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None): # def crud(request, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None):
""" # """
Adds a common CRUD mechanism for objects. # Adds a common CRUD mechanism for objects.
``cls`` should be a SQLAlchemy-mapped class, presumably deriving from # ``cls`` should be a SQLAlchemy-mapped class, presumably deriving from
:class:`edbob.Object`. # :class:`edbob.Object`.
``fieldset_factory`` must be a callable which accepts the fieldset's # ``fieldset_factory`` must be a callable which accepts the fieldset's
"model" as its only positional argument. # "model" as its only positional argument.
``home`` will be used as the redirect location once a form is fully # ``home`` will be used as the redirect location once a form is fully
validated and data saved. If you do not speficy this parameter, the # validated and data saved. If you do not speficy this parameter, the
user will be redirected to be the CRUD page for the new object (e.g. so # user will be redirected to be the CRUD page for the new object (e.g. so
an object may be created before certain properties may be edited). # an object may be created before certain properties may be edited).
``delete`` may either be a string containing a URL to which the user # ``delete`` may either be a string containing a URL to which the user
should be redirected after the object has been deleted, or else a # should be redirected after the object has been deleted, or else a
callback which will be executed *instead of* the normal algorithm # callback which will be executed *instead of* the normal algorithm
(which is merely to delete the object via the Session). # (which is merely to delete the object via the Session).
``post_sync`` may be a callback which will be executed immediately # ``post_sync`` may be a callback which will be executed immediately
after :meth:`FieldSet.sync()` is called, i.e. after validation as well. # after :meth:`FieldSet.sync()` is called, i.e. after validation as well.
``pre_render`` may be a callback which will be executed after any POST # ``pre_render`` may be a callback which will be executed after any POST
processing has occured, but just before rendering. # processing has occured, but just before rendering.
""" # """
uuid = request.params.get('uuid') # uuid = request.params.get('uuid')
obj = Session.query(cls).get(uuid) if uuid else cls # obj = Session.query(cls).get(uuid) if uuid else cls
assert obj # assert obj
if request.params.get('delete'): # if request.params.get('delete'):
if delete: # if delete:
if isinstance(delete, basestring): # if isinstance(delete, basestring):
with transaction.manager: # with transaction.manager:
Session.delete(obj) # Session.delete(obj)
return HTTPFound(location=delete) # return HTTPFound(location=delete)
with transaction.manager: # with transaction.manager:
res = delete(obj) # res = delete(obj)
if res: # if res:
return res # return res
else: # else:
with transaction.manager: # with transaction.manager:
Session.delete(obj) # Session.delete(obj)
if not home: # if not home:
raise ValueError("Must specify 'home' or 'delete' url " # raise ValueError("Must specify 'home' or 'delete' url "
"in call to crud()") # "in call to crud()")
return HTTPFound(location=home) # return HTTPFound(location=home)
fs = fieldset_factory(obj) # fs = fieldset_factory(obj)
# if not fs.readonly and self.request.params.get('fieldset'): # # if not fs.readonly and self.request.params.get('fieldset'):
# fs.rebind(data=self.request.params) # # fs.rebind(data=self.request.params)
# if fs.validate(): # # if fs.validate():
# fs.sync() # # fs.sync()
# if post_sync: # # if post_sync:
# res = post_sync(fs) # # res = post_sync(fs)
# if isinstance(res, HTTPFound): # # if isinstance(res, HTTPFound):
# return res # # return res
# if self.request.params.get('partial'): # # if self.request.params.get('partial'):
# self.Session.flush() # # self.Session.flush()
# return self.json_success(uuid=fs.model.uuid) # # return self.json_success(uuid=fs.model.uuid)
# return HTTPFound(location=self.request.route_url(objects, action='index')) # # return HTTPFound(location=self.request.route_url(objects, action='index'))
if not fs.readonly and request.POST: # if not fs.readonly and request.POST:
fs.rebind(data=request.params) # fs.rebind(data=request.params)
if fs.validate(): # if fs.validate():
with transaction.manager: # with transaction.manager:
fs.sync() # fs.sync()
if post_sync: # if post_sync:
res = post_sync(fs) # res = post_sync(fs)
if res: # if res:
return res # return res
if request.params.get('partial'): # if request.params.get('partial'):
# Session.flush() # # Session.flush()
# return self.json_success(uuid=fs.model.uuid) # # return self.json_success(uuid=fs.model.uuid)
assert False, "need to fix this" # assert False, "need to fix this"
if not home: # if not home:
fs.model = Session.merge(fs.model) # fs.model = Session.merge(fs.model)
home = request.current_route_url(uuid=fs.model.uuid) # home = request.current_route_url(uuid=fs.model.uuid)
request.session.flash("%s \"%s\" has been %s." % ( # request.session.flash("%s \"%s\" has been %s." % (
fs.crud_title, fs.get_display_text(), # fs.crud_title, fs.get_display_text(),
'updated' if fs.edit else 'created')) # 'updated' if fs.edit else 'created'))
return HTTPFound(location=home) # return HTTPFound(location=home)
data = {'fieldset': fs, 'crud': True} # data = {'fieldset': fs, 'crud': True}
if pre_render: # if pre_render:
res = pre_render(fs) # res = pre_render(fs)
if res: # if res:
if isinstance(res, HTTPException): # if isinstance(res, HTTPException):
return res # return res
data.update(res) # data.update(res)
# data = {'fieldset':fs} # # data = {'fieldset':fs}
# if self.request.params.get('partial'): # # if self.request.params.get('partial'):
# return render_to_response('/%s/crud_partial.mako' % objects, # # return render_to_response('/%s/crud_partial.mako' % objects,
# data, request=self.request) # # data, request=self.request)
# return data # # return data
return data # return data
# class needs_perm(object): # class needs_perm(object):

View file

@ -26,78 +26,63 @@
``edbob.pyramid.views.users`` -- User Views ``edbob.pyramid.views.users`` -- User Views
""" """
import transaction from webhelpers.html import literal, tags
from pyramid.httpexceptions import HTTPFound
import formalchemy import formalchemy
from formalchemy.fields import SelectFieldRenderer from formalchemy.fields import SelectFieldRenderer
from webhelpers.html import literal
from webhelpers.html.tags import hidden, link_to, password
import edbob import edbob
from edbob.db.auth import set_user_password from edbob.db.auth import set_user_password
from edbob.pyramid import filters
from edbob.pyramid import forms
from edbob.pyramid import grids
from edbob.pyramid import Session from edbob.pyramid import Session
from edbob.pyramid.filters import filter_ilike
from edbob.pyramid.grids import sorter
from edbob.pyramid.views import GridView
from edbob.pyramid.views.crud import Crud
def filter_map(): class UserGrid(GridView):
return filters.get_filter_map(
edbob.User, mapped_class = edbob.User
route_name = 'users.list'
route_prefix = 'user'
def join_map(self):
return {
'person':
lambda q: q.outerjoin(edbob.Person),
}
def filter_map(self):
return self.make_filter_map(
ilike=['username'], ilike=['username'],
person=filters.filter_ilike(edbob.Person.display_name)) person=filter_ilike(edbob.Person.display_name))
def search_config(request, fmap): def search_config(self, fmap):
return filters.get_search_config( return self.make_search_config(
'users.list', request, fmap, fmap,
include_filter_username=True, include_filter_username=True,
filter_type_username='lk') filter_type_username='lk',
include_filter_person=True,
filter_type_person='lk')
def search_form(config): def grid_config(self, search, fmap):
return filters.get_search_form(config) return self.make_grid_config(search, fmap,
sort='username')
def grid_config(request, search, fmap): def sort_map(self):
return grids.get_grid_config( return self.make_sort_map(
'users.list', request, search, 'username',
filter_map=fmap, sort='username') person=sorter(edbob.Person.display_name))
def sort_map():
return grids.get_sort_map(
edbob.User, ['username'],
person=grids.sorter(edbob.Person.display_name))
def query(config):
jmap = {'person': lambda q: q.outerjoin(edbob.Person)}
smap = sort_map()
q = Session.query(edbob.User)
q = filters.filter_query(q, config, jmap)
q = grids.sort_query(q, config, smap, jmap)
return q
def users(context, request):
fmap = filter_map()
config = search_config(request, fmap)
search = search_form(config)
config = grid_config(request, search, fmap)
users = grids.get_pager(query, config)
g = forms.AlchemyGrid(
edbob.User, users, config,
gridurl=request.route_url('users.list'),
objurl='user.edit')
def grid(self, data, config):
g = self.make_grid(data, config)
g.configure( g.configure(
include=[ include=[
g.username, g.username,
g.person, g.person,
], ],
readonly=True) readonly=True)
return g
grid = g.render(class_='clickable users')
return grids.render_grid(request, grid, search)
class _RolesFieldRenderer(SelectFieldRenderer): class _RolesFieldRenderer(SelectFieldRenderer):
@ -108,7 +93,7 @@ class _RolesFieldRenderer(SelectFieldRenderer):
for uuid in self.value: for uuid in self.value:
role = roles.get(uuid) role = roles.get(uuid)
res += literal('<li>%s</li>' % ( res += literal('<li>%s</li>' % (
link_to(role.name, tags.link_to(role.name,
self.request.route_url('role.edit', uuid=role.uuid)))) self.request.route_url('role.edit', uuid=role.uuid))))
res += literal('</ul>') res += literal('</ul>')
return res return res
@ -146,7 +131,7 @@ class _ProtectedPersonRenderer(formalchemy.FieldRenderer):
def render_readonly(self, **kwargs): def render_readonly(self, **kwargs):
res = str(self.person) res = str(self.person)
res += hidden('User--person_uuid', res += tags.hidden('User--person_uuid',
value=self.field.parent.person_uuid.value) value=self.field.parent.person_uuid.value)
return res return res
@ -161,18 +146,19 @@ def ProtectedPersonRenderer(uuid):
class _LinkedPersonRenderer(formalchemy.FieldRenderer): class _LinkedPersonRenderer(formalchemy.FieldRenderer):
def render_readonly(self, **kwargs): def render_readonly(self, **kwargs):
return link_to(str(self.raw_value), return tags.link_to(str(self.raw_value),
self.request.route_url('person.edit', uuid=self.value)) self.request.route_url('person.edit', uuid=self.value))
def LinkedPersonRenderer(request): def LinkedPersonRenderer(request):
return type('LinkedPersonRenderer', (_LinkedPersonRenderer,), {'request': request}) return type('LinkedPersonRenderer', (_LinkedPersonRenderer,),
{'request': request})
class PasswordFieldRenderer(formalchemy.PasswordFieldRenderer): class PasswordFieldRenderer(formalchemy.PasswordFieldRenderer):
def render(self, **kwargs): def render(self, **kwargs):
return password(self.name, value='', maxlength=self.length, **kwargs) return tags.password(self.name, value='', maxlength=self.length, **kwargs)
def passwords_match(value, field): def passwords_match(value, field):
@ -196,94 +182,42 @@ class PasswordField(formalchemy.Field):
set_user_password(self.model, password) set_user_password(self.model, password)
def user_fieldset(user, request): class UserCrud(Crud):
fs = forms.make_fieldset(user, url=request.route_url,
url_action=request.current_route_url(), mapped_class = edbob.User
route_name='users.list') home_route = 'users.list'
def fieldset(self, user):
fs = self.make_fieldset(user)
fs.append(PasswordField('password')) fs.append(PasswordField('password'))
fs.append(formalchemy.Field('confirm_password', fs.append(formalchemy.Field('confirm_password',
renderer=PasswordFieldRenderer)) renderer=PasswordFieldRenderer))
fs.append(RolesField('roles',
fs.append(RolesField( renderer=RolesFieldRenderer(self.request)))
'roles', renderer=RolesFieldRenderer(request)))
fs.configure( fs.configure(
include=[ include=[
fs.person,
fs.username, fs.username,
fs.person,
fs.password.label("Set Password"), fs.password.label("Set Password"),
fs.confirm_password, fs.confirm_password,
fs.roles, fs.roles,
]) ])
# if fs.edit and user.person:
if isinstance(user, edbob.User) and user.person: if isinstance(user, edbob.User) and user.person:
fs.person.set(readonly=True, fs.person.set(readonly=True,
renderer=LinkedPersonRenderer(request)) renderer=LinkedPersonRenderer(self.request))
return fs return fs
def validation_failed(self, fs):
def new_user(request): if not fs.edit and fs.person_uuid.value:
"""
View for creating a new :class:`edbob.User` instance.
"""
fs = user_fieldset(edbob.User, request)
if request.POST:
fs.rebind(data=request.params)
if fs.validate():
with transaction.manager:
Session.add(fs.model)
fs.sync()
request.session.flash("%s \"%s\" has been %s." % (
fs.crud_title, fs.get_display_text(),
'updated' if fs.edit else 'created'))
home = request.route_url('users.list')
return HTTPFound(location=home)
if fs.person_uuid.value:
fs.person.set(readonly=True, fs.person.set(readonly=True,
renderer=ProtectedPersonRenderer(fs.person_uuid.value)) renderer=ProtectedPersonRenderer(fs.person_uuid.value))
return {'fieldset': fs, 'crud': True}
def edit_user(request):
uuid = request.matchdict['uuid']
user = Session.query(edbob.User).get(uuid) if uuid else None
assert user
fs = user_fieldset(user, request)
if request.POST:
fs.rebind(data=request.params)
if fs.validate():
with transaction.manager:
Session.add(fs.model)
fs.sync()
request.session.flash("%s \"%s\" has been %s." % (
fs.crud_title, fs.get_display_text(),
'updated' if fs.edit else 'created'))
home = request.route_url('users.list')
return HTTPFound(location=home)
return {'fieldset': fs, 'crud': True}
def includeme(config): def includeme(config):
UserGrid.add_route(config, 'users.list', '/users')
config.add_route('users.list', '/users') UserCrud.add_routes(config)
config.add_view(users, route_name='users.list', renderer='/users/index.mako',
permission='users.list', http_cache=0)
config.add_route('user.new', '/users/new')
config.add_view(new_user, route_name='user.new', renderer='/users/user.mako',
permission='users.create', http_cache=0)
config.add_route('user.edit', '/users/{uuid}/edit')
config.add_view(edit_user, route_name='user.edit', renderer='/users/user.mako',
permission='users.edit', http_cache=0)