714 lines
26 KiB
Python
714 lines
26 KiB
Python
# -*- coding: utf-8; -*-
|
|
################################################################################
|
|
#
|
|
# Rattail -- Retail Software Framework
|
|
# Copyright © 2010-2020 Lance Edgar
|
|
#
|
|
# This file is part of Rattail.
|
|
#
|
|
# Rattail is free software: you can redistribute it and/or modify it under the
|
|
# terms of the GNU General Public License as published by the Free Software
|
|
# Foundation, either version 3 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
# details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# Rattail. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
################################################################################
|
|
"""
|
|
Person Views
|
|
"""
|
|
|
|
from __future__ import unicode_literals, absolute_import
|
|
|
|
import six
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import orm
|
|
|
|
from rattail.db import model, api
|
|
from rattail.time import localtime
|
|
from rattail.util import OrderedDict
|
|
|
|
import colander
|
|
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
|
|
from webhelpers2.html import HTML, tags
|
|
|
|
from tailbone import forms, grids
|
|
from tailbone.views import MasterView, AutocompleteView
|
|
|
|
|
|
class PeopleView(MasterView):
|
|
"""
|
|
Master view for the Person class.
|
|
"""
|
|
model_class = model.Person
|
|
model_title_plural = "People"
|
|
route_prefix = 'people'
|
|
touchable = True
|
|
has_versions = True
|
|
supports_mobile = True
|
|
bulk_deletable = True
|
|
is_contact = True
|
|
manage_notes_from_profile_view = False
|
|
|
|
labels = {
|
|
'default_phone': "Phone Number",
|
|
'default_email': "Email Address",
|
|
}
|
|
|
|
grid_columns = [
|
|
'display_name',
|
|
'first_name',
|
|
'last_name',
|
|
'phone',
|
|
'email',
|
|
]
|
|
|
|
form_fields = [
|
|
'first_name',
|
|
'middle_name',
|
|
'last_name',
|
|
'display_name',
|
|
'default_phone',
|
|
'default_email',
|
|
'address',
|
|
'employee',
|
|
'customers',
|
|
'members',
|
|
'users',
|
|
]
|
|
|
|
mobile_form_fields = [
|
|
'first_name',
|
|
'middle_name',
|
|
'last_name',
|
|
'display_name',
|
|
'phone',
|
|
'email',
|
|
'address',
|
|
'employee',
|
|
'customers',
|
|
'users',
|
|
]
|
|
|
|
def configure_grid(self, g):
|
|
super(PeopleView, self).configure_grid(g)
|
|
|
|
g.joiners['email'] = lambda q: q.outerjoin(model.PersonEmailAddress, sa.and_(
|
|
model.PersonEmailAddress.parent_uuid == model.Person.uuid,
|
|
model.PersonEmailAddress.preference == 1))
|
|
g.joiners['phone'] = lambda q: q.outerjoin(model.PersonPhoneNumber, sa.and_(
|
|
model.PersonPhoneNumber.parent_uuid == model.Person.uuid,
|
|
model.PersonPhoneNumber.preference == 1))
|
|
|
|
g.filters['email'] = g.make_filter('email', model.PersonEmailAddress.address)
|
|
g.set_filter('phone', model.PersonPhoneNumber.number,
|
|
factory=grids.filters.AlchemyPhoneNumberFilter)
|
|
|
|
g.joiners['customer_id'] = lambda q: q.outerjoin(model.CustomerPerson).outerjoin(model.Customer)
|
|
g.filters['customer_id'] = g.make_filter('customer_id', model.Customer.id)
|
|
|
|
g.filters['first_name'].default_active = True
|
|
g.filters['first_name'].default_verb = 'contains'
|
|
|
|
g.filters['last_name'].default_active = True
|
|
g.filters['last_name'].default_verb = 'contains'
|
|
|
|
g.sorters['email'] = lambda q, d: q.order_by(getattr(model.PersonEmailAddress.address, d)())
|
|
g.sorters['phone'] = lambda q, d: q.order_by(getattr(model.PersonPhoneNumber.number, d)())
|
|
|
|
g.set_sort_defaults('display_name')
|
|
|
|
g.set_label('display_name', "Full Name")
|
|
g.set_label('phone', "Phone Number")
|
|
g.set_label('email', "Email Address")
|
|
g.set_label('customer_id', "Customer ID")
|
|
|
|
g.set_link('display_name')
|
|
g.set_link('first_name')
|
|
g.set_link('last_name')
|
|
|
|
def get_instance(self):
|
|
# TODO: I don't recall why this fallback check for a vendor contact
|
|
# exists here, but leaving it intact for now.
|
|
key = self.request.matchdict['uuid']
|
|
instance = self.Session.query(model.Person).get(key)
|
|
if instance:
|
|
return instance
|
|
instance = self.Session.query(model.VendorContact).get(key)
|
|
if instance:
|
|
return instance.person
|
|
raise HTTPNotFound
|
|
|
|
def editable_instance(self, person):
|
|
if self.rattail_config.demo():
|
|
return not bool(person.user and person.user.username == 'chuck')
|
|
return True
|
|
|
|
def deletable_instance(self, person):
|
|
if self.rattail_config.demo():
|
|
return not bool(person.user and person.user.username == 'chuck')
|
|
return True
|
|
|
|
def delete_instance(self, person):
|
|
"""
|
|
Supplements the default logic as follows:
|
|
|
|
Any customer associations are first deleted for the person. Once that
|
|
is complete, deletion continues as per usual.
|
|
"""
|
|
session = orm.object_session(person)
|
|
|
|
# must explicitly remove all CustomerPerson records
|
|
for cp in list(person._customers):
|
|
customer = cp.customer
|
|
session.delete(cp)
|
|
# session.flush()
|
|
customer._people.reorder()
|
|
|
|
# continue with normal logic
|
|
super(PeopleView, self).delete_instance(person)
|
|
|
|
def touch_instance(self, person):
|
|
"""
|
|
Supplements the default logic as follows:
|
|
|
|
In addition to "touching" the person proper, we also "touch" each
|
|
contact info record associated with them.
|
|
"""
|
|
# touch person, as per usual
|
|
super(PeopleView, self).touch_instance(person)
|
|
|
|
def touch(obj):
|
|
change = model.Change()
|
|
change.class_name = obj.__class__.__name__
|
|
change.instance_uuid = obj.uuid
|
|
change.deleted = False
|
|
self.Session.add(change)
|
|
|
|
# phone numbers
|
|
for phone in person.phones:
|
|
touch(phone)
|
|
|
|
# email addresses
|
|
for email in person.emails:
|
|
touch(email)
|
|
|
|
# mailing addresses
|
|
for address in person.addresses:
|
|
touch(address)
|
|
|
|
def configure_common_form(self, f):
|
|
super(PeopleView, self).configure_common_form(f)
|
|
person = f.model_instance
|
|
|
|
f.set_label('display_name', "Full Name")
|
|
|
|
# TODO: should remove this?
|
|
f.set_readonly('phone')
|
|
f.set_label('phone', "Phone Number")
|
|
|
|
f.set_renderer('default_phone', self.render_default_phone)
|
|
if not self.creating and person.phones:
|
|
f.set_default('default_phone', person.phones[0].number)
|
|
|
|
# TODO: should remove this?
|
|
f.set_readonly('email')
|
|
f.set_label('email', "Email Address")
|
|
|
|
f.set_renderer('default_email', self.render_default_email)
|
|
if not self.creating and person.emails:
|
|
f.set_default('default_email', person.emails[0].address)
|
|
|
|
f.set_readonly('address')
|
|
f.set_label('address', "Mailing Address")
|
|
|
|
# employee
|
|
if self.creating:
|
|
f.remove_field('employee')
|
|
else:
|
|
f.set_readonly('employee')
|
|
f.set_renderer('employee', self.render_employee)
|
|
|
|
# customers
|
|
if self.creating:
|
|
f.remove_field('customers')
|
|
else:
|
|
f.set_readonly('customers')
|
|
f.set_renderer('customers', self.render_customers)
|
|
|
|
# members
|
|
if self.creating:
|
|
f.remove_field('members')
|
|
else:
|
|
f.set_readonly('members')
|
|
f.set_renderer('members', self.render_members)
|
|
|
|
# users
|
|
if self.creating:
|
|
f.remove_field('users')
|
|
else:
|
|
f.set_readonly('users')
|
|
f.set_renderer('users', self.render_users)
|
|
|
|
def render_employee(self, person, field):
|
|
employee = person.employee
|
|
if not employee:
|
|
return ""
|
|
text = six.text_type(employee)
|
|
url = self.request.route_url('employees.view', uuid=employee.uuid)
|
|
return tags.link_to(text, url)
|
|
|
|
def render_customers(self, person, field):
|
|
customers = person._customers
|
|
if not customers:
|
|
return ""
|
|
items = []
|
|
for customer in customers:
|
|
customer = customer.customer
|
|
text = six.text_type(customer)
|
|
if customer.number:
|
|
text = "(#{}) {}".format(customer.number, text)
|
|
elif customer.id:
|
|
text = "({}) {}".format(customer.id, text)
|
|
route = '{}customers.view'.format('mobile.' if self.mobile else '')
|
|
url = self.request.route_url(route, uuid=customer.uuid)
|
|
items.append(HTML.tag('li', c=[tags.link_to(text, url)]))
|
|
return HTML.tag('ul', c=items)
|
|
|
|
def render_members(self, person, field):
|
|
members = person.members
|
|
if not members:
|
|
return ""
|
|
items = []
|
|
for member in members:
|
|
text = six.text_type(member)
|
|
if member.number:
|
|
text = "(#{}) {}".format(member.number, text)
|
|
elif member.id:
|
|
text = "({}) {}".format(member.id, text)
|
|
url = self.request.route_url('members.view', uuid=member.uuid)
|
|
items.append(HTML.tag('li', c=[tags.link_to(text, url)]))
|
|
return HTML.tag('ul', c=items)
|
|
|
|
def render_users(self, person, field):
|
|
use_buefy = self.get_use_buefy()
|
|
users = person.users
|
|
items = []
|
|
for user in users:
|
|
text = user.username
|
|
url = self.request.route_url('users.view', uuid=user.uuid)
|
|
items.append(HTML.tag('li', c=[tags.link_to(text, url)]))
|
|
if items:
|
|
return HTML.tag('ul', c=items)
|
|
elif self.viewing and self.request.has_perm('users.create'):
|
|
if use_buefy:
|
|
return HTML.tag('b-button', type='is-primary', c="Make User",
|
|
**{'@click': 'clickMakeUser()'})
|
|
else:
|
|
return HTML.tag('button', type='button', id='make-user', c="Make User")
|
|
else:
|
|
return ""
|
|
|
|
def get_version_child_classes(self):
|
|
return [
|
|
(model.PersonPhoneNumber, 'parent_uuid'),
|
|
(model.PersonEmailAddress, 'parent_uuid'),
|
|
(model.PersonMailingAddress, 'parent_uuid'),
|
|
(model.Employee, 'person_uuid'),
|
|
(model.CustomerPerson, 'person_uuid'),
|
|
(model.VendorContact, 'person_uuid'),
|
|
]
|
|
|
|
def view_profile(self):
|
|
"""
|
|
View which exposes the "full profile" for a given person, i.e. all
|
|
related customer, employee, user info etc.
|
|
"""
|
|
self.viewing = True
|
|
person = self.get_instance()
|
|
employee = person.employee
|
|
context = {
|
|
'person': person,
|
|
'instance': person,
|
|
'instance_title': self.get_instance_title(person),
|
|
'today': localtime(self.rattail_config).date(),
|
|
'person_data': self.get_context_person(person),
|
|
'customers_data': self.get_context_customers(person),
|
|
'members_data': self.get_context_members(person),
|
|
'employee': employee,
|
|
'employee_view_url': self.request.route_url('employees.view', uuid=employee.uuid) if employee else None,
|
|
'employee_history': employee.get_current_history() if employee else None,
|
|
'employee_history_data': self.get_context_employee_history(employee),
|
|
}
|
|
|
|
use_buefy = self.get_use_buefy()
|
|
template = 'view_profile_buefy' if use_buefy else 'view_profile'
|
|
return self.render_to_response(template, context)
|
|
|
|
def get_context_person(self, person):
|
|
return {
|
|
'uuid': person.uuid,
|
|
'first_name': person.first_name,
|
|
'last_name': person.last_name,
|
|
'display_name': person.display_name,
|
|
'view_url': self.get_action_url('view', person),
|
|
'view_profile_url': self.get_action_url('view_profile', person),
|
|
}
|
|
|
|
def get_context_address(self, address):
|
|
return {
|
|
'uuid': address.uuid,
|
|
'street': address.street,
|
|
'street2': address.street2,
|
|
'city': address.city,
|
|
'state': address.state,
|
|
'zipcode': address.zipcode,
|
|
'display': six.text_type(address),
|
|
}
|
|
|
|
def get_context_customers(self, person):
|
|
data = []
|
|
for cp in person._customers:
|
|
customer = cp.customer
|
|
data.append({
|
|
'uuid': customer.uuid,
|
|
'ordinal': cp.ordinal,
|
|
'id': customer.id,
|
|
'number': customer.number,
|
|
'name': customer.name,
|
|
'view_url': self.request.route_url('customers.view',
|
|
uuid=customer.uuid),
|
|
'people': [self.get_context_person(p)
|
|
for p in customer.people],
|
|
'addresses': [self.get_context_address(a)
|
|
for a in customer.addresses],
|
|
})
|
|
return data
|
|
|
|
def get_context_members(self, person):
|
|
data = OrderedDict()
|
|
|
|
for member in person.members:
|
|
data[member.uuid] = self.get_context_member(member)
|
|
|
|
for customer in person.customers:
|
|
for member in customer.members:
|
|
if member.uuid not in data:
|
|
data[member.uuid] = self.get_context_member(member)
|
|
|
|
return list(data.values())
|
|
|
|
def get_context_member(self, member):
|
|
profile_url = None
|
|
if member.person:
|
|
profile_url = self.request.route_url('people.view_profile',
|
|
uuid=member.person_uuid)
|
|
|
|
return {
|
|
'uuid': member.uuid,
|
|
'number': member.number,
|
|
'id': member.id,
|
|
'active': member.active,
|
|
'joined': six.text_type(member.joined) if member.joined else None,
|
|
'withdrew': six.text_type(member.withdrew) if member.withdrew else None,
|
|
'customer_uuid': member.customer_uuid,
|
|
'customer_name': member.customer.name if member.customer else None,
|
|
'person_uuid': member.person_uuid,
|
|
'display': six.text_type(member),
|
|
'person_display_name': member.person.display_name if member.person else None,
|
|
'view_url': self.request.route_url('members.view', uuid=member.uuid),
|
|
'view_profile_url': profile_url,
|
|
}
|
|
|
|
def get_context_employee_history(self, employee):
|
|
data = []
|
|
if employee:
|
|
for history in employee.sorted_history(reverse=True):
|
|
data.append({
|
|
'uuid': history.uuid,
|
|
'start_date': six.text_type(history.start_date),
|
|
'end_date': six.text_type(history.end_date or ''),
|
|
})
|
|
return data
|
|
|
|
def make_note_form(self, mode, person):
|
|
schema = NoteSchema().bind(session=self.Session(),
|
|
person_uuid=person.uuid)
|
|
if mode == 'create':
|
|
del schema['uuid']
|
|
form = forms.Form(schema=schema, request=self.request)
|
|
return form
|
|
|
|
def profile_add_note(self):
|
|
person = self.get_instance()
|
|
form = self.make_note_form('create', person)
|
|
if form.validate(newstyle=True):
|
|
note = self.create_note(person, form)
|
|
self.Session.flush()
|
|
return self.profile_add_note_success(note)
|
|
else:
|
|
return self.profile_add_note_failure(person, form)
|
|
|
|
def create_note(self, person, form):
|
|
note = model.PersonNote()
|
|
note.type = form.validated['note_type']
|
|
note.subject = form.validated['note_subject']
|
|
note.text = form.validated['note_text']
|
|
note.created_by = self.request.user
|
|
person.notes.append(note)
|
|
return note
|
|
|
|
def profile_add_note_success(self, note):
|
|
return self.redirect(self.get_action_url('view_profile', person))
|
|
|
|
def profile_add_note_failure(self, person, form):
|
|
return self.redirect(self.get_action_url('view_profile', person))
|
|
|
|
def profile_edit_note(self):
|
|
person = self.get_instance()
|
|
form = self.make_note_form('edit', person)
|
|
if form.validate(newstyle=True):
|
|
note = self.update_note(person, form)
|
|
self.Session.flush()
|
|
return self.profile_edit_note_success(note)
|
|
else:
|
|
return self.profile_edit_note_failure(person, form)
|
|
|
|
def update_note(self, person, form):
|
|
note = self.Session.query(model.PersonNote).get(form.validated['uuid'])
|
|
note.subject = form.validated['note_subject']
|
|
note.text = form.validated['note_text']
|
|
return note
|
|
|
|
def profile_edit_note_success(self, note):
|
|
return self.redirect(self.get_action_url('view_profile', person))
|
|
|
|
def profile_edit_note_failure(self, person, form):
|
|
return self.redirect(self.get_action_url('view_profile', person))
|
|
|
|
def profile_delete_note(self):
|
|
person = self.get_instance()
|
|
form = self.make_note_form('delete', person)
|
|
if form.validate(newstyle=True):
|
|
self.delete_note(person, form)
|
|
self.Session.flush()
|
|
return self.profile_delete_note_success(person)
|
|
else:
|
|
return self.profile_delete_note_failure(person, form)
|
|
|
|
def delete_note(self, person, form):
|
|
note = self.Session.query(model.PersonNote).get(form.validated['uuid'])
|
|
self.Session.delete(note)
|
|
|
|
def profile_delete_note_success(self, person):
|
|
return self.redirect(self.get_action_url('view_profile', person))
|
|
|
|
def profile_delete_note_failure(self, person, form):
|
|
return self.redirect(self.get_action_url('view_profile', person))
|
|
|
|
def make_user(self):
|
|
uuid = self.request.POST['person_uuid']
|
|
person = self.Session.query(model.Person).get(uuid)
|
|
if not person:
|
|
return self.notfound()
|
|
if person.users:
|
|
raise RuntimeError("person {} already has {} user accounts: ".format(
|
|
person.uuid, len(person.users), person))
|
|
user = model.User()
|
|
user.username = api.make_username(person)
|
|
user.person = person
|
|
user.active = False
|
|
self.Session.add(user)
|
|
self.Session.flush()
|
|
self.request.session.flash("User has been created: {}".format(user.username))
|
|
return self.redirect(self.request.route_url('users.view', uuid=user.uuid))
|
|
|
|
@classmethod
|
|
def defaults(cls, config):
|
|
cls._people_defaults(config)
|
|
cls._defaults(config)
|
|
|
|
@classmethod
|
|
def _people_defaults(cls, config):
|
|
permission_prefix = cls.get_permission_prefix()
|
|
route_prefix = cls.get_route_prefix()
|
|
url_prefix = cls.get_url_prefix()
|
|
model_key = cls.get_model_key()
|
|
model_title = cls.get_model_title()
|
|
|
|
# "profile" perms
|
|
# TODO: should let view class (or config) determine which of these are available
|
|
config.add_tailbone_permission_group('people_profile', "People Profile View")
|
|
config.add_tailbone_permission('people_profile', 'people_profile.toggle_employee',
|
|
"Toggle the person's Employee status")
|
|
config.add_tailbone_permission('people_profile', 'people_profile.edit_employee_history',
|
|
"Edit the person's Employee History records")
|
|
|
|
# view profile
|
|
config.add_tailbone_permission(permission_prefix, '{}.view_profile'.format(permission_prefix),
|
|
"View full \"profile\" for {}".format(model_title))
|
|
config.add_route('{}.view_profile'.format(route_prefix), '{}/{{{}}}/profile'.format(url_prefix, model_key),
|
|
request_method='GET')
|
|
config.add_view(cls, attr='view_profile', route_name='{}.view_profile'.format(route_prefix),
|
|
permission='{}.view_profile'.format(permission_prefix))
|
|
|
|
# manage notes from profile view
|
|
if cls.manage_notes_from_profile_view:
|
|
|
|
# add note
|
|
config.add_tailbone_permission('people_profile', 'people_profile.add_note',
|
|
"Add new {} Note records".format(model_title))
|
|
config.add_route('{}.profile_add_note'.format(route_prefix), '{}/{{{}}}/profile/new-note'.format(url_prefix, model_key),
|
|
request_method='POST')
|
|
config.add_view(cls, attr='profile_add_note', route_name='{}.profile_add_note'.format(route_prefix),
|
|
permission='people_profile.add_note')
|
|
|
|
# edit note
|
|
config.add_tailbone_permission('people_profile', 'people_profile.edit_note',
|
|
"Edit {} Note records".format(model_title))
|
|
config.add_route('{}.profile_edit_note'.format(route_prefix), '{}/{{{}}}/profile/edit-note'.format(url_prefix, model_key),
|
|
request_method='POST')
|
|
config.add_view(cls, attr='profile_edit_note', route_name='{}.profile_edit_note'.format(route_prefix),
|
|
permission='people_profile.edit_note')
|
|
|
|
# delete note
|
|
config.add_tailbone_permission('people_profile', 'people_profile.delete_note',
|
|
"Delete {} Note records".format(model_title))
|
|
config.add_route('{}.profile_delete_note'.format(route_prefix), '{}/{{{}}}/profile/delete-note'.format(url_prefix, model_key),
|
|
request_method='POST')
|
|
config.add_view(cls, attr='profile_delete_note', route_name='{}.profile_delete_note'.format(route_prefix),
|
|
permission='people_profile.delete_note')
|
|
|
|
# make user for person
|
|
config.add_route('{}.make_user'.format(route_prefix), '{}/make-user'.format(url_prefix),
|
|
request_method='POST')
|
|
config.add_view(cls, attr='make_user', route_name='{}.make_user'.format(route_prefix),
|
|
permission='users.create')
|
|
|
|
|
|
class PeopleAutocomplete(AutocompleteView):
|
|
|
|
mapped_class = model.Person
|
|
fieldname = 'display_name'
|
|
|
|
|
|
class PeopleEmployeesAutocomplete(PeopleAutocomplete):
|
|
"""
|
|
Autocomplete view for the Person model, but restricted to return only
|
|
results for people who are employees.
|
|
"""
|
|
|
|
def filter_query(self, q):
|
|
return q.join(model.Employee)
|
|
|
|
|
|
class PersonNoteView(MasterView):
|
|
"""
|
|
Master view for the PersonNote class.
|
|
"""
|
|
model_class = model.PersonNote
|
|
route_prefix = 'person_notes'
|
|
url_prefix = '/people/notes'
|
|
has_versions = True
|
|
|
|
grid_columns = [
|
|
'person',
|
|
'type',
|
|
'subject',
|
|
'created',
|
|
'created_by',
|
|
]
|
|
|
|
form_fields = [
|
|
'person',
|
|
'type',
|
|
'subject',
|
|
'text',
|
|
'created',
|
|
'created_by',
|
|
]
|
|
|
|
def get_instance_title(self, note):
|
|
return note.subject or "(no subject)"
|
|
|
|
def configure_grid(self, g):
|
|
super(PersonNoteView, self).configure_grid(g)
|
|
|
|
# person
|
|
g.set_joiner('person', lambda q: q.join(model.Person,
|
|
model.Person.uuid == model.PersonNote.parent_uuid))
|
|
g.set_sorter('person', model.Person.display_name)
|
|
g.set_filter('person', model.Person.display_name, label="Person Name")
|
|
|
|
# created_by
|
|
CreatorPerson = orm.aliased(model.Person)
|
|
g.set_joiner('created_by', lambda q: q.join(model.User).outerjoin(CreatorPerson,
|
|
CreatorPerson.uuid == model.User.person_uuid))
|
|
g.set_sorter('created_by', CreatorPerson.display_name)
|
|
|
|
g.set_sort_defaults('created', 'desc')
|
|
|
|
g.set_link('person')
|
|
g.set_link('subject')
|
|
g.set_link('created')
|
|
|
|
def configure_form(self, f):
|
|
super(PersonNoteView, self).configure_form(f)
|
|
|
|
# person
|
|
f.set_readonly('person')
|
|
f.set_renderer('person', self.render_person)
|
|
|
|
# created
|
|
f.set_readonly('created')
|
|
|
|
# created_by
|
|
f.set_readonly('created_by')
|
|
f.set_renderer('created_by', self.render_user)
|
|
|
|
|
|
@colander.deferred
|
|
def valid_note_uuid(node, kw):
|
|
session = kw['session']
|
|
person_uuid = kw['person_uuid']
|
|
def validate(node, value):
|
|
note = session.query(model.PersonNote).get(value)
|
|
if not note:
|
|
raise colander.Invalid(node, "Note not found")
|
|
if note.person.uuid != person_uuid:
|
|
raise colander.Invalid(node, "Note is for the wrong person")
|
|
return note.uuid
|
|
return validate
|
|
|
|
|
|
class NoteSchema(colander.Schema):
|
|
|
|
uuid = colander.SchemaNode(colander.String(),
|
|
validator=valid_note_uuid)
|
|
|
|
note_type = colander.SchemaNode(colander.String())
|
|
|
|
note_subject = colander.SchemaNode(colander.String(), missing='')
|
|
|
|
note_text = colander.SchemaNode(colander.String(), missing='')
|
|
|
|
|
|
def includeme(config):
|
|
|
|
# autocomplete
|
|
config.add_route('people.autocomplete', '/people/autocomplete')
|
|
config.add_view(PeopleAutocomplete, route_name='people.autocomplete',
|
|
renderer='json', permission='people.list')
|
|
config.add_route('people.autocomplete.employees', '/people/autocomplete/employees')
|
|
config.add_view(PeopleEmployeesAutocomplete, route_name='people.autocomplete.employees',
|
|
renderer='json', permission='people.list')
|
|
|
|
PeopleView.defaults(config)
|
|
PersonNoteView.defaults(config)
|