Convert User pages to use master view.
And of course make some more tweaks to new grids etc.
This commit is contained in:
parent
9cfbc918e7
commit
af07f477dc
11 changed files with 269 additions and 289 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue