Refactor employees view to use master3
This commit is contained in:
parent
7a777964a7
commit
84ebf5d929
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue