Refactor employees view to use master3

This commit is contained in:
Lance Edgar 2017-12-04 13:48:31 -06:00
parent 7a777964a7
commit 84ebf5d929
4 changed files with 181 additions and 117 deletions

View file

@ -75,6 +75,38 @@ class CustomSchemaNode(SQLAlchemySchemaNode):
"""
return get_association_proxy(self.inspector, field)
def association_proxy_target(self, field):
"""
Returns the property on the main class, which represents the "target"
for the given association proxy field name. Typically this will refer
to the "extension" model class.
"""
proxy = self.association_proxy(field)
if proxy:
proxy_target = self.inspector.get_property(proxy.target_collection)
if isinstance(proxy_target, orm.RelationshipProperty) and not proxy_target.uselist:
return proxy_target
def association_proxy_column(self, field):
"""
Returns the property on the proxy target class, for the column which is
reflected by the proxy.
"""
proxy_target = self.association_proxy_target(field)
if proxy_target:
prop = proxy_target.mapper.get_property(field)
if isinstance(prop, orm.ColumnProperty) and isinstance(prop.columns[0], sa.Column):
return prop
def supported_association_proxy(self, field):
"""
Returns boolean indicating whether the association proxy corresponding
to the given field name, is "supported" with typical logic.
"""
if not self.association_proxy_column(field):
return False
return True
def add_nodes(self, includes, excludes, overrides):
"""
Add all automatic nodes to the schema.
@ -117,13 +149,9 @@ class CustomSchemaNode(SQLAlchemySchemaNode):
else:
# magic for association proxy fields
proxy = self.association_proxy(name)
if proxy:
proxy_prop = self.inspector.get_property(proxy.target_collection)
if isinstance(proxy_prop, orm.RelationshipProperty):
prop = proxy_prop.mapper.get_property(name)
if isinstance(prop, orm.ColumnProperty) and isinstance(prop.columns[0], sa.Column):
node = self.get_schema_from_column(prop, name_overrides_copy)
column = self.association_proxy_column(name)
if column:
node = self.get_schema_from_column(column, name_overrides_copy)
else:
log.debug(
@ -167,7 +195,8 @@ class CustomSchemaNode(SQLAlchemySchemaNode):
name = node.name
if name not in dict_:
if not self.association_proxy(name):
# we're only processing association proxy fields here
if not self.supported_association_proxy(name):
continue
value = getattr(obj, name)
@ -229,7 +258,7 @@ class CustomSchemaNode(SQLAlchemySchemaNode):
else:
# try to process association proxy field
if self.association_proxy(attr):
if self.supported_association_proxy(attr):
value = dict_[attr]
if value is colander.null:
# `colander.null` is never an appropriate
@ -306,6 +335,10 @@ class Form(object):
if key in self.fields:
self.fields.remove(key)
def remove_fields(self, *args):
for arg in args:
self.remove_field(arg)
def make_schema(self):
if not self.model_class:
# TODO
@ -566,10 +599,11 @@ class Form(object):
return HTML.tag('pre', value)
def obtain_value(self, record, field_name):
try:
return record[field_name]
except TypeError:
return getattr(record, field_name)
if record:
try:
return record[field_name]
except TypeError:
return getattr(record, field_name)
def validate(self, *args, **kwargs):
form = self.make_deform_form()

View file

@ -26,15 +26,18 @@ Employee Views
from __future__ import unicode_literals, absolute_import
import six
import sqlalchemy as sa
from rattail.db import model
import formalchemy as fa
import colander
from deform import widget as dfwidget
from webhelpers2.html import tags, HTML
from tailbone import forms, grids
from tailbone import grids
from tailbone.db import Session
from tailbone.views import MasterView2 as MasterView, AutocompleteView
from tailbone.views import MasterView3 as MasterView, AutocompleteView
class EmployeesView(MasterView):
@ -53,6 +56,21 @@ class EmployeesView(MasterView):
'status',
]
form_fields = [
'person',
'first_name',
'last_name',
'display_name',
'phone',
'email',
'status',
'full_time',
'full_time_start',
'id',
'stores',
'departments',
]
def configure_grid(self, g):
super(EmployeesView, self).configure_grid(g)
@ -124,38 +142,116 @@ class EmployeesView(MasterView):
return not bool(employee.user and employee.user.username == 'chuck')
return True
def _preconfigure_fieldset(self, fs):
fs.append(forms.AssociationProxyField('first_name'))
fs.append(forms.AssociationProxyField('last_name'))
fs.append(StoresField('stores'))
fs.append(DepartmentsField('departments'))
def configure_form(self, f):
super(EmployeesView, self).configure_form(f)
employee = f.model_instance
fs.person.set(renderer=forms.renderers.PersonFieldRenderer)
fs.display_name.set(label="Short Name")
fs.phone.set(label="Phone Number", readonly=True)
fs.email.set(label="Email Address", readonly=True)
fs.status.set(renderer=forms.renderers.EnumFieldRenderer(self.enum.EMPLOYEE_STATUS))
fs.id.set(label="ID")
f.set_renderer('person', self.render_person)
f.set_renderer('stores', self.render_stores)
f.set_label('stores', "Stores") # TODO: should not be necessary
if self.creating or self.editing:
stores = self.get_possible_stores().all()
store_values = [(s.uuid, six.text_type(s)) for s in stores]
f.set_node('stores', colander.SchemaNode(colander.Set()))
f.set_widget('stores', dfwidget.SelectWidget(multiple=True,
size=len(stores),
values=store_values))
if self.editing:
f.set_default('stores', [s.uuid for s in employee.stores])
f.set_renderer('departments', self.render_departments)
f.set_label('departments', "Departments") # TODO: should not be necessary
if self.creating or self.editing:
departments = self.get_possible_departments().all()
dept_values = [(d.uuid, six.text_type(d)) for d in departments]
f.set_node('departments', colander.SchemaNode(colander.Set()))
f.set_widget('departments', dfwidget.SelectWidget(multiple=True,
size=len(departments),
values=dept_values))
if self.editing:
f.set_default('departments', [d.uuid for d in employee.departments])
f.set_enum('status', self.enum.EMPLOYEE_STATUS)
f.set_type('full_time_start', 'date_jquery')
if self.editing:
# TODO: this should not be needed (association proxy)
f.set_default('full_time_start', employee.full_time_start)
f.set_readonly('person')
f.set_readonly('phone')
f.set_readonly('email')
f.set_label('display_name', "Short Name")
f.set_label('phone', "Phone Number")
f.set_label('email', "Email Address")
f.set_label('id', "ID")
def configure_fieldset(self, fs):
fs.configure(
include=[
fs.person,
fs.first_name,
fs.last_name,
fs.display_name,
fs.phone,
fs.email,
fs.status,
fs.full_time,
fs.full_time_start,
fs.id,
fs.stores,
fs.departments,
])
if not self.viewing:
del fs.first_name
del fs.last_name
f.remove_fields('first_name', 'last_name')
def objectify(self, form, data):
employee = super(EmployeesView, self).objectify(form, data)
self.update_stores(employee, data)
self.update_departments(employee, data)
return employee
def update_stores(self, employee, data):
old_stores = set([s.uuid for s in employee.stores])
new_stores = data['stores']
for uuid in new_stores:
if uuid not in old_stores:
employee._stores.append(model.EmployeeStore(store_uuid=uuid))
for uuid in old_stores:
if uuid not in new_stores:
store = self.Session.query(model.Store).get(uuid)
employee.stores.remove(store)
def update_departments(self, employee, data):
old_depts = set([d.uuid for d in employee.departments])
new_depts = data['departments']
for uuid in new_depts:
if uuid not in old_depts:
employee._departments.append(model.EmployeeDepartment(department_uuid=uuid))
for uuid in old_depts:
if uuid not in new_depts:
dept = self.Session.query(model.Department).get(uuid)
employee.departments.remove(dept)
def get_possible_stores(self):
return self.Session.query(model.Store)\
.order_by(model.Store.name)
def get_possible_departments(self):
return self.Session.query(model.Department)\
.order_by(model.Department.name)
def render_person(self, employee, field):
person = employee.person if employee else None
if not person:
return ""
text = six.text_type(person)
url = self.request.route_url('people.view', uuid=person.uuid)
return tags.link_to(text, url)
def render_stores(self, employee, field):
stores = employee.stores if employee else None
if not stores:
return ""
items = HTML.literal('')
for store in sorted(stores, key=six.text_type):
items += HTML.tag('li', c=six.text_type(store))
return HTML.tag('ul', c=items)
def render_departments(self, employee, field):
departments = employee.departments if employee else None
if not departments:
return ""
items = HTML.literal('')
for department in sorted(departments, key=six.text_type):
items += HTML.tag('li', c=six.text_type(department))
return HTML.tag('ul', c=items)
def get_version_child_classes(self):
return [
@ -167,62 +263,6 @@ class EmployeesView(MasterView):
]
class StoresField(fa.Field):
def __init__(self, name, **kwargs):
kwargs.setdefault('type', fa.types.Set)
kwargs.setdefault('options', Session.query(model.Store).order_by(model.Store.name))
kwargs.setdefault('value', self.get_value)
kwargs.setdefault('multiple', True)
kwargs.setdefault('size', 3)
fa.Field.__init__(self, name=name, **kwargs)
def get_value(self, employee):
return [s.uuid for s in employee.stores]
def sync(self):
if not self.is_readonly():
employee = self.parent.model
old_stores = set([s.uuid for s in employee.stores])
new_stores = set(self._deserialize())
for uuid in new_stores:
if uuid not in old_stores:
employee._stores.append(model.EmployeeStore(store_uuid=uuid))
for uuid in old_stores:
if uuid not in new_stores:
store = Session.query(model.Store).get(uuid)
assert store
employee.stores.remove(store)
class DepartmentsField(fa.Field):
def __init__(self, name, **kwargs):
kwargs.setdefault('type', fa.types.Set)
kwargs.setdefault('options', Session.query(model.Department).order_by(model.Department.name))
kwargs.setdefault('value', self.get_value)
kwargs.setdefault('multiple', True)
kwargs.setdefault('size', 10)
fa.Field.__init__(self, name=name, **kwargs)
def get_value(self, employee):
return [d.uuid for d in employee.departments]
def sync(self):
if not self.is_readonly():
employee = self.parent.model
old_depts = set([d.uuid for d in employee.departments])
new_depts = set(self._deserialize())
for uuid in new_depts:
if uuid not in old_depts:
employee._departments.append(model.EmployeeDepartment(department_uuid=uuid))
for uuid in old_depts:
if uuid not in new_depts:
dept = Session.query(model.Department).get(uuid)
assert dept
employee.departments.remove(dept)
class EmployeesAutocomplete(AutocompleteView):
"""
Autocomplete view for the Employee model, but restricted to return only

View file

@ -250,8 +250,6 @@ class MasterView(View):
# let save_create_form() return alternate object if necessary
obj = self.save_create_form(form) or form.fieldset.model
self.after_create(obj)
# TODO: ugh, avoiding refactor for now but it's needed
self.after_create_form(form, obj)
self.flash_after_create(obj)
return self.redirect_after_create(obj)
context = {'form': form}
@ -1625,11 +1623,6 @@ class MasterView(View):
Event hook, called just after a new instance is saved.
"""
def after_create_form(self, form, obj):
"""
Event hook, called just after a new instance is saved.
"""
def editable_instance(self, instance):
"""
Returns boolean indicating whether or not the given instance can be
@ -1643,11 +1636,6 @@ class MasterView(View):
Event hook, called just after an existing instance is saved.
"""
def after_edit_form(self, form, obj):
"""
Event hook, called just after an instance is updated.
"""
def deletable_instance(self, instance):
"""
Returns boolean indicating whether or not the given instance can be

View file

@ -152,9 +152,13 @@ class MasterView3(MasterView2):
return False
return True
def objectify(self, form, data):
obj = form.schema.objectify(data, context=form.model_instance)
return obj
def save_create_form(self, form):
self.before_create(form)
obj = form.schema.objectify(self.form_deserialized)
obj = self.objectify(form, self.form_deserialized)
self.before_create_flush(obj, form)
self.Session.add(obj)
self.Session.flush()
@ -164,8 +168,6 @@ class MasterView3(MasterView2):
pass
def save_edit_form(self, form):
obj = form.schema.objectify(self.form_deserialized, context=form.model_instance)
obj = self.objectify(form, self.form_deserialized)
self.after_edit(obj)
# TODO: ugh, this is to avoid refactor for the moment..but it's needed..
self.after_edit_form(form, obj)
self.Session.flush()