diff --git a/edbob/pyramid/static/css/crud.css b/edbob/pyramid/static/css/crud.css index ccb9dd3..1422d63 100644 --- a/edbob/pyramid/static/css/crud.css +++ b/edbob/pyramid/static/css/crud.css @@ -13,7 +13,6 @@ div.crud { font-size: 10pt; margin: auto; - width: 950px; } @@ -41,7 +40,7 @@ div.crud div.field-couple { div.crud div.field-couple label { display: block; float: left; - width: 135px; + width: 140px; font-weight: bold; margin-top: 2px; white-space: nowrap; diff --git a/edbob/pyramid/templates/people/index.mako b/edbob/pyramid/templates/people/index.mako index f02a31f..577fedf 100644 --- a/edbob/pyramid/templates/people/index.mako +++ b/edbob/pyramid/templates/people/index.mako @@ -3,9 +3,9 @@ <%def name="title()">People -<%def name="menu()"> +<%def name="context_menu_items()"> % if request.has_perm('people.create'): -

${h.link_to("Create a new Person", url('person.new'))}

+
  • ${h.link_to("Create a new Person", url('person.new'))}
  • % endif diff --git a/edbob/pyramid/templates/roles/index.mako b/edbob/pyramid/templates/roles/index.mako index 597c8f7..773153a 100644 --- a/edbob/pyramid/templates/roles/index.mako +++ b/edbob/pyramid/templates/roles/index.mako @@ -3,8 +3,8 @@ <%def name="title()">Roles -<%def name="menu()"> -

    ${h.link_to("Create a new Role", url('role.new'))}

    +<%def name="context_menu_items()"> +
  • ${h.link_to("Create a new Role", url('role.new'))}
  • ${parent.body()} diff --git a/edbob/pyramid/templates/users/user.mako b/edbob/pyramid/templates/users/crud.mako similarity index 58% rename from edbob/pyramid/templates/users/user.mako rename to edbob/pyramid/templates/users/crud.mako index 73e93e8..c9314e9 100644 --- a/edbob/pyramid/templates/users/user.mako +++ b/edbob/pyramid/templates/users/crud.mako @@ -3,8 +3,8 @@ <%def name="crud_name()">User -<%def name="menu()"> -

    ${h.link_to("Back to Users", url('users.list'))}

    +<%def name="context_menu_items()"> +
  • ${h.link_to("Back to Users", url('users.list'))}
  • ${parent.body()} diff --git a/edbob/pyramid/templates/users/edit.mako b/edbob/pyramid/templates/users/edit.mako new file mode 100644 index 0000000..77fd24c --- /dev/null +++ b/edbob/pyramid/templates/users/edit.mako @@ -0,0 +1,2 @@ +<%inherit file="/users/crud.mako" /> +${parent.body()} diff --git a/edbob/pyramid/templates/users/index.mako b/edbob/pyramid/templates/users/index.mako index 6494aaf..493a309 100644 --- a/edbob/pyramid/templates/users/index.mako +++ b/edbob/pyramid/templates/users/index.mako @@ -3,8 +3,8 @@ <%def name="title()">Users -<%def name="menu()"> -

    ${h.link_to("Create a new User", url('user.new'))}

    +<%def name="context_menu_items()"> +
  • ${h.link_to("Create a new User", url('user.new'))}
  • ${parent.body()} diff --git a/edbob/pyramid/templates/users/new.mako b/edbob/pyramid/templates/users/new.mako new file mode 100644 index 0000000..77fd24c --- /dev/null +++ b/edbob/pyramid/templates/users/new.mako @@ -0,0 +1,2 @@ +<%inherit file="/users/crud.mako" /> +${parent.body()} diff --git a/edbob/pyramid/views/crud.py b/edbob/pyramid/views/crud.py index 0c9abfc..3b34e94 100644 --- a/edbob/pyramid/views/crud.py +++ b/edbob/pyramid/views/crud.py @@ -28,8 +28,8 @@ # from pyramid.renderers import render_to_response # from pyramid.httpexceptions import HTTPException, HTTPFound, HTTPOk, HTTPUnauthorized -import transaction -from pyramid.httpexceptions import HTTPFound, HTTPException +# import transaction +from pyramid.httpexceptions import HTTPFound # import sqlahelper @@ -97,6 +97,9 @@ class Crud(object): def post_sync(self, fs): pass + def validation_failed(self, fs): + pass + def crud(self, obj=None): if obj is None: obj = self.mapped_class @@ -126,15 +129,14 @@ class Crud(object): result = None - with transaction.manager: - fs.sync() - result = self.post_sync(fs) - if not result: - Session.add(fs.model) - Session.flush() - self.request.session.flash('%s "%s" has been %s.' % ( - fs.crud_title, fs.get_display_text(), - 'updated' if fs.edit else 'created')) + fs.sync() + result = self.post_sync(fs) + if not result: + Session.add(fs.model) + Session.flush() + self.request.session.flash('%s "%s" has been %s.' % ( + fs.crud_title, fs.get_display_text(), + 'updated' if fs.edit else 'created')) if result: return result @@ -144,6 +146,8 @@ class Crud(object): return HTTPFound(location=self.home_url) + self.validation_failed(fs) + # TODO: This probably needs attention. if not fs.edit: fs.allow_continue = True @@ -163,8 +167,7 @@ class Crud(object): uuid = self.request.matchdict['uuid'] obj = Session.query(self.mapped_class).get(uuid) if uuid else None assert obj - with transaction.manager: - Session.delete(obj) + Session.delete(obj) return HTTPFound(location=self.home_url) @classmethod @@ -201,109 +204,109 @@ class Crud(object): permission=kw['permission'], http_cache=0) -def crud(request, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None): - """ - Adds a common CRUD mechanism for objects. +# def crud(request, cls, fieldset_factory, home=None, delete=None, post_sync=None, pre_render=None): +# """ +# Adds a common CRUD mechanism for objects. - ``cls`` should be a SQLAlchemy-mapped class, presumably deriving from - :class:`edbob.Object`. +# ``cls`` should be a SQLAlchemy-mapped class, presumably deriving from +# :class:`edbob.Object`. - ``fieldset_factory`` must be a callable which accepts the fieldset's - "model" as its only positional argument. +# ``fieldset_factory`` must be a callable which accepts the fieldset's +# "model" as its only positional argument. - ``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 - 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). +# ``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 +# 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). - ``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 - callback which will be executed *instead of* the normal algorithm - (which is merely to delete the object via the Session). +# ``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 +# callback which will be executed *instead of* the normal algorithm +# (which is merely to delete the object via the Session). - ``post_sync`` may be a callback which will be executed immediately - after :meth:`FieldSet.sync()` is called, i.e. after validation as well. +# ``post_sync`` may be a callback which will be executed immediately +# 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 - processing has occured, but just before rendering. - """ +# ``pre_render`` may be a callback which will be executed after any POST +# processing has occured, but just before rendering. +# """ - uuid = request.params.get('uuid') - obj = Session.query(cls).get(uuid) if uuid else cls - assert obj +# uuid = request.params.get('uuid') +# obj = Session.query(cls).get(uuid) if uuid else cls +# assert obj - if request.params.get('delete'): - if delete: - if isinstance(delete, basestring): - with transaction.manager: - Session.delete(obj) - return HTTPFound(location=delete) - with transaction.manager: - res = delete(obj) - if res: - return res - else: - with transaction.manager: - Session.delete(obj) - if not home: - raise ValueError("Must specify 'home' or 'delete' url " - "in call to crud()") - return HTTPFound(location=home) +# if request.params.get('delete'): +# if delete: +# if isinstance(delete, basestring): +# with transaction.manager: +# Session.delete(obj) +# return HTTPFound(location=delete) +# with transaction.manager: +# res = delete(obj) +# if res: +# return res +# else: +# with transaction.manager: +# Session.delete(obj) +# if not home: +# raise ValueError("Must specify 'home' or 'delete' url " +# "in call to crud()") +# return HTTPFound(location=home) - fs = fieldset_factory(obj) +# fs = fieldset_factory(obj) - # if not fs.readonly and self.request.params.get('fieldset'): - # fs.rebind(data=self.request.params) - # if fs.validate(): - # fs.sync() - # if post_sync: - # res = post_sync(fs) - # if isinstance(res, HTTPFound): - # return res - # if self.request.params.get('partial'): - # self.Session.flush() - # return self.json_success(uuid=fs.model.uuid) - # return HTTPFound(location=self.request.route_url(objects, action='index')) +# # if not fs.readonly and self.request.params.get('fieldset'): +# # fs.rebind(data=self.request.params) +# # if fs.validate(): +# # fs.sync() +# # if post_sync: +# # res = post_sync(fs) +# # if isinstance(res, HTTPFound): +# # return res +# # if self.request.params.get('partial'): +# # self.Session.flush() +# # return self.json_success(uuid=fs.model.uuid) +# # return HTTPFound(location=self.request.route_url(objects, action='index')) - if not fs.readonly and request.POST: - fs.rebind(data=request.params) - if fs.validate(): - with transaction.manager: - fs.sync() - if post_sync: - res = post_sync(fs) - if res: - return res +# if not fs.readonly and request.POST: +# fs.rebind(data=request.params) +# if fs.validate(): +# with transaction.manager: +# fs.sync() +# if post_sync: +# res = post_sync(fs) +# if res: +# return res - if request.params.get('partial'): - # Session.flush() - # return self.json_success(uuid=fs.model.uuid) - assert False, "need to fix this" +# if request.params.get('partial'): +# # Session.flush() +# # return self.json_success(uuid=fs.model.uuid) +# assert False, "need to fix this" - if not home: - fs.model = Session.merge(fs.model) - home = request.current_route_url(uuid=fs.model.uuid) - request.session.flash("%s \"%s\" has been %s." % ( - fs.crud_title, fs.get_display_text(), - 'updated' if fs.edit else 'created')) - return HTTPFound(location=home) +# if not home: +# fs.model = Session.merge(fs.model) +# home = request.current_route_url(uuid=fs.model.uuid) +# request.session.flash("%s \"%s\" has been %s." % ( +# fs.crud_title, fs.get_display_text(), +# 'updated' if fs.edit else 'created')) +# return HTTPFound(location=home) - data = {'fieldset': fs, 'crud': True} +# data = {'fieldset': fs, 'crud': True} - if pre_render: - res = pre_render(fs) - if res: - if isinstance(res, HTTPException): - return res - data.update(res) +# if pre_render: +# res = pre_render(fs) +# if res: +# if isinstance(res, HTTPException): +# return res +# data.update(res) - # data = {'fieldset':fs} - # if self.request.params.get('partial'): - # return render_to_response('/%s/crud_partial.mako' % objects, - # data, request=self.request) - # return data +# # data = {'fieldset':fs} +# # if self.request.params.get('partial'): +# # return render_to_response('/%s/crud_partial.mako' % objects, +# # data, request=self.request) +# # return data - return data +# return data # class needs_perm(object): diff --git a/edbob/pyramid/views/users.py b/edbob/pyramid/views/users.py index ff647e8..68f0551 100644 --- a/edbob/pyramid/views/users.py +++ b/edbob/pyramid/views/users.py @@ -26,78 +26,63 @@ ``edbob.pyramid.views.users`` -- User Views """ -import transaction -from pyramid.httpexceptions import HTTPFound +from webhelpers.html import literal, tags import formalchemy from formalchemy.fields import SelectFieldRenderer -from webhelpers.html import literal -from webhelpers.html.tags import hidden, link_to, password import edbob 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.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(): - return filters.get_filter_map( - edbob.User, - ilike=['username'], - person=filters.filter_ilike(edbob.Person.display_name)) +class UserGrid(GridView): -def search_config(request, fmap): - return filters.get_search_config( - 'users.list', request, fmap, - include_filter_username=True, - filter_type_username='lk') + mapped_class = edbob.User + route_name = 'users.list' + route_prefix = 'user' -def search_form(config): - return filters.get_search_form(config) + def join_map(self): + return { + 'person': + lambda q: q.outerjoin(edbob.Person), + } -def grid_config(request, search, fmap): - return grids.get_grid_config( - 'users.list', request, search, - filter_map=fmap, sort='username') + def filter_map(self): + return self.make_filter_map( + ilike=['username'], + person=filter_ilike(edbob.Person.display_name)) -def sort_map(): - return grids.get_sort_map( - edbob.User, ['username'], - person=grids.sorter(edbob.Person.display_name)) + def search_config(self, fmap): + return self.make_search_config( + fmap, + include_filter_username=True, + filter_type_username='lk', + include_filter_person=True, + filter_type_person='lk') -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 grid_config(self, search, fmap): + return self.make_grid_config(search, fmap, + sort='username') + def sort_map(self): + return self.make_sort_map( + 'username', + person=sorter(edbob.Person.display_name)) -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') - - g.configure( - include=[ - g.username, - g.person, - ], - readonly=True) - - grid = g.render(class_='clickable users') - return grids.render_grid(request, grid, search) + def grid(self, data, config): + g = self.make_grid(data, config) + g.configure( + include=[ + g.username, + g.person, + ], + readonly=True) + return g class _RolesFieldRenderer(SelectFieldRenderer): @@ -108,8 +93,8 @@ class _RolesFieldRenderer(SelectFieldRenderer): for uuid in self.value: role = roles.get(uuid) res += literal('
  • %s
  • ' % ( - link_to(role.name, - self.request.route_url('role.edit', uuid=role.uuid)))) + tags.link_to(role.name, + self.request.route_url('role.edit', uuid=role.uuid)))) res += literal('') return res @@ -146,8 +131,8 @@ class _ProtectedPersonRenderer(formalchemy.FieldRenderer): def render_readonly(self, **kwargs): res = str(self.person) - res += hidden('User--person_uuid', - value=self.field.parent.person_uuid.value) + res += tags.hidden('User--person_uuid', + value=self.field.parent.person_uuid.value) return res @@ -161,18 +146,19 @@ def ProtectedPersonRenderer(uuid): class _LinkedPersonRenderer(formalchemy.FieldRenderer): def render_readonly(self, **kwargs): - return link_to(str(self.raw_value), - self.request.route_url('person.edit', uuid=self.value)) + return tags.link_to(str(self.raw_value), + self.request.route_url('person.edit', uuid=self.value)) def LinkedPersonRenderer(request): - return type('LinkedPersonRenderer', (_LinkedPersonRenderer,), {'request': request}) + return type('LinkedPersonRenderer', (_LinkedPersonRenderer,), + {'request': request}) class PasswordFieldRenderer(formalchemy.PasswordFieldRenderer): 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): @@ -196,94 +182,42 @@ class PasswordField(formalchemy.Field): set_user_password(self.model, password) -def user_fieldset(user, request): - fs = forms.make_fieldset(user, url=request.route_url, - url_action=request.current_route_url(), - route_name='users.list') +class UserCrud(Crud): - fs.append(PasswordField('password')) - fs.append(formalchemy.Field('confirm_password', - renderer=PasswordFieldRenderer)) + mapped_class = edbob.User + home_route = 'users.list' - fs.append(RolesField( - 'roles', renderer=RolesFieldRenderer(request))) + def fieldset(self, user): + fs = self.make_fieldset(user) - fs.configure( - include=[ - fs.person, - fs.username, - fs.password.label("Set Password"), - fs.confirm_password, - fs.roles, - ]) + fs.append(PasswordField('password')) + fs.append(formalchemy.Field('confirm_password', + renderer=PasswordFieldRenderer)) + fs.append(RolesField('roles', + renderer=RolesFieldRenderer(self.request))) - if isinstance(user, edbob.User) and user.person: - fs.person.set(readonly=True, - renderer=LinkedPersonRenderer(request)) + fs.configure( + include=[ + fs.username, + fs.person, + fs.password.label("Set Password"), + fs.confirm_password, + fs.roles, + ]) - return fs + # if fs.edit and user.person: + if isinstance(user, edbob.User) and user.person: + fs.person.set(readonly=True, + renderer=LinkedPersonRenderer(self.request)) + return fs -def new_user(request): - """ - 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: + def validation_failed(self, fs): + if not fs.edit and fs.person_uuid.value: fs.person.set(readonly=True, 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): - - config.add_route('users.list', '/users') - 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) + UserGrid.add_route(config, 'users.list', '/users') + UserCrud.add_routes(config)