Convert User pages to use master view.

And of course make some more tweaks to new grids etc.
This commit is contained in:
Lance Edgar 2015-08-11 23:19:41 -05:00
parent 9cfbc918e7
commit af07f477dc
11 changed files with 269 additions and 289 deletions

View file

@ -29,6 +29,8 @@ from __future__ import unicode_literals
import os
import logging
import sqlalchemy as sa
import edbob
from edbob.pyramid.forms.formalchemy import TemplateEngine
@ -131,6 +133,7 @@ def make_pyramid_config(settings):
# Configure FormAlchemy.
formalchemy.config.engine = TemplateEngine()
formalchemy.FieldSet.default_renderers[sa.Boolean] = renderers.YesNoFieldRenderer
formalchemy.FieldSet.default_renderers[GPCType] = renderers.GPCFieldRenderer
return config

View file

@ -78,26 +78,22 @@ class AlchemyGrid(Grid):
filtrs = filters.GridFilterSet()
mapper = orm.class_mapper(self.model_class)
for prop in mapper.iterate_properties:
if isinstance(prop, orm.ColumnProperty) and prop.key != 'uuid':
filtrs[prop.key] = self.make_filter(prop)
if isinstance(prop, orm.ColumnProperty) and not prop.key.endswith('uuid'):
filtrs[prop.key] = self.make_filter(prop.key, prop.columns[0])
return filtrs
def make_filter(self, model_property, **kwargs):
def make_filter(self, key, column, **kwargs):
"""
Make a filter suitable for use with the given model property.
Make a filter suitable for use with the given column.
"""
if len(model_property.columns) > 1:
log.debug("ignoring secondary columns for sake of type detection")
coltype = model_property.columns[0].type
factory = filters.AlchemyGridFilter
if isinstance(coltype, sa.String):
if isinstance(column.type, sa.String):
factory = filters.AlchemyStringFilter
elif isinstance(coltype, sa.Numeric):
elif isinstance(column.type, sa.Numeric):
factory = filters.AlchemyNumericFilter
kwargs['model_property'] = model_property
return factory(model_property.key, **kwargs)
elif isinstance(column.type, sa.Boolean):
factory = filters.AlchemyBooleanFilter
return factory(key, column=column, **kwargs)
def iter_filters(self):
"""
@ -112,19 +108,20 @@ class AlchemyGrid(Grid):
"""
sorters, updates = {}, sorters
mapper = orm.class_mapper(self.model_class)
for key, column in mapper.columns.items():
if key != 'uuid':
sorters[key] = self.make_sorter(column)
for prop in mapper.iterate_properties:
if isinstance(prop, orm.ColumnProperty) and not prop.key.endswith('uuid'):
sorters[prop.key] = self.make_sorter(prop)
if updates:
sorters.update(updates)
return sorters
def make_sorter(self, field):
def make_sorter(self, model_property):
"""
Returns a function suitable for a sort map callable, with typical logic
built in for sorting applied to ``field``.
"""
return lambda q, d: q.order_by(getattr(field, d)())
column = getattr(self.model_class, model_property.key)
return lambda q, d: q.order_by(getattr(column, d)())
def load_settings(self):
"""
@ -136,23 +133,6 @@ class AlchemyGrid(Grid):
self._fa_grid.rebind(self.make_visible_data(), session=Session(),
request=self.request)
def sort_data(self, query):
"""
Sort the given query according to current settings, and return the result.
"""
# Cannot sort unless we know which column to sort by.
if not self.sortkey:
return query
# Cannot sort unless we have a sort function.
sortfunc = self.sorters.get(self.sortkey)
if not sortfunc:
return query
# We can provide a default sort direction though.
sortdir = getattr(self, 'sortdir', 'asc')
return sortfunc(query, sortdir)
def paginate_data(self, query):
"""
Paginate the given data set according to current settings, and return
@ -170,7 +150,7 @@ class AlchemyGrid(Grid):
for field in self._fa_grid.render_fields.itervalues():
column = GridColumn()
column.field = field
column.key = field.name
column.key = field.key
column.label = field.label()
yield column

View file

@ -39,7 +39,7 @@ class Grid(object):
"""
def __init__(self, key, request, columns=[], data=[], main_actions=[], more_actions=[],
filterable=False, filters={},
joiners={}, filterable=False, filters={},
sortable=False, sorters={}, default_sortkey=None, default_sortdir='asc',
pageable=False, default_pagesize=20, default_page=1,
width='auto', checkboxes=False, **kwargs):
@ -49,6 +49,7 @@ class Grid(object):
self.data = data
self.main_actions = main_actions
self.more_actions = more_actions
self.joiners = joiners
# Set extra attributes first, in case other init logic depends on any
# of them (i.e. in subclasses).
@ -378,16 +379,32 @@ class Grid(object):
Filter and return the given data set, according to current settings.
"""
for filtr in self.iter_active_filters():
if filtr.key in self.joiners and filtr.key not in self.joined:
data = self.joiners[filtr.key](data)
self.joined.add(filtr.key)
data = filtr.filter(data)
return data
def sort_data(self, data):
"""
Sort the given data set according to current settings, and return the
result. Note that the default implementation does nothing.
Sort the given query according to current settings, and return the result.
"""
# Cannot sort unless we know which column to sort by.
if not self.sortkey:
return data
# Cannot sort unless we have a sort function.
sortfunc = self.sorters.get(self.sortkey)
if not sortfunc:
return data
# We can provide a default sort direction though.
sortdir = getattr(self, 'sortdir', 'asc')
if self.sortkey in self.joiners and self.sortkey not in self.joined:
data = self.joiners[self.sortkey](data)
self.joined.add(self.sortkey)
return sortfunc(data, sortdir)
def paginate_data(self, data):
"""
Paginate the given data set according to current settings, and return
@ -401,6 +418,7 @@ class Grid(object):
set. This will page / sort / filter as necessary, according to the
grid's defaults and the current request etc.
"""
self.joined = set()
data = self.data
if self.filterable:
data = self.filter_data(data)

View file

@ -70,6 +70,7 @@ class GridFilter(object):
'filters' section when rendering the index page template.
"""
verbmap = {
'is_any': "is any",
'equal': "equal to",
'not_equal': "not equal to",
'greater_than': "greater than",
@ -78,6 +79,8 @@ class GridFilter(object):
'less_equal': "less than or equal to",
'is_null': "is null",
'is_not_null': "is not null",
'is_true': "is true",
'is_false': "is false",
'contains': "contains",
'does_not_contain': "does not contain",
}
@ -86,7 +89,7 @@ class GridFilter(object):
default_active=False, default_verb=None, default_value=None):
self.key = key
self.label = label or prettify(key)
self.verbs = verbs or self.default_verbs()
self.verbs = verbs or self.get_default_verbs()
self.renderer = renderer or DefaultRenderer()
self.renderer.filter = self
self.default_active = default_active
@ -96,11 +99,16 @@ class GridFilter(object):
def __repr__(self):
return "GridFilter({0})".format(repr(self.key))
def default_verbs(self):
def get_default_verbs(self):
"""
Returns the set of verbs which will be used by default, i.e. unless
overridden by constructor args etc.
"""
verbs = getattr(self, 'default_verbs', None)
if verbs:
if callable(verbs):
return verbs()
return verbs
return ['equal', 'not_equal', 'is_null', 'is_not_null']
def filter(self, data, verb=None, value=UNSPECIFIED):
@ -116,6 +124,14 @@ class GridFilter(object):
raise ValueError("Unknown filter verb: {0}".format(repr(verb)))
return filtr(data, value)
def filter_is_any(self, data, value):
"""
Special no-op filter which does no actual filtering. Useful in some
cases to add an "ineffective" option to the verb list for a given grid
filter.
"""
return data
def render(self, **kwargs):
kwargs['filter'] = self
return self.renderer.render(**kwargs)
@ -127,9 +143,7 @@ class AlchemyGridFilter(GridFilter):
"""
def __init__(self, *args, **kwargs):
self.model_property = kwargs.pop('model_property')
self.model_class = self.model_property.parent.class_
self.model_column = getattr(self.model_class, self.model_property.key)
self.column = kwargs.pop('column')
super(AlchemyGridFilter, self).__init__(*args, **kwargs)
def filter_equal(self, query, value):
@ -138,7 +152,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column == value)
return query.filter(self.column == value)
def filter_not_equal(self, query, value):
"""
@ -150,8 +164,8 @@ class AlchemyGridFilter(GridFilter):
# When saying something is 'not equal' to something else, we must also
# include things which are nothing at all, in our result set.
return query.filter(sa.or_(
self.model_column == None,
self.model_column != value,
self.column == None,
self.column != value,
))
def filter_is_null(self, query, value):
@ -159,14 +173,14 @@ class AlchemyGridFilter(GridFilter):
Filter data with an 'IS NULL' query. Note that this filter does not
use the value for anything.
"""
return query.filter(self.model_column == None)
return query.filter(self.column == None)
def filter_is_not_null(self, query, value):
"""
Filter data with an 'IS NOT NULL' query. Note that this filter does
not use the value for anything.
"""
return query.filter(self.model_column != None)
return query.filter(self.column != None)
def filter_greater_than(self, query, value):
"""
@ -174,7 +188,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column > value)
return query.filter(self.column > value)
def filter_greater_equal(self, query, value):
"""
@ -182,7 +196,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column >= value)
return query.filter(self.column >= value)
def filter_less_than(self, query, value):
"""
@ -190,7 +204,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column < value)
return query.filter(self.column < value)
def filter_less_equal(self, query, value):
"""
@ -198,7 +212,7 @@ class AlchemyGridFilter(GridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column <= value)
return query.filter(self.column <= value)
class AlchemyStringFilter(AlchemyGridFilter):
@ -219,7 +233,7 @@ class AlchemyStringFilter(AlchemyGridFilter):
"""
if value is None or value == '':
return query
return query.filter(self.model_column.ilike('%{0}%'.format(value)))
return query.filter(self.column.ilike('%{0}%'.format(value)))
def filter_does_not_contain(self, query, value):
"""
@ -231,8 +245,8 @@ class AlchemyStringFilter(AlchemyGridFilter):
# When saying something is 'not like' something else, we must also
# include things which are nothing at all, in our result set.
return query.filter(sa.or_(
self.model_column == None,
~self.model_column.ilike('%{0}%'.format(value)),
self.column == None,
~self.column.ilike('%{0}%'.format(value)),
))
@ -249,6 +263,27 @@ class AlchemyNumericFilter(AlchemyGridFilter):
'less_than', 'less_equal', 'is_null', 'is_not_null']
class AlchemyBooleanFilter(AlchemyGridFilter):
"""
Boolean filter for SQLAlchemy.
"""
default_verbs = ['is_true', 'is_false', 'is_null', 'is_not_null', 'is_any']
def filter_is_true(self, query, value):
"""
Filter data with an "is true" query. Note that this filter does not
use the value for anything.
"""
return query.filter(self.column == True)
def filter_is_false(self, query, value):
"""
Filter data with an "is false" query. Note that this filter does not
use the value for anything.
"""
return query.filter(self.column == False)
class GridFilterSet(OrderedDict):
"""
Collection class for :class:`GridFilter` instances.

View file

@ -1,16 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Users", url('users'))}</li>
% if form.readonly:
<li>${h.link_to("Edit this User", url('user.update', uuid=form.fieldset.model.uuid))}</li>
% elif form.updating:
<li>${h.link_to("View this User", url('user.read', uuid=form.fieldset.model.uuid))}</li>
% endif
% if version_count is not Undefined and request.has_perm('user.versions.view'):
<li>${h.link_to("View Change History ({0})".format(version_count), url('user.versions', uuid=form.fieldset.model.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,11 @@
## -*- coding: utf-8 -*-
<%inherit file="/master/edit.mako" />
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if version_count is not Undefined and request.has_perm('user.versions.view'):
<li>${h.link_to("View Change History ({0})".format(version_count), url('user.versions', uuid=instance.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,12 +0,0 @@
## -*- coding: utf-8 -*-
<%inherit file="/grid.mako" />
<%def name="title()">Users</%def>
<%def name="context_menu_items()">
% if request.has_perm('users.create'):
<li>${h.link_to("Create a new User", url('user.create'))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,11 @@
## -*- coding: utf-8 -*-
<%inherit file="/master/view.mako" />
<%def name="context_menu_items()">
${parent.context_menu_items()}
% if version_count is not Undefined and request.has_perm('user.versions.view'):
<li>${h.link_to("View Change History ({0})".format(version_count), url('user.versions', uuid=instance.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -45,6 +45,10 @@ class MasterView(View):
"""
Base "master" view class. All model master views should derive from this.
"""
creating = False
viewing = False
editing = False
deleting = False
##############################
# Available Views
@ -69,7 +73,8 @@ class MasterView(View):
"""
View for creating a new model record.
"""
form = self.make_form(self.model_class, creating=True)
self.creating = True
form = self.make_form(self.model_class)
if self.request.method == 'POST':
if form.validate():
form.save()
@ -83,11 +88,9 @@ class MasterView(View):
"""
View for viewing details of an existing model record.
"""
key = self.request.matchdict[self.get_model_key()]
instance = Session.query(self.model_class).get(key)
if not instance:
return HTTPNotFound()
form = self.make_form(instance, readonly=True)
self.viewing = True
instance = self.get_instance()
form = self.make_form(instance)
return self.render_to_response('view', {
'instance': instance, 'form': form})
@ -95,11 +98,9 @@ class MasterView(View):
"""
View for editing an existing model record.
"""
key = self.request.matchdict[self.get_model_key()]
instance = Session.query(self.model_class).get(key)
if not instance:
return HTTPNotFound()
form = self.make_form(instance, editing=True)
self.editing = True
instance = self.get_instance()
form = self.make_form(instance)
if self.request.method == 'POST':
if form.validate():
form.save()
@ -112,10 +113,8 @@ class MasterView(View):
"""
View for deleting an existing model record.
"""
key = self.request.matchdict[self.get_model_key()]
instance = Session.query(self.model_class).get(key)
if not instance:
return HTTPNotFound()
self.deleting = True
instance = self.get_instance()
# Let derived classes prep for (or cancel) deletion.
result = self.before_delete(instance)
@ -253,6 +252,12 @@ class MasterView(View):
return render_to_response('/master/{0}.mako'.format(template),
data, request=self.request)
def template_kwargs(self, **kwargs):
"""
Supplement the template context, for all views.
"""
return kwargs
def redirect(self, url):
"""
Convenience method to return a HTTP 302 response.
@ -362,29 +367,40 @@ class MasterView(View):
def make_query(self, session=None):
"""
Make the base query to be used for the grid. Note that this query will
have been prefiltered but otherwise will be "pure". The user's filter
selections etc. are later applied to this query.
Make the base query to be used for the grid. Subclasses should not
override this method; override :meth:`query()` instead.
"""
if session is None:
session = Session()
query = session.query(self.model_class)
return self.prefilter_query(query)
return self.query(session)
def prefilter_query(self, query):
def query(self, session):
"""
Apply any sort of pre-filtering to the grid query, as necessary. This
is useful if say, you don't ever want to show records of a certain type
to non-admin users. You would use a "prefilter" to hide what you
wanted, regardless of the user's filter selections.
Produce the initial/base query for the master grid. By default this is
simply a query against the model class, but you may override as
necessary to apply any sort of pre-filtering etc. This is useful if
say, you don't ever want to show records of a certain type to non-admin
users. You would modify the base query to hide what you wanted,
regardless of the user's filter selections.
"""
return query
return session.query(self.model_class)
##############################
# CRUD Stuff
##############################
def get_instance(self):
"""
Fetch the current model instance by inspecting the route kwargs and
doing a database lookup. If the instance cannot be found, raises 404.
"""
key = self.request.matchdict[self.get_model_key()]
instance = Session.query(self.model_class).get(key)
if not instance:
raise HTTPNotFound()
return instance
def make_form(self, instance, **kwargs):
"""
Make a FormAlchemy-based form for use with CRUD views.
@ -392,13 +408,8 @@ class MasterView(View):
# TODO: Some hacky stuff here, to accommodate old form cruft. Probably
# should refactor forms soon too, but trying to avoid it for the moment.
readonly = kwargs.pop('readonly', False)
kwargs.setdefault('creating', False)
kwargs.setdefault('editing', False)
# Ugh, these attributes must be present on the view..?
self.creating = kwargs['creating']
self.editing = kwargs['editing']
kwargs.setdefault('creating', self.creating)
kwargs.setdefault('editing', self.editing)
fieldset = self.make_fieldset(instance)
self.configure_fieldset(fieldset)
@ -409,7 +420,7 @@ class MasterView(View):
else:
kwargs.setdefault('cancel_url', self.get_action_url('view', instance))
form = AlchemyForm(self.request, fieldset, **kwargs)
form.readonly = readonly
form.readonly = self.viewing
return form
def make_fieldset(self, instance, **kwargs):
@ -422,11 +433,10 @@ class MasterView(View):
fieldset.prettify = prettify
return fieldset
def template_kwargs(self, **kwargs):
def before_delete(self, instance):
"""
Supplement the template context, for all views.
Event hook which is called just before deletion is attempted.
"""
return kwargs
##############################
# Config Stuff

View file

@ -134,7 +134,7 @@ class RolesView(MasterView):
users = AlchemyGrid('roles.users', self.request, data=users, model_class=model.User,
main_actions=[
GridAction('view', icon='zoomin',
url=lambda u: self.request.route_url('user.read', uuid=u.uuid)),
url=lambda u: self.request.route_url('users.view', uuid=u.uuid)),
])
users.configure(include=[users.username], readonly=True)
kwargs['users'] = users

View file

@ -26,125 +26,28 @@ User Views
from __future__ import unicode_literals
from sqlalchemy import orm
from rattail.db import model
from rattail.db.model import User, Person, Role
from rattail.db.auth import guest_role, set_user_password
import formalchemy
from formalchemy import Field, ValidationError
from formalchemy.fields import SelectFieldRenderer
from webhelpers.html import tags
from webhelpers.html import HTML
from webhelpers.html import HTML, tags
from . import SearchableAlchemyGridView, CrudView
from ..forms import PersonFieldLinkRenderer
from ..db import Session
from tailbone.grids.search import BooleanSearchFilter
from .continuum import VersionView, version_defaults
from tailbone.db import Session
from tailbone.views import MasterView
from tailbone.views.continuum import VersionView, version_defaults
from tailbone.forms import renderers
class UsersGrid(SearchableAlchemyGridView):
mapped_class = User
config_prefix = 'users'
sort = 'username'
def join_map(self):
return {
'person':
lambda q: q.outerjoin(Person),
}
def filter_map(self):
return self.make_filter_map(
ilike=['username'],
exact=['active'],
person=self.filter_ilike(Person.display_name))
def filter_config(self):
return self.make_filter_config(
include_filter_username=True,
filter_type_username='lk',
include_filter_person=True,
filter_type_person='lk',
filter_factory_active=BooleanSearchFilter,
include_filter_active=True,
filter_type_active='is',
active='True')
def sort_map(self):
return self.make_sort_map(
'username',
person=self.sorter(Person.display_name))
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.username,
g.person,
],
readonly=True)
if self.request.has_perm('users.read'):
g.viewable = True
g.view_route_name = 'user.read'
if self.request.has_perm('users.update'):
g.editable = True
g.edit_route_name = 'user.update'
if self.request.has_perm('users.delete'):
g.deletable = True
g.delete_route_name = 'user.delete'
return g
class RolesField(Field):
def __init__(self, name, **kwargs):
kwargs.setdefault('value', self.get_value)
kwargs.setdefault('options', self.get_options())
kwargs.setdefault('multiple', True)
super(RolesField, self).__init__(name, **kwargs)
def get_value(self, user):
return [x.uuid for x in user.roles]
def get_options(self):
return Session.query(Role.name, Role.uuid)\
.filter(Role.uuid != guest_role(Session()).uuid)\
.order_by(Role.name)\
.all()
def sync(self):
if not self.is_readonly():
user = self.model
roles = Session.query(Role)
data = self.renderer.deserialize()
user.roles = [roles.get(x) for x in data]
def RolesFieldRenderer(request):
class RolesFieldRenderer(SelectFieldRenderer):
def render_readonly(self, **kwargs):
roles = Session.query(Role)
html = ''
for uuid in self.value:
role = roles.get(uuid)
link = tags.link_to(
role.name, request.route_url('roles.view', uuid=role.uuid))
html += HTML.tag('li', c=link)
html = HTML.tag('ul', c=html)
return html
return RolesFieldRenderer
class PasswordFieldRenderer(formalchemy.PasswordFieldRenderer):
def render(self, **kwargs):
return tags.password(self.name, value='', maxlength=self.length, **kwargs)
def unique_username(value, field):
user = field.parent.model
query = Session.query(model.User).filter(model.User.username == value)
if user.uuid:
query = query.filter(model.User.uuid != user.uuid)
if query.count():
raise formalchemy.ValidationError("Username must be unique.")
def passwords_match(value, field):
@ -153,6 +56,12 @@ def passwords_match(value, field):
return value
class PasswordFieldRenderer(formalchemy.PasswordFieldRenderer):
def render(self, **kwargs):
return tags.password(self.name, value='', maxlength=self.length, **kwargs)
class PasswordField(formalchemy.Field):
def __init__(self, *args, **kwargs):
@ -168,45 +77,103 @@ class PasswordField(formalchemy.Field):
set_user_password(self.model, password)
class UserCrud(CrudView):
def RolesFieldRenderer(request):
mapped_class = User
home_route = 'users'
class RolesFieldRenderer(SelectFieldRenderer):
def fieldset(self, user):
fs = self.make_fieldset(user)
def render_readonly(self, **kwargs):
roles = Session.query(model.Role)
html = ''
for uuid in self.value:
role = roles.get(uuid)
link = tags.link_to(
role.name, request.route_url('roles.view', uuid=role.uuid))
html += HTML.tag('li', c=link)
html = HTML.tag('ul', c=html)
return html
# Must set Person options to empty set to avoid unwanted magic.
return RolesFieldRenderer
class RolesField(formalchemy.Field):
def __init__(self, name, **kwargs):
kwargs.setdefault('value', self.get_value)
kwargs.setdefault('options', self.get_options())
kwargs.setdefault('multiple', True)
super(RolesField, self).__init__(name, **kwargs)
def get_value(self, user):
return [x.uuid for x in user.roles]
def get_options(self):
return Session.query(model.Role.name, model.Role.uuid)\
.filter(model.Role.uuid != guest_role(Session()).uuid)\
.order_by(model.Role.name)\
.all()
def sync(self):
if not self.is_readonly():
user = self.model
roles = Session.query(model.Role)
data = self.renderer.deserialize()
user.roles = [roles.get(x) for x in data]
class UsersView(MasterView):
"""
Master view for the User model.
"""
model_class = model.User
def query(self, session):
return session.query(model.User)\
.options(orm.joinedload(model.User.person))
def configure_grid(self, g):
g.joiners['person'] = lambda q: q.outerjoin(model.Person)
del g.filters['password']
del g.filters['salt']
g.filters['username'].default_active = True
g.filters['username'].default_verb = 'contains'
g.filters['active'].verbs = ['is_true', 'is_false', 'is_any']
g.filters['active'].default_active = True
g.filters['active'].default_verb = 'is_true'
g.filters['person'] = g.make_filter('person', model.Person.display_name, label="Person's Name",
default_active=True, default_verb='contains')
g.sorters['person'] = lambda q, d: q.order_by(getattr(model.Person.display_name, d)())
g.default_sortkey = 'username'
g.person.set(label="Person's Name")
g.configure(
include=[
g.username,
g.person,
],
readonly=True)
def configure_fieldset(self, fs):
fs.username.set(validate=unique_username)
fs.person.set(options=[])
fs.append(PasswordField('password'))
fs.append(Field('confirm_password',
renderer=PasswordFieldRenderer))
fs.append(RolesField(
'roles', renderer=RolesFieldRenderer(self.request)))
fs.person.set(renderer=renderers.PersonFieldLinkRenderer)
fs.append(PasswordField('password', label="Set Password"))
fs.append(formalchemy.Field('confirm_password', renderer=PasswordFieldRenderer))
fs.append(RolesField('roles', renderer=RolesFieldRenderer(self.request)))
fs.configure(
include=[
fs.username,
fs.person.with_renderer(PersonFieldLinkRenderer),
fs.password.label("Set Password"),
fs.person,
fs.active,
fs.password,
fs.confirm_password,
fs.roles,
fs.active,
])
if self.creating:
def unique_username(value, field):
if Session.query(User).filter_by(username=value).count():
raise ValidationError("Username must be unique.")
fs.username.set(validate=unique_username)
if self.readonly:
if self.viewing:
del fs.password
del fs.confirm_password
return fs
class UserVersionView(VersionView):
"""
@ -216,33 +183,6 @@ class UserVersionView(VersionView):
route_model_view = 'user.read'
def add_routes(config):
config.add_route(u'users', u'/users')
config.add_route(u'user.create', u'/users/new')
config.add_route(u'user.read', u'/users/{uuid}')
config.add_route(u'user.update', u'/users/{uuid}/edit')
config.add_route(u'user.delete', u'/users/{uuid}/delete')
def includeme(config):
add_routes(config)
# List
config.add_view(UsersGrid, route_name='users',
renderer='/users/index.mako',
permission='users.list')
# CRUD
config.add_view(UserCrud, attr='create', route_name='user.create',
renderer='/users/crud.mako',
permission='users.create')
config.add_view(UserCrud, attr='read', route_name='user.read',
renderer='/users/crud.mako',
permission='users.read')
config.add_view(UserCrud, attr='update', route_name='user.update',
renderer='/users/crud.mako',
permission='users.update')
config.add_view(UserCrud, attr='delete', route_name='user.delete',
permission='users.delete')
UsersView.defaults(config)
version_defaults(config, UserVersionView, 'user')