Rebranded to Tailbone.
This commit is contained in:
parent
47944767dc
commit
40efd8a3bc
111 changed files with 188 additions and 209 deletions
47
tailbone/views/__init__.py
Normal file
47
tailbone/views/__init__.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Pyramid Views
|
||||
"""
|
||||
|
||||
from .core import *
|
||||
from .grids import *
|
||||
from .crud import *
|
||||
from .autocomplete import *
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('tailbone.views.batches')
|
||||
# config.include('tailbone.views.categories')
|
||||
config.include('tailbone.views.customergroups')
|
||||
config.include('tailbone.views.customers')
|
||||
config.include('tailbone.views.departments')
|
||||
config.include('tailbone.views.employees')
|
||||
config.include('tailbone.views.labels')
|
||||
config.include('tailbone.views.products')
|
||||
config.include('tailbone.views.roles')
|
||||
config.include('tailbone.views.stores')
|
||||
config.include('tailbone.views.subdepartments')
|
||||
config.include('tailbone.views.vendors')
|
61
tailbone/views/autocomplete.py
Normal file
61
tailbone/views/autocomplete.py
Normal file
|
@ -0,0 +1,61 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Autocomplete View
|
||||
"""
|
||||
|
||||
from .core import View
|
||||
from .. import Session
|
||||
|
||||
|
||||
__all__ = ['AutocompleteView']
|
||||
|
||||
|
||||
class AutocompleteView(View):
|
||||
|
||||
def filter_query(self, q):
|
||||
return q
|
||||
|
||||
def make_query(self, term):
|
||||
q = Session.query(self.mapped_class)
|
||||
q = self.filter_query(q)
|
||||
q = q.filter(getattr(self.mapped_class, self.fieldname).ilike('%%%s%%' % term))
|
||||
q = q.order_by(getattr(self.mapped_class, self.fieldname))
|
||||
return q
|
||||
|
||||
def query(self, term):
|
||||
return self.make_query(term)
|
||||
|
||||
def display(self, instance):
|
||||
return getattr(instance, self.fieldname)
|
||||
|
||||
def __call__(self):
|
||||
term = self.request.params.get('term')
|
||||
if term:
|
||||
term = term.strip()
|
||||
if not term:
|
||||
return []
|
||||
results = self.query(term).all()
|
||||
return [{'label': self.display(x), 'value': x.uuid} for x in results]
|
35
tailbone/views/batches/__init__.py
Normal file
35
tailbone/views/batches/__init__.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Batch Views
|
||||
"""
|
||||
|
||||
from .params import *
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('tailbone.views.batches.core')
|
||||
config.include('tailbone.views.batches.params')
|
||||
config.include('tailbone.views.batches.rows')
|
203
tailbone/views/batches/core.py
Normal file
203
tailbone/views/batches/core.py
Normal file
|
@ -0,0 +1,203 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Core Batch Views
|
||||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
from pyramid.renderers import render_to_response
|
||||
|
||||
from webhelpers.html import tags
|
||||
|
||||
from edbob.pyramid.forms import PrettyDateTimeFieldRenderer
|
||||
from ...forms import EnumFieldRenderer
|
||||
from ...grids.search import BooleanSearchFilter
|
||||
from edbob.pyramid.progress import SessionProgress
|
||||
from .. import SearchableAlchemyGridView, CrudView, View
|
||||
|
||||
import rattail
|
||||
from rattail import batches
|
||||
from ... import Session
|
||||
from rattail.db.model import Batch
|
||||
from rattail.threads import Thread
|
||||
|
||||
|
||||
class BatchesGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Batch
|
||||
config_prefix = 'batches'
|
||||
sort = 'id'
|
||||
|
||||
def filter_map(self):
|
||||
|
||||
def executed_is(q, v):
|
||||
if v == 'True':
|
||||
return q.filter(Batch.executed != None)
|
||||
else:
|
||||
return q.filter(Batch.executed == None)
|
||||
|
||||
def executed_isnot(q, v):
|
||||
if v == 'True':
|
||||
return q.filter(Batch.executed == None)
|
||||
else:
|
||||
return q.filter(Batch.executed != None)
|
||||
|
||||
return self.make_filter_map(
|
||||
exact=['id'],
|
||||
ilike=['source', 'destination', 'description'],
|
||||
executed={
|
||||
'is': executed_is,
|
||||
'nt': executed_isnot,
|
||||
})
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
filter_label_id="ID",
|
||||
filter_factory_executed=BooleanSearchFilter,
|
||||
include_filter_executed=True,
|
||||
filter_type_executed='is',
|
||||
executed='False')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('source', 'id', 'destination', 'description', 'executed')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.executed.set(renderer=PrettyDateTimeFieldRenderer(from_='utc'))
|
||||
g.configure(
|
||||
include=[
|
||||
g.source,
|
||||
g.id.label("ID"),
|
||||
g.destination,
|
||||
g.description,
|
||||
g.rowcount.label("Row Count"),
|
||||
g.executed,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('batches.read'):
|
||||
def rows(row):
|
||||
return tags.link_to("View Rows", self.request.route_url(
|
||||
'batch.rows', uuid=row.uuid))
|
||||
g.add_column('rows', "", rows)
|
||||
g.viewable = True
|
||||
g.view_route_name = 'batch.read'
|
||||
if self.request.has_perm('batches.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'batch.update'
|
||||
if self.request.has_perm('batches.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'batch.delete'
|
||||
return g
|
||||
|
||||
|
||||
class BatchCrud(CrudView):
|
||||
|
||||
mapped_class = Batch
|
||||
home_route = 'batches'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.action_type.set(renderer=EnumFieldRenderer(rattail.BATCH_ACTION))
|
||||
fs.executed.set(renderer=PrettyDateTimeFieldRenderer(from_='utc'))
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.source,
|
||||
fs.id.label("ID"),
|
||||
fs.destination,
|
||||
fs.action_type,
|
||||
fs.description,
|
||||
fs.rowcount.label("Row Count").readonly(),
|
||||
fs.executed.readonly(),
|
||||
])
|
||||
return fs
|
||||
|
||||
def post_delete(self, batch):
|
||||
batch.drop_table()
|
||||
|
||||
|
||||
class ExecuteBatch(View):
|
||||
|
||||
def execute_batch(self, batch, progress):
|
||||
from rattail.db import Session
|
||||
session = Session()
|
||||
batch = session.merge(batch)
|
||||
|
||||
if not batch.execute(progress):
|
||||
session.rollback()
|
||||
session.close()
|
||||
return
|
||||
|
||||
session.commit()
|
||||
session.refresh(batch)
|
||||
session.close()
|
||||
|
||||
progress.session.load()
|
||||
progress.session['complete'] = True
|
||||
progress.session['success_msg'] = "Batch \"%s\" has been executed." % batch.description
|
||||
progress.session['success_url'] = self.request.route_url('batches')
|
||||
progress.session.save()
|
||||
|
||||
def __call__(self):
|
||||
uuid = self.request.matchdict['uuid']
|
||||
batch = Session.query(Batch).get(uuid) if uuid else None
|
||||
if not batch:
|
||||
return HTTPFound(location=self.request.route_url('batches'))
|
||||
|
||||
progress = SessionProgress(self.request.session, 'batch.execute')
|
||||
thread = Thread(target=self.execute_batch, args=(batch, progress))
|
||||
thread.start()
|
||||
kwargs = {
|
||||
'key': 'batch.execute',
|
||||
'cancel_url': self.request.route_url('batch.rows', uuid=batch.uuid),
|
||||
'cancel_msg': "Batch execution was canceled.",
|
||||
}
|
||||
return render_to_response('/progress.mako', kwargs, request=self.request)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('batches', '/batches')
|
||||
config.add_view(BatchesGrid, route_name='batches',
|
||||
renderer='/batches/index.mako',
|
||||
permission='batches.list')
|
||||
|
||||
config.add_route('batch.read', '/batches/{uuid}')
|
||||
config.add_view(BatchCrud, attr='read',
|
||||
route_name='batch.read',
|
||||
renderer='/batches/read.mako',
|
||||
permission='batches.read')
|
||||
|
||||
config.add_route('batch.update', '/batches/{uuid}/edit')
|
||||
config.add_view(BatchCrud, attr='update', route_name='batch.update',
|
||||
renderer='/batches/crud.mako',
|
||||
permission='batches.update')
|
||||
|
||||
config.add_route('batch.delete', '/batches/{uuid}/delete')
|
||||
config.add_view(BatchCrud, attr='delete', route_name='batch.delete',
|
||||
permission='batches.delete')
|
||||
|
||||
config.add_route('batch.execute', '/batches/{uuid}/execute')
|
||||
config.add_view(ExecuteBatch, route_name='batch.execute',
|
||||
permission='batches.execute')
|
52
tailbone/views/batches/params/__init__.py
Normal file
52
tailbone/views/batches/params/__init__.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Batch Parameter Views
|
||||
"""
|
||||
|
||||
from ... import View
|
||||
|
||||
|
||||
__all__ = ['BatchParamsView']
|
||||
|
||||
|
||||
class BatchParamsView(View):
|
||||
|
||||
provider_name = None
|
||||
|
||||
def render_kwargs(self):
|
||||
return {}
|
||||
|
||||
def __call__(self):
|
||||
if self.request.POST:
|
||||
if self.set_batch_params():
|
||||
return HTTPFound(location=self.request.get_referer())
|
||||
kwargs = self.render_kwargs()
|
||||
kwargs['provider'] = self.provider_name
|
||||
return kwargs
|
||||
|
||||
|
||||
def includeme(config):
|
||||
config.include('tailbone.views.batches.params.labels')
|
51
tailbone/views/batches/params/labels.py
Normal file
51
tailbone/views/batches/params/labels.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Print Labels Batch
|
||||
"""
|
||||
|
||||
from .... import Session
|
||||
|
||||
import rattail
|
||||
from . import BatchParamsView
|
||||
|
||||
|
||||
class PrintLabels(BatchParamsView):
|
||||
|
||||
provider_name = 'print_labels'
|
||||
|
||||
def render_kwargs(self):
|
||||
q = Session.query(rattail.LabelProfile)
|
||||
q = q.order_by(rattail.LabelProfile.ordinal)
|
||||
profiles = [(x.code, x.description) for x in q]
|
||||
return {'label_profiles': profiles}
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('batch_params.print_labels', '/batches/params/print-labels')
|
||||
config.add_view(PrintLabels, route_name='batch_params.print_labels',
|
||||
renderer='/batches/params/print_labels.mako',
|
||||
permission='batches.print_labels')
|
222
tailbone/views/batches/rows.py
Normal file
222
tailbone/views/batches/rows.py
Normal file
|
@ -0,0 +1,222 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Batch Row Views
|
||||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
from ... import Session
|
||||
from .. import SearchableAlchemyGridView, CrudView
|
||||
|
||||
import rattail
|
||||
from ...forms import GPCFieldRenderer
|
||||
|
||||
|
||||
def field_with_renderer(field, column):
|
||||
|
||||
if column.sil_name == 'F01': # UPC
|
||||
field = field.with_renderer(GPCFieldRenderer)
|
||||
|
||||
elif column.sil_name == 'F95': # Shelf Tag Type
|
||||
q = Session.query(rattail.LabelProfile)
|
||||
q = q.order_by(rattail.LabelProfile.ordinal)
|
||||
field = field.dropdown(options=[(x.description, x.code) for x in q])
|
||||
|
||||
return field
|
||||
|
||||
|
||||
def BatchRowsGrid(request):
|
||||
uuid = request.matchdict['uuid']
|
||||
batch = Session.query(rattail.Batch).get(uuid) if uuid else None
|
||||
if not batch:
|
||||
return HTTPFound(location=request.route_url('batches'))
|
||||
|
||||
class BatchRowsGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = batch.rowclass
|
||||
config_prefix = 'batch.%s' % batch.uuid
|
||||
sort = 'ordinal'
|
||||
|
||||
def filter_map(self):
|
||||
fmap = self.make_filter_map()
|
||||
for column in batch.columns:
|
||||
if column.visible:
|
||||
if column.data_type.startswith('CHAR'):
|
||||
fmap[column.name] = self.filter_ilike(
|
||||
getattr(batch.rowclass, column.name))
|
||||
else:
|
||||
fmap[column.name] = self.filter_exact(
|
||||
getattr(batch.rowclass, column.name))
|
||||
return fmap
|
||||
|
||||
def filter_config(self):
|
||||
config = self.make_filter_config()
|
||||
for column in batch.columns:
|
||||
if column.visible:
|
||||
config['filter_label_%s' % column.name] = column.display_name
|
||||
return config
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
|
||||
include = [g.ordinal.label("Row")]
|
||||
for column in batch.columns:
|
||||
if column.visible:
|
||||
field = getattr(g, column.name)
|
||||
field = field_with_renderer(field, column)
|
||||
field = field.label(column.display_name)
|
||||
include.append(field)
|
||||
g.column_titles[field.key] = '%s - %s - %s' % (
|
||||
column.sil_name, column.description, column.data_type)
|
||||
|
||||
g.configure(include=include, readonly=True)
|
||||
|
||||
route_kwargs = lambda x: {'batch_uuid': x.batch.uuid, 'uuid': x.uuid}
|
||||
|
||||
if self.request.has_perm('batch_rows.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'batch_row.read'
|
||||
g.view_route_kwargs = route_kwargs
|
||||
|
||||
if self.request.has_perm('batch_rows.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'batch_row.update'
|
||||
g.edit_route_kwargs = route_kwargs
|
||||
|
||||
if self.request.has_perm('batch_rows.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'batch_row.delete'
|
||||
g.delete_route_kwargs = route_kwargs
|
||||
|
||||
return g
|
||||
|
||||
def render_kwargs(self):
|
||||
return {'batch': batch}
|
||||
|
||||
grid = BatchRowsGrid(request)
|
||||
grid.batch = batch
|
||||
return grid
|
||||
|
||||
|
||||
def batch_rows_grid(request):
|
||||
result = BatchRowsGrid(request)
|
||||
if isinstance(result, HTTPFound):
|
||||
return result
|
||||
return result()
|
||||
|
||||
|
||||
def batch_rows_delete(request):
|
||||
grid = BatchRowsGrid(request)
|
||||
grid._filter_config = grid.filter_config()
|
||||
rows = grid.make_query()
|
||||
count = rows.count()
|
||||
rows.delete(synchronize_session=False)
|
||||
grid.batch.rowcount -= count
|
||||
request.session.flash("Deleted %d rows from batch." % count)
|
||||
return HTTPFound(location=request.route_url('batch.rows', uuid=grid.batch.uuid))
|
||||
|
||||
|
||||
def batch_row_crud(request, attr):
|
||||
batch_uuid = request.matchdict['batch_uuid']
|
||||
batch = Session.query(rattail.Batch).get(batch_uuid)
|
||||
if not batch:
|
||||
return HTTPFound(location=request.route_url('batches'))
|
||||
|
||||
row_uuid = request.matchdict['uuid']
|
||||
row = Session.query(batch.rowclass).get(row_uuid)
|
||||
if not row:
|
||||
return HTTPFound(location=request.route_url('batch.read', uuid=batch.uuid))
|
||||
|
||||
class BatchRowCrud(CrudView):
|
||||
|
||||
mapped_class = batch.rowclass
|
||||
pretty_name = "Batch Row"
|
||||
|
||||
@property
|
||||
def home_url(self):
|
||||
return self.request.route_url('batch.rows', uuid=batch.uuid)
|
||||
|
||||
@property
|
||||
def cancel_url(self):
|
||||
return self.home_url
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
|
||||
include = [fs.ordinal.label("Row Number").readonly()]
|
||||
for column in batch.columns:
|
||||
field = getattr(fs, column.name)
|
||||
field = field_with_renderer(field, column)
|
||||
field = field.label(column.display_name)
|
||||
include.append(field)
|
||||
|
||||
fs.configure(include=include)
|
||||
return fs
|
||||
|
||||
def flash_delete(self, row):
|
||||
self.request.session.flash("Batch Row %d has been deleted."
|
||||
% row.ordinal)
|
||||
|
||||
def post_delete(self, model):
|
||||
batch.rowcount -= 1
|
||||
|
||||
crud = BatchRowCrud(request)
|
||||
return getattr(crud, attr)()
|
||||
|
||||
def batch_row_read(request):
|
||||
return batch_row_crud(request, 'read')
|
||||
|
||||
def batch_row_update(request):
|
||||
return batch_row_crud(request, 'update')
|
||||
|
||||
def batch_row_delete(request):
|
||||
return batch_row_crud(request, 'delete')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('batch.rows', '/batches/{uuid}/rows')
|
||||
config.add_view(batch_rows_grid, route_name='batch.rows',
|
||||
renderer='/batches/rows/index.mako',
|
||||
permission='batches.read')
|
||||
|
||||
config.add_route('batch.rows.delete', '/batches/{uuid}/rows/delete')
|
||||
config.add_view(batch_rows_delete, route_name='batch.rows.delete',
|
||||
permission='batch_rows.delete')
|
||||
|
||||
config.add_route('batch_row.read', '/batches/{batch_uuid}/{uuid}')
|
||||
config.add_view(batch_row_read, route_name='batch_row.read',
|
||||
renderer='/batches/rows/crud.mako',
|
||||
permission='batch_rows.read')
|
||||
|
||||
config.add_route('batch_row.update', '/batches/{batch_uuid}/{uuid}/edit')
|
||||
config.add_view(batch_row_update, route_name='batch_row.update',
|
||||
renderer='/batches/rows/crud.mako',
|
||||
permission='batch_rows.update')
|
||||
|
||||
config.add_route('batch_row.delete', '/batches/{batch_uuid}/{uuid}/delete')
|
||||
config.add_view(batch_row_delete, route_name='batch_row.delete',
|
||||
permission='batch_rows.delete')
|
124
tailbone/views/brands.py
Normal file
124
tailbone/views/brands.py
Normal file
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Brand Views
|
||||
"""
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView, AutocompleteView
|
||||
|
||||
from rattail.db.model import Brand
|
||||
|
||||
|
||||
class BrandsGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Brand
|
||||
config_prefix = 'brands'
|
||||
sort = 'name'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(ilike=['name'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('brands.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'brand.read'
|
||||
if self.request.has_perm('brands.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'brand.update'
|
||||
if self.request.has_perm('brands.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'brand.delete'
|
||||
return g
|
||||
|
||||
|
||||
class BrandCrud(CrudView):
|
||||
|
||||
mapped_class = Brand
|
||||
home_route = 'brands'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.name,
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
class BrandsAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = Brand
|
||||
fieldname = 'name'
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('brands', '/brands')
|
||||
config.add_route('brands.autocomplete', '/brands/autocomplete')
|
||||
config.add_route('brand.create', '/brands/new')
|
||||
config.add_route('brand.read', '/brands/{uuid}')
|
||||
config.add_route('brand.update', '/brands/{uuid}/edit')
|
||||
config.add_route('brand.delete', '/brands/{uuid}/delete')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(BrandsGrid,
|
||||
route_name='brands',
|
||||
renderer='/brands/index.mako',
|
||||
permission='brands.list')
|
||||
config.add_view(BrandsAutocomplete,
|
||||
route_name='brands.autocomplete',
|
||||
renderer='json',
|
||||
permission='brands.list')
|
||||
config.add_view(BrandCrud, attr='create',
|
||||
route_name='brand.create',
|
||||
renderer='/brands/crud.mako',
|
||||
permission='brands.create')
|
||||
config.add_view(BrandCrud, attr='read',
|
||||
route_name='brand.read',
|
||||
renderer='/brands/crud.mako',
|
||||
permission='brands.read')
|
||||
config.add_view(BrandCrud, attr='update',
|
||||
route_name='brand.update',
|
||||
renderer='/brands/crud.mako',
|
||||
permission='brands.update')
|
||||
config.add_view(BrandCrud, attr='delete',
|
||||
route_name='brand.delete',
|
||||
permission='brands.delete')
|
110
tailbone/views/categories.py
Normal file
110
tailbone/views/categories.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Category Views
|
||||
"""
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
|
||||
from rattail.db.model import Category
|
||||
|
||||
|
||||
class CategoriesGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Category
|
||||
config_prefix = 'categories'
|
||||
sort = 'number'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(exact=['number'], ilike=['name'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('number', 'name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.number,
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('categories.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'category.read'
|
||||
if self.request.has_perm('categories.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'category.update'
|
||||
if self.request.has_perm('categories.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'category.delete'
|
||||
return g
|
||||
|
||||
|
||||
class CategoryCrud(CrudView):
|
||||
|
||||
mapped_class = Category
|
||||
home_route = 'categories'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.number,
|
||||
fs.name,
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('categories', '/categories')
|
||||
config.add_view(CategoriesGrid, route_name='categories',
|
||||
renderer='/categories/index.mako',
|
||||
permission='categories.list')
|
||||
|
||||
config.add_route('category.create', '/categories/new')
|
||||
config.add_view(CategoryCrud, attr='create', route_name='category.create',
|
||||
renderer='/categories/crud.mako',
|
||||
permission='categories.create')
|
||||
|
||||
config.add_route('category.read', '/categories/{uuid}')
|
||||
config.add_view(CategoryCrud, attr='read', route_name='category.read',
|
||||
renderer='/categories/crud.mako',
|
||||
permission='categories.read')
|
||||
|
||||
config.add_route('category.update', '/categories/{uuid}/edit')
|
||||
config.add_view(CategoryCrud, attr='update', route_name='category.update',
|
||||
renderer='/categories/crud.mako',
|
||||
permission='categories.update')
|
||||
|
||||
config.add_route('category.delete', '/categories/{uuid}/delete')
|
||||
config.add_view(CategoryCrud, attr='delete', route_name='category.delete',
|
||||
permission='categories.delete')
|
35
tailbone/views/core.py
Normal file
35
tailbone/views/core.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Core View
|
||||
"""
|
||||
|
||||
class View(object):
|
||||
"""
|
||||
Base for all class-based views.
|
||||
"""
|
||||
|
||||
def __init__(self, request):
|
||||
self.request = request
|
212
tailbone/views/crud.py
Normal file
212
tailbone/views/crud.py
Normal file
|
@ -0,0 +1,212 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
CRUD View
|
||||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
import formalchemy
|
||||
|
||||
from .. import Session
|
||||
from edbob.pyramid.forms.formalchemy import AlchemyForm
|
||||
from .core import View
|
||||
from edbob.util import requires_impl, prettify
|
||||
|
||||
|
||||
__all__ = ['CrudView']
|
||||
|
||||
|
||||
class CrudView(View):
|
||||
|
||||
readonly = False
|
||||
allow_successive_creates = False
|
||||
update_cancel_route = None
|
||||
|
||||
@property
|
||||
@requires_impl(is_property=True)
|
||||
def mapped_class(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def pretty_name(self):
|
||||
return self.mapped_class.__name__
|
||||
|
||||
@property
|
||||
@requires_impl(is_property=True)
|
||||
def home_route(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def home_url(self):
|
||||
return self.request.route_url(self.home_route)
|
||||
|
||||
@property
|
||||
def cancel_route(self):
|
||||
return self.home_route
|
||||
|
||||
@property
|
||||
def cancel_url(self):
|
||||
return self.request.route_url(self.cancel_route)
|
||||
|
||||
def make_fieldset(self, model, **kwargs):
|
||||
kwargs.setdefault('session', Session())
|
||||
kwargs.setdefault('request', self.request)
|
||||
fieldset = formalchemy.FieldSet(model, **kwargs)
|
||||
fieldset.prettify = prettify
|
||||
return fieldset
|
||||
|
||||
def fieldset(self, model):
|
||||
return self.make_fieldset(model)
|
||||
|
||||
def make_form(self, model, **kwargs):
|
||||
if self.readonly:
|
||||
self.creating = False
|
||||
self.updating = False
|
||||
else:
|
||||
self.creating = model is self.mapped_class
|
||||
self.updating = not self.creating
|
||||
|
||||
fieldset = self.fieldset(model)
|
||||
kwargs.setdefault('pretty_name', self.pretty_name)
|
||||
kwargs.setdefault('action_url', self.request.current_route_url())
|
||||
if self.updating and self.update_cancel_route:
|
||||
kwargs.setdefault('cancel_url', self.request.route_url(
|
||||
self.update_cancel_route, uuid=model.uuid))
|
||||
else:
|
||||
kwargs.setdefault('cancel_url', self.cancel_url)
|
||||
kwargs.setdefault('creating', self.creating)
|
||||
kwargs.setdefault('updating', self.updating)
|
||||
form = AlchemyForm(self.request, fieldset, **kwargs)
|
||||
|
||||
if form.creating:
|
||||
if hasattr(self, 'create_label'):
|
||||
form.create_label = self.create_label
|
||||
if self.allow_successive_creates:
|
||||
form.allow_successive_creates = True
|
||||
if hasattr(self, 'successive_create_label'):
|
||||
form.successive_create_label = self.successive_create_label
|
||||
|
||||
return form
|
||||
|
||||
def form(self, model):
|
||||
return self.make_form(model)
|
||||
|
||||
def crud(self, model, readonly=False):
|
||||
|
||||
if readonly:
|
||||
self.readonly = True
|
||||
|
||||
form = self.form(model)
|
||||
if readonly:
|
||||
form.readonly = True
|
||||
|
||||
if not form.readonly and self.request.POST:
|
||||
if form.validate():
|
||||
form.save()
|
||||
|
||||
result = self.post_save(form)
|
||||
if result:
|
||||
return result
|
||||
|
||||
if form.creating:
|
||||
self.flash_create(form.fieldset.model)
|
||||
else:
|
||||
self.flash_update(form.fieldset.model)
|
||||
|
||||
if (form.creating and form.allow_successive_creates
|
||||
and self.request.params.get('create_and_continue')):
|
||||
return HTTPFound(location=self.request.current_route_url())
|
||||
|
||||
return HTTPFound(location=self.post_save_url(form))
|
||||
|
||||
self.validation_failed(form)
|
||||
|
||||
kwargs = self.template_kwargs(form)
|
||||
kwargs['form'] = form
|
||||
return kwargs
|
||||
|
||||
def template_kwargs(self, form):
|
||||
return {}
|
||||
|
||||
def post_save(self, form):
|
||||
pass
|
||||
|
||||
def post_save_url(self, form):
|
||||
return self.home_url
|
||||
|
||||
def validation_failed(self, form):
|
||||
pass
|
||||
|
||||
def flash_create(self, model):
|
||||
self.request.session.flash("%s \"%s\" has been created." %
|
||||
(self.pretty_name, model))
|
||||
|
||||
def flash_delete(self, model):
|
||||
self.request.session.flash("%s \"%s\" has been deleted." %
|
||||
(self.pretty_name, model))
|
||||
|
||||
def flash_update(self, model):
|
||||
self.request.session.flash("%s \"%s\" has been updated." %
|
||||
(self.pretty_name, model))
|
||||
|
||||
def create(self):
|
||||
return self.crud(self.mapped_class)
|
||||
|
||||
def get_model(self, key):
|
||||
model = Session.query(self.mapped_class).get(key)
|
||||
return model
|
||||
|
||||
def read(self):
|
||||
key = self.request.matchdict['uuid']
|
||||
model = self.get_model(key)
|
||||
if not model:
|
||||
return HTTPFound(location=self.home_url)
|
||||
return self.crud(model, readonly=True)
|
||||
|
||||
def update(self):
|
||||
uuid = self.request.matchdict['uuid']
|
||||
model = Session.query(self.mapped_class).get(uuid) if uuid else None
|
||||
assert model
|
||||
return self.crud(model)
|
||||
|
||||
def pre_delete(self, model):
|
||||
pass
|
||||
|
||||
def post_delete(self, model):
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
uuid = self.request.matchdict['uuid']
|
||||
model = Session.query(self.mapped_class).get(uuid) if uuid else None
|
||||
assert model
|
||||
result = self.pre_delete(model)
|
||||
if result:
|
||||
return result
|
||||
Session.delete(model)
|
||||
Session.flush() # Don't set flash message if delete fails.
|
||||
self.post_delete(model)
|
||||
self.flash_delete(model)
|
||||
return HTTPFound(location=self.home_url)
|
119
tailbone/views/customergroups.py
Normal file
119
tailbone/views/customergroups.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
CustomerGroup Views
|
||||
"""
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
|
||||
from .. import Session
|
||||
from rattail.db.model import CustomerGroup, CustomerGroupAssignment
|
||||
|
||||
|
||||
class CustomerGroupsGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = CustomerGroup
|
||||
config_prefix = 'customer_groups'
|
||||
sort = 'name'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(ilike=['name'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('id', 'name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.id.label("ID"),
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('customer_groups.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'customer_group.read'
|
||||
if self.request.has_perm('customer_groups.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'customer_group.update'
|
||||
if self.request.has_perm('customer_groups.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'customer_group.delete'
|
||||
return g
|
||||
|
||||
|
||||
class CustomerGroupCrud(CrudView):
|
||||
|
||||
mapped_class = CustomerGroup
|
||||
home_route = 'customer_groups'
|
||||
pretty_name = "Customer Group"
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.id.label("ID"),
|
||||
fs.name,
|
||||
])
|
||||
return fs
|
||||
|
||||
def pre_delete(self, group):
|
||||
# First remove customer associations.
|
||||
q = Session.query(CustomerGroupAssignment)\
|
||||
.filter(CustomerGroupAssignment.group == group)
|
||||
for assignment in q:
|
||||
Session.delete(assignment)
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('customer_groups', '/customer-groups')
|
||||
config.add_route('customer_group.create', '/customer-groups/new')
|
||||
config.add_route('customer_group.read', '/customer-groups/{uuid}')
|
||||
config.add_route('customer_group.update', '/customer-groups/{uuid}/edit')
|
||||
config.add_route('customer_group.delete', '/customer-groups/{uuid}/delete')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(CustomerGroupsGrid, route_name='customer_groups',
|
||||
renderer='/customergroups/index.mako',
|
||||
permission='customer_groups.list')
|
||||
config.add_view(CustomerGroupCrud, attr='create', route_name='customer_group.create',
|
||||
renderer='/customergroups/crud.mako',
|
||||
permission='customer_groups.create')
|
||||
config.add_view(CustomerGroupCrud, attr='read', route_name='customer_group.read',
|
||||
renderer='/customergroups/crud.mako',
|
||||
permission='customer_groups.read')
|
||||
config.add_view(CustomerGroupCrud, attr='update', route_name='customer_group.update',
|
||||
renderer='/customergroups/crud.mako',
|
||||
permission='customer_groups.update')
|
||||
config.add_view(CustomerGroupCrud, attr='delete', route_name='customer_group.delete',
|
||||
permission='customer_groups.delete')
|
165
tailbone/views/customers.py
Normal file
165
tailbone/views/customers.py
Normal file
|
@ -0,0 +1,165 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Customer Views
|
||||
"""
|
||||
|
||||
from sqlalchemy import and_
|
||||
|
||||
from edbob.enum import EMAIL_PREFERENCE
|
||||
|
||||
from . import SearchableAlchemyGridView
|
||||
from ..forms import EnumFieldRenderer
|
||||
|
||||
import rattail
|
||||
from .. import Session
|
||||
from rattail.db.model import (
|
||||
Customer, CustomerPerson, CustomerGroupAssignment,
|
||||
CustomerEmailAddress, CustomerPhoneNumber)
|
||||
from . import CrudView
|
||||
|
||||
|
||||
class CustomersGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Customer
|
||||
config_prefix = 'customers'
|
||||
sort = 'name'
|
||||
|
||||
def join_map(self):
|
||||
return {
|
||||
'email':
|
||||
lambda q: q.outerjoin(CustomerEmailAddress, and_(
|
||||
CustomerEmailAddress.parent_uuid == Customer.uuid,
|
||||
CustomerEmailAddress.preference == 1)),
|
||||
'phone':
|
||||
lambda q: q.outerjoin(CustomerPhoneNumber, and_(
|
||||
CustomerPhoneNumber.parent_uuid == Customer.uuid,
|
||||
CustomerPhoneNumber.preference == 1)),
|
||||
}
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(
|
||||
exact=['id'],
|
||||
ilike=['name'],
|
||||
email=self.filter_ilike(CustomerEmailAddress.address),
|
||||
phone=self.filter_ilike(CustomerPhoneNumber.number))
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk',
|
||||
filter_label_phone="Phone Number",
|
||||
filter_label_email="Email Address",
|
||||
filter_label_id="ID")
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map(
|
||||
'id', 'name',
|
||||
email=self.sorter(CustomerEmailAddress.address),
|
||||
phone=self.sorter(CustomerPhoneNumber.number))
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.id.label("ID"),
|
||||
g.name,
|
||||
g.phone.label("Phone Number"),
|
||||
g.email.label("Email Address"),
|
||||
],
|
||||
readonly=True)
|
||||
|
||||
if self.request.has_perm('customers.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'customer.read'
|
||||
if self.request.has_perm('customers.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'customer.update'
|
||||
if self.request.has_perm('customers.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'customer.delete'
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class CustomerCrud(CrudView):
|
||||
|
||||
mapped_class = Customer
|
||||
home_route = 'customers'
|
||||
|
||||
def get_model(self, key):
|
||||
model = super(CustomerCrud, self).get_model(key)
|
||||
if model:
|
||||
return model
|
||||
model = Session.query(Customer).filter_by(id=key).first()
|
||||
if model:
|
||||
return model
|
||||
model = Session.query(CustomerPerson).get(key)
|
||||
if model:
|
||||
return model.customer
|
||||
model = Session.query(CustomerGroupAssignment).get(key)
|
||||
if model:
|
||||
return model.customer
|
||||
return None
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.email_preference.set(renderer=EnumFieldRenderer(EMAIL_PREFERENCE))
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.id.label("ID"),
|
||||
fs.name,
|
||||
fs.phone.label("Phone Number").readonly(),
|
||||
fs.email.label("Email Address").readonly(),
|
||||
fs.email_preference,
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('customers', '/customers')
|
||||
config.add_route('customer.create', '/customers/new')
|
||||
config.add_route('customer.read', '/customers/{uuid}')
|
||||
config.add_route('customer.update', '/customers/{uuid}/edit')
|
||||
config.add_route('customer.delete', '/customers/{uuid}/delete')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(CustomersGrid, route_name='customers',
|
||||
renderer='/customers/index.mako',
|
||||
permission='customers.list')
|
||||
config.add_view(CustomerCrud, attr='create', route_name='customer.create',
|
||||
renderer='/customers/crud.mako',
|
||||
permission='customers.create')
|
||||
config.add_view(CustomerCrud, attr='read', route_name='customer.read',
|
||||
renderer='/customers/read.mako',
|
||||
permission='customers.read')
|
||||
config.add_view(CustomerCrud, attr='update', route_name='customer.update',
|
||||
renderer='/customers/crud.mako',
|
||||
permission='customers.update')
|
||||
config.add_view(CustomerCrud, attr='delete', route_name='customer.delete',
|
||||
permission='customers.delete')
|
160
tailbone/views/departments.py
Normal file
160
tailbone/views/departments.py
Normal file
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Department Views
|
||||
"""
|
||||
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView, AlchemyGridView, AutocompleteView
|
||||
|
||||
from rattail.db.model import Department, Product, ProductCost, Vendor
|
||||
|
||||
|
||||
class DepartmentsGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Department
|
||||
config_prefix = 'departments'
|
||||
sort = 'name'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(ilike=['name'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('number', 'name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.number,
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('departments.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'department.read'
|
||||
if self.request.has_perm('departments.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'department.update'
|
||||
if self.request.has_perm('departments.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'department.delete'
|
||||
return g
|
||||
|
||||
|
||||
class DepartmentCrud(CrudView):
|
||||
|
||||
mapped_class = Department
|
||||
home_route = 'departments'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.number,
|
||||
fs.name,
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
class DepartmentsByVendorGrid(AlchemyGridView):
|
||||
|
||||
mapped_class = Department
|
||||
config_prefix = 'departments.by_vendor'
|
||||
checkboxes = True
|
||||
partial_only = True
|
||||
|
||||
def query(self):
|
||||
q = self.make_query()
|
||||
q = q.outerjoin(Product)
|
||||
q = q.join(ProductCost)
|
||||
q = q.join(Vendor)
|
||||
q = q.filter(Vendor.uuid == self.request.params['uuid'])
|
||||
q = q.distinct()
|
||||
q = q.order_by(Department.name)
|
||||
return q
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
return g
|
||||
|
||||
|
||||
class DepartmentsAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = Department
|
||||
fieldname = 'name'
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('departments', '/departments')
|
||||
config.add_view(DepartmentsGrid,
|
||||
route_name='departments',
|
||||
renderer='/departments/index.mako',
|
||||
permission='departments.list')
|
||||
|
||||
config.add_route('departments.autocomplete', '/departments/autocomplete')
|
||||
config.add_view(DepartmentsAutocomplete,
|
||||
route_name='departments.autocomplete',
|
||||
renderer='json',
|
||||
permission='departments.list')
|
||||
|
||||
config.add_route('departments.by_vendor', '/departments/by-vendor')
|
||||
config.add_view(DepartmentsByVendorGrid,
|
||||
route_name='departments.by_vendor',
|
||||
permission='departments.list')
|
||||
|
||||
config.add_route('department.create', '/departments/new')
|
||||
config.add_view(DepartmentCrud, attr='create',
|
||||
route_name='department.create',
|
||||
renderer='/departments/crud.mako',
|
||||
permission='departments.create')
|
||||
|
||||
config.add_route('department.read', '/departments/{uuid}')
|
||||
config.add_view(DepartmentCrud, attr='read',
|
||||
route_name='department.read',
|
||||
renderer='/departments/crud.mako',
|
||||
permission='departments.read')
|
||||
|
||||
config.add_route('department.update', '/departments/{uuid}/edit')
|
||||
config.add_view(DepartmentCrud, attr='update',
|
||||
route_name='department.update',
|
||||
renderer='/departments/crud.mako',
|
||||
permission='departments.update')
|
||||
|
||||
config.add_route('department.delete', '/departments/{uuid}/delete')
|
||||
config.add_view(DepartmentCrud, attr='delete',
|
||||
route_name='department.delete',
|
||||
permission='departments.delete')
|
178
tailbone/views/employees.py
Normal file
178
tailbone/views/employees.py
Normal file
|
@ -0,0 +1,178 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Employee Views
|
||||
"""
|
||||
|
||||
from sqlalchemy import and_
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from ..grids.search import EnumSearchFilter
|
||||
from ..forms import AssociationProxyField, EnumFieldRenderer
|
||||
from rattail.db.model import (
|
||||
Employee, EmployeePhoneNumber, EmployeeEmailAddress, Person)
|
||||
from rattail.enum import EMPLOYEE_STATUS, EMPLOYEE_STATUS_CURRENT
|
||||
|
||||
|
||||
class EmployeesGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Employee
|
||||
config_prefix = 'employees'
|
||||
sort = 'first_name'
|
||||
|
||||
def join_map(self):
|
||||
return {
|
||||
'phone':
|
||||
lambda q: q.outerjoin(EmployeePhoneNumber, and_(
|
||||
EmployeePhoneNumber.parent_uuid == Employee.uuid,
|
||||
EmployeePhoneNumber.preference == 1)),
|
||||
'email':
|
||||
lambda q: q.outerjoin(EmployeeEmailAddress, and_(
|
||||
EmployeeEmailAddress.parent_uuid == Employee.uuid,
|
||||
EmployeeEmailAddress.preference == 1)),
|
||||
}
|
||||
|
||||
def filter_map(self):
|
||||
kwargs = dict(
|
||||
first_name=self.filter_ilike(Person.first_name),
|
||||
last_name=self.filter_ilike(Person.last_name),
|
||||
phone=self.filter_ilike(EmployeePhoneNumber.number),
|
||||
email=self.filter_ilike(EmployeeEmailAddress.address))
|
||||
if self.request.has_perm('employees.edit'):
|
||||
kwargs.update(dict(
|
||||
exact=['id', 'status']))
|
||||
return self.make_filter_map(**kwargs)
|
||||
|
||||
def filter_config(self):
|
||||
kwargs = dict(
|
||||
include_filter_first_name=True,
|
||||
filter_type_first_name='lk',
|
||||
include_filter_last_name=True,
|
||||
filter_type_last_name='lk',
|
||||
filter_label_phone="Phone Number",
|
||||
filter_label_email="Email Address")
|
||||
if self.request.has_perm('employees.edit'):
|
||||
kwargs.update(dict(
|
||||
filter_label_id="ID",
|
||||
include_filter_status=True,
|
||||
filter_type_status='is',
|
||||
filter_factory_status=EnumSearchFilter(EMPLOYEE_STATUS),
|
||||
status=EMPLOYEE_STATUS_CURRENT))
|
||||
return self.make_filter_config(**kwargs)
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map(
|
||||
first_name=self.sorter(Person.first_name),
|
||||
last_name=self.sorter(Person.last_name),
|
||||
phone=self.sorter(EmployeePhoneNumber.number),
|
||||
email=self.sorter(EmployeeEmailAddress.address))
|
||||
|
||||
def query(self):
|
||||
q = self.make_query()
|
||||
q = q.join(Person)
|
||||
if not self.request.has_perm('employees.edit'):
|
||||
q = q.filter(Employee.status == EMPLOYEE_STATUS_CURRENT)
|
||||
return q
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.append(AssociationProxyField('first_name'))
|
||||
g.append(AssociationProxyField('last_name'))
|
||||
g.configure(
|
||||
include=[
|
||||
g.id.label("ID"),
|
||||
g.first_name,
|
||||
g.last_name,
|
||||
g.phone.label("Phone Number"),
|
||||
g.email.label("Email Address"),
|
||||
g.status.with_renderer(EnumFieldRenderer(EMPLOYEE_STATUS)),
|
||||
],
|
||||
readonly=True)
|
||||
|
||||
# Hide ID and Status fields for unprivileged users.
|
||||
if not self.request.has_perm('employees.edit'):
|
||||
del g.id
|
||||
del g.status
|
||||
|
||||
if self.request.has_perm('employees.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'employee.read'
|
||||
if self.request.has_perm('employees.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'employee.update'
|
||||
if self.request.has_perm('employees.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'employee.delete'
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class EmployeeCrud(CrudView):
|
||||
|
||||
mapped_class = Employee
|
||||
home_route = 'employees'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.append(AssociationProxyField('first_name'))
|
||||
fs.append(AssociationProxyField('last_name'))
|
||||
fs.append(AssociationProxyField('display_name'))
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.id.label("ID"),
|
||||
fs.first_name,
|
||||
fs.last_name,
|
||||
fs.phone.label("Phone Number").readonly(),
|
||||
fs.email.label("Email Address").readonly(),
|
||||
fs.status.with_renderer(EnumFieldRenderer(EMPLOYEE_STATUS)),
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('employees', '/employees')
|
||||
config.add_route('employee.create', '/employees/new')
|
||||
config.add_route('employee.read', '/employees/{uuid}')
|
||||
config.add_route('employee.update', '/employees/{uuid}/edit')
|
||||
config.add_route('employee.delete', '/employees/{uuid}/delete')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(EmployeesGrid, route_name='employees',
|
||||
renderer='/employees/index.mako',
|
||||
permission='employees.list')
|
||||
config.add_view(EmployeeCrud, attr='create', route_name='employee.create',
|
||||
renderer='/employees/crud.mako',
|
||||
permission='employees.create')
|
||||
config.add_view(EmployeeCrud, attr='read', route_name='employee.read',
|
||||
renderer='/employees/crud.mako',
|
||||
permission='employees.read')
|
||||
config.add_view(EmployeeCrud, attr='update', route_name='employee.update',
|
||||
renderer='/employees/crud.mako',
|
||||
permission='employees.update')
|
||||
config.add_view(EmployeeCrud, attr='delete', route_name='employee.delete',
|
||||
permission='employees.delete')
|
30
tailbone/views/grids/__init__.py
Normal file
30
tailbone/views/grids/__init__.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Grid Views
|
||||
"""
|
||||
|
||||
from .core import *
|
||||
from .alchemy import *
|
181
tailbone/views/grids/alchemy.py
Normal file
181
tailbone/views/grids/alchemy.py
Normal file
|
@ -0,0 +1,181 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
FormAlchemy Grid Views
|
||||
"""
|
||||
|
||||
from webhelpers import paginate
|
||||
|
||||
from .core import GridView
|
||||
from ... import grids
|
||||
from ... import Session
|
||||
|
||||
|
||||
__all__ = ['AlchemyGridView', 'SortableAlchemyGridView',
|
||||
'PagedAlchemyGridView', 'SearchableAlchemyGridView']
|
||||
|
||||
|
||||
class AlchemyGridView(GridView):
|
||||
|
||||
def make_query(self, session=Session):
|
||||
query = session.query(self.mapped_class)
|
||||
return self.modify_query(query)
|
||||
|
||||
def modify_query(self, query):
|
||||
return query
|
||||
|
||||
def query(self):
|
||||
return self.make_query()
|
||||
|
||||
def make_grid(self, **kwargs):
|
||||
self.update_grid_kwargs(kwargs)
|
||||
return grids.AlchemyGrid(
|
||||
self.request, self.mapped_class, self._data, **kwargs)
|
||||
|
||||
def grid(self):
|
||||
return self.make_grid()
|
||||
|
||||
def __call__(self):
|
||||
self._data = self.query()
|
||||
grid = self.grid()
|
||||
return grids.util.render_grid(grid)
|
||||
|
||||
|
||||
class SortableAlchemyGridView(AlchemyGridView):
|
||||
|
||||
sort = None
|
||||
|
||||
@property
|
||||
def config_prefix(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def join_map(self):
|
||||
return {}
|
||||
|
||||
def make_sort_map(self, *args, **kwargs):
|
||||
return grids.util.get_sort_map(
|
||||
self.mapped_class, names=args or None, **kwargs)
|
||||
|
||||
def sorter(self, field):
|
||||
return grids.util.sorter(field)
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map()
|
||||
|
||||
def make_sort_config(self, **kwargs):
|
||||
return grids.util.get_sort_config(
|
||||
self.config_prefix, self.request, **kwargs)
|
||||
|
||||
def sort_config(self):
|
||||
return self.make_sort_config(sort=self.sort)
|
||||
|
||||
def modify_query(self, query):
|
||||
return grids.util.sort_query(
|
||||
query, self._sort_config, self.sort_map(), self.join_map())
|
||||
|
||||
def make_grid(self, **kwargs):
|
||||
self.update_grid_kwargs(kwargs)
|
||||
return grids.AlchemyGrid(
|
||||
self.request, self.mapped_class, self._data,
|
||||
sort_map=self.sort_map(), config=self._sort_config, **kwargs)
|
||||
|
||||
def grid(self):
|
||||
return self.make_grid()
|
||||
|
||||
def __call__(self):
|
||||
self._sort_config = self.sort_config()
|
||||
self._data = self.query()
|
||||
grid = self.grid()
|
||||
return grids.util.render_grid(grid)
|
||||
|
||||
|
||||
class PagedAlchemyGridView(SortableAlchemyGridView):
|
||||
|
||||
full = True
|
||||
|
||||
def make_pager(self):
|
||||
config = self._sort_config
|
||||
query = self.query()
|
||||
return paginate.Page(
|
||||
query, item_count=query.count(),
|
||||
items_per_page=int(config['per_page']),
|
||||
page=int(config['page']),
|
||||
url=paginate.PageURL_WebOb(self.request))
|
||||
|
||||
def __call__(self):
|
||||
self._sort_config = self.sort_config()
|
||||
self._data = self.make_pager()
|
||||
grid = self.grid()
|
||||
grid.pager = self._data
|
||||
return grids.util.render_grid(grid)
|
||||
|
||||
|
||||
class SearchableAlchemyGridView(PagedAlchemyGridView):
|
||||
|
||||
def filter_exact(self, field):
|
||||
return grids.search.filter_exact(field)
|
||||
|
||||
def filter_ilike(self, field):
|
||||
return grids.search.filter_ilike(field)
|
||||
|
||||
def make_filter_map(self, **kwargs):
|
||||
return grids.search.get_filter_map(self.mapped_class, **kwargs)
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map()
|
||||
|
||||
def make_filter_config(self, **kwargs):
|
||||
return grids.search.get_filter_config(
|
||||
self.config_prefix, self.request, self.filter_map(), **kwargs)
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config()
|
||||
|
||||
def make_search_form(self):
|
||||
return grids.search.get_search_form(
|
||||
self.request, self.filter_map(), self._filter_config)
|
||||
|
||||
def search_form(self):
|
||||
return self.make_search_form()
|
||||
|
||||
def modify_query(self, query):
|
||||
join_map = self.join_map()
|
||||
query = grids.search.filter_query(
|
||||
query, self._filter_config, self.filter_map(), join_map)
|
||||
if hasattr(self, '_sort_config'):
|
||||
self._sort_config['joins'] = self._filter_config['joins']
|
||||
query = grids.util.sort_query(
|
||||
query, self._sort_config, self.sort_map(), join_map)
|
||||
return query
|
||||
|
||||
def __call__(self):
|
||||
self._filter_config = self.filter_config()
|
||||
search = self.search_form()
|
||||
self._sort_config = self.sort_config()
|
||||
self._data = self.make_pager()
|
||||
grid = self.grid()
|
||||
grid.pager = self._data
|
||||
kwargs = self.render_kwargs()
|
||||
return grids.util.render_grid(grid, search, **kwargs)
|
68
tailbone/views/grids/core.py
Normal file
68
tailbone/views/grids/core.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Core Grid View
|
||||
"""
|
||||
|
||||
from .. import View
|
||||
from ... import grids
|
||||
|
||||
|
||||
__all__ = ['GridView']
|
||||
|
||||
|
||||
class GridView(View):
|
||||
|
||||
route_name = None
|
||||
route_url = None
|
||||
renderer = None
|
||||
permission = None
|
||||
|
||||
full = False
|
||||
checkboxes = False
|
||||
deletable = False
|
||||
|
||||
partial_only = False
|
||||
|
||||
def update_grid_kwargs(self, kwargs):
|
||||
kwargs.setdefault('full', self.full)
|
||||
kwargs.setdefault('checkboxes', self.checkboxes)
|
||||
kwargs.setdefault('deletable', self.deletable)
|
||||
kwargs.setdefault('partial_only', self.partial_only)
|
||||
|
||||
def make_grid(self, **kwargs):
|
||||
self.update_grid_kwargs(kwargs)
|
||||
return grids.Grid(self.request, **kwargs)
|
||||
|
||||
def grid(self):
|
||||
return self.make_grid()
|
||||
|
||||
def render_kwargs(self):
|
||||
return {}
|
||||
|
||||
def __call__(self):
|
||||
grid = self.grid()
|
||||
kwargs = self.render_kwargs()
|
||||
return grids.util.render_grid(grid, **kwargs)
|
192
tailbone/views/labels.py
Normal file
192
tailbone/views/labels.py
Normal file
|
@ -0,0 +1,192 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Label Views
|
||||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
import formalchemy
|
||||
|
||||
from webhelpers.html import HTML
|
||||
|
||||
from .. import Session
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from ..grids.search import BooleanSearchFilter
|
||||
from edbob.pyramid.forms import StrippingFieldRenderer
|
||||
|
||||
from rattail.db.model import LabelProfile
|
||||
|
||||
|
||||
class ProfilesGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = LabelProfile
|
||||
config_prefix = 'label_profiles'
|
||||
sort = 'ordinal'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(
|
||||
exact=['code', 'visible'],
|
||||
ilike=['description'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
filter_factory_visible=BooleanSearchFilter)
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('ordinal', 'code', 'description', 'visible')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.ordinal,
|
||||
g.code,
|
||||
g.description,
|
||||
g.visible,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('label_profiles.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'label_profile.read'
|
||||
if self.request.has_perm('label_profiles.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'label_profile.update'
|
||||
if self.request.has_perm('label_profiles.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'label_profile.delete'
|
||||
return g
|
||||
|
||||
|
||||
class ProfileCrud(CrudView):
|
||||
|
||||
mapped_class = LabelProfile
|
||||
home_route = 'label_profiles'
|
||||
pretty_name = "Label Profile"
|
||||
update_cancel_route = 'label_profile.read'
|
||||
|
||||
def fieldset(self, model):
|
||||
|
||||
class FormatFieldRenderer(formalchemy.TextAreaFieldRenderer):
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
value = self.raw_value
|
||||
if not value:
|
||||
return ''
|
||||
return HTML.tag('pre', c=value)
|
||||
|
||||
def render(self, **kwargs):
|
||||
kwargs.setdefault('size', (80, 8))
|
||||
return super(FormatFieldRenderer, self).render(**kwargs)
|
||||
|
||||
fs = self.make_fieldset(model)
|
||||
fs.printer_spec.set(renderer=StrippingFieldRenderer)
|
||||
fs.formatter_spec.set(renderer=StrippingFieldRenderer)
|
||||
fs.format.set(renderer=FormatFieldRenderer)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.ordinal,
|
||||
fs.code,
|
||||
fs.description,
|
||||
fs.printer_spec,
|
||||
fs.formatter_spec,
|
||||
fs.format,
|
||||
fs.visible,
|
||||
])
|
||||
return fs
|
||||
|
||||
def post_save(self, form):
|
||||
profile = form.fieldset.model
|
||||
if not profile.format:
|
||||
formatter = profile.get_formatter()
|
||||
if formatter:
|
||||
try:
|
||||
profile.format = formatter.default_format
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
def post_save_url(self, form):
|
||||
return self.request.route_url('label_profile.read',
|
||||
uuid=form.fieldset.model.uuid)
|
||||
|
||||
|
||||
def printer_settings(request):
|
||||
uuid = request.matchdict['uuid']
|
||||
profile = Session.query(LabelProfile).get(uuid) if uuid else None
|
||||
if not profile:
|
||||
return HTTPFound(location=request.route_url('label_profiles'))
|
||||
|
||||
read_profile = HTTPFound(location=request.route_url(
|
||||
'label_profile.read', uuid=profile.uuid))
|
||||
|
||||
printer = profile.get_printer()
|
||||
if not printer:
|
||||
request.session.flash("Label profile \"%s\" does not have a functional "
|
||||
"printer spec." % profile)
|
||||
return read_profile
|
||||
if not printer.required_settings:
|
||||
request.session.flash("Printer class for label profile \"%s\" does not "
|
||||
"require any settings." % profile)
|
||||
return read_profile
|
||||
|
||||
if request.POST:
|
||||
for setting in printer.required_settings:
|
||||
if setting in request.POST:
|
||||
profile.save_printer_setting(setting, request.POST[setting])
|
||||
return read_profile
|
||||
|
||||
return {'profile': profile, 'printer': printer}
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('label_profiles', '/labels/profiles')
|
||||
config.add_view(ProfilesGrid, route_name='label_profiles',
|
||||
renderer='/labels/profiles/index.mako',
|
||||
permission='label_profiles.list')
|
||||
|
||||
config.add_route('label_profile.create', '/labels/profiles/new')
|
||||
config.add_view(ProfileCrud, attr='create', route_name='label_profile.create',
|
||||
renderer='/labels/profiles/crud.mako',
|
||||
permission='label_profiles.create')
|
||||
|
||||
config.add_route('label_profile.read', '/labels/profiles/{uuid}')
|
||||
config.add_view(ProfileCrud, attr='read', route_name='label_profile.read',
|
||||
renderer='/labels/profiles/read.mako',
|
||||
permission='label_profiles.read')
|
||||
|
||||
config.add_route('label_profile.update', '/labels/profiles/{uuid}/edit')
|
||||
config.add_view(ProfileCrud, attr='update', route_name='label_profile.update',
|
||||
renderer='/labels/profiles/crud.mako',
|
||||
permission='label_profiles.update')
|
||||
|
||||
config.add_route('label_profile.delete', '/labels/profiles/{uuid}/delete')
|
||||
config.add_view(ProfileCrud, attr='delete', route_name='label_profile.delete',
|
||||
permission='label_profiles.delete')
|
||||
|
||||
config.add_route('label_profile.printer_settings', '/labels/profiles/{uuid}/printer')
|
||||
config.add_view(printer_settings, route_name='label_profile.printer_settings',
|
||||
renderer='/labels/profiles/printer.mako',
|
||||
permission='label_profiles.update')
|
156
tailbone/views/people.py
Normal file
156
tailbone/views/people.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Person Views
|
||||
"""
|
||||
|
||||
from sqlalchemy import and_
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView, AutocompleteView
|
||||
|
||||
from .. import Session
|
||||
from rattail.db.model import (Person, PersonEmailAddress, PersonPhoneNumber,
|
||||
VendorContact)
|
||||
|
||||
|
||||
class PeopleGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Person
|
||||
config_prefix = 'people'
|
||||
sort = 'first_name'
|
||||
|
||||
def join_map(self):
|
||||
return {
|
||||
'email':
|
||||
lambda q: q.outerjoin(PersonEmailAddress, and_(
|
||||
PersonEmailAddress.parent_uuid == Person.uuid,
|
||||
PersonEmailAddress.preference == 1)),
|
||||
'phone':
|
||||
lambda q: q.outerjoin(PersonPhoneNumber, and_(
|
||||
PersonPhoneNumber.parent_uuid == Person.uuid,
|
||||
PersonPhoneNumber.preference == 1)),
|
||||
}
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(
|
||||
ilike=['first_name', 'last_name', 'display_name'],
|
||||
email=self.filter_ilike(PersonEmailAddress.address),
|
||||
phone=self.filter_ilike(PersonPhoneNumber.number))
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_first_name=True,
|
||||
filter_type_first_name='lk',
|
||||
include_filter_last_name=True,
|
||||
filter_type_last_name='lk',
|
||||
filter_label_phone="Phone Number",
|
||||
filter_label_email="Email Address")
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map(
|
||||
'first_name', 'last_name', 'display_name',
|
||||
email=self.sorter(PersonEmailAddress.address),
|
||||
phone=self.sorter(PersonPhoneNumber.number))
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.first_name,
|
||||
g.last_name,
|
||||
g.display_name,
|
||||
g.phone.label("Phone Number"),
|
||||
g.email.label("Email Address"),
|
||||
],
|
||||
readonly=True)
|
||||
|
||||
if self.request.has_perm('people.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'person.read'
|
||||
if self.request.has_perm('people.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'person.update'
|
||||
# if self.request.has_perm('products.delete'):
|
||||
# g.deletable = True
|
||||
# g.delete_route_name = 'product.delete'
|
||||
|
||||
return g
|
||||
|
||||
|
||||
class PersonCrud(CrudView):
|
||||
|
||||
mapped_class = Person
|
||||
home_route = 'people'
|
||||
|
||||
def get_model(self, key):
|
||||
model = super(PersonCrud, self).get_model(key)
|
||||
if model:
|
||||
return model
|
||||
model = Session.query(VendorContact).get(key)
|
||||
if model:
|
||||
return model.person
|
||||
return None
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.first_name,
|
||||
fs.last_name,
|
||||
fs.display_name,
|
||||
fs.phone.label("Phone Number").readonly(),
|
||||
fs.email.label("Email Address").readonly(),
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
class PeopleAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = Person
|
||||
fieldname = 'display_name'
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('people', '/people')
|
||||
config.add_route('people.autocomplete', '/people/autocomplete')
|
||||
config.add_route('person.read', '/people/{uuid}')
|
||||
config.add_route('person.update', '/people/{uuid}/edit')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(PeopleGrid, route_name='people',
|
||||
renderer='/people/index.mako',
|
||||
permission='people.list')
|
||||
config.add_view(PersonCrud, attr='read', route_name='person.read',
|
||||
renderer='/people/crud.mako',
|
||||
permission='people.read')
|
||||
config.add_view(PersonCrud, attr='update', route_name='person.update',
|
||||
renderer='/people/crud.mako',
|
||||
permission='people.update')
|
||||
config.add_view(PeopleAutocomplete, route_name='people.autocomplete',
|
||||
renderer='json',
|
||||
permission='people.list')
|
368
tailbone/views/products.py
Normal file
368
tailbone/views/products.py
Normal file
|
@ -0,0 +1,368 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Product Views
|
||||
"""
|
||||
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from webhelpers.html.tags import link_to
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
from pyramid.renderers import render_to_response
|
||||
|
||||
import edbob
|
||||
from edbob.pyramid.progress import SessionProgress
|
||||
from . import SearchableAlchemyGridView
|
||||
|
||||
import rattail.labels
|
||||
from rattail import sil
|
||||
from rattail import batches
|
||||
from rattail.threads import Thread
|
||||
from rattail.exceptions import LabelPrintingError
|
||||
from rattail.db.model import (
|
||||
Product, ProductPrice, ProductCost, ProductCode,
|
||||
Brand, Vendor, Department, Subdepartment, LabelProfile)
|
||||
from rattail.gpc import GPC
|
||||
|
||||
from .. import Session
|
||||
from ..forms import AutocompleteFieldRenderer, GPCFieldRenderer, PriceFieldRenderer
|
||||
from . import CrudView
|
||||
|
||||
|
||||
class ProductsGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Product
|
||||
config_prefix = 'products'
|
||||
sort = 'description'
|
||||
|
||||
def join_map(self):
|
||||
|
||||
def join_vendor(q):
|
||||
q = q.outerjoin(
|
||||
ProductCost,
|
||||
and_(
|
||||
ProductCost.product_uuid == Product.uuid,
|
||||
ProductCost.preference == 1,
|
||||
))
|
||||
q = q.outerjoin(Vendor)
|
||||
return q
|
||||
|
||||
return {
|
||||
'brand':
|
||||
lambda q: q.outerjoin(Brand),
|
||||
'department':
|
||||
lambda q: q.outerjoin(Department,
|
||||
Department.uuid == Product.department_uuid),
|
||||
'subdepartment':
|
||||
lambda q: q.outerjoin(Subdepartment,
|
||||
Subdepartment.uuid == Product.subdepartment_uuid),
|
||||
'regular_price':
|
||||
lambda q: q.outerjoin(ProductPrice,
|
||||
ProductPrice.uuid == Product.regular_price_uuid),
|
||||
'current_price':
|
||||
lambda q: q.outerjoin(ProductPrice,
|
||||
ProductPrice.uuid == Product.current_price_uuid),
|
||||
'vendor':
|
||||
join_vendor,
|
||||
'code':
|
||||
lambda q: q.outerjoin(ProductCode),
|
||||
}
|
||||
|
||||
def filter_map(self):
|
||||
|
||||
def filter_upc():
|
||||
|
||||
def filter_is(q, v):
|
||||
if not v:
|
||||
return q
|
||||
try:
|
||||
return q.filter(Product.upc.in_((
|
||||
GPC(v), GPC(v, calc_check_digit='upc'))))
|
||||
except ValueError:
|
||||
return q
|
||||
|
||||
def filter_not(q, v):
|
||||
if not v:
|
||||
return q
|
||||
try:
|
||||
return q.filter(~Product.upc.in_((
|
||||
GPC(v), GPC(v, calc_check_digit='upc'))))
|
||||
except ValueError:
|
||||
return q
|
||||
|
||||
return {'is': filter_is, 'nt': filter_not}
|
||||
|
||||
return self.make_filter_map(
|
||||
ilike=['description', 'size'],
|
||||
upc=filter_upc(),
|
||||
brand=self.filter_ilike(Brand.name),
|
||||
department=self.filter_ilike(Department.name),
|
||||
subdepartment=self.filter_ilike(Subdepartment.name),
|
||||
vendor=self.filter_ilike(Vendor.name),
|
||||
code=self.filter_ilike(ProductCode.code))
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_upc=True,
|
||||
filter_type_upc='eq',
|
||||
filter_label_upc="UPC",
|
||||
include_filter_brand=True,
|
||||
filter_type_brand='lk',
|
||||
include_filter_description=True,
|
||||
filter_type_description='lk',
|
||||
include_filter_department=True,
|
||||
filter_type_department='lk',
|
||||
include_filter_vendor=True,
|
||||
filter_type_vendor='lk')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map(
|
||||
'upc', 'description', 'size',
|
||||
brand=self.sorter(Brand.name),
|
||||
department=self.sorter(Department.name),
|
||||
subdepartment=self.sorter(Subdepartment.name),
|
||||
regular_price=self.sorter(ProductPrice.price),
|
||||
current_price=self.sorter(ProductPrice.price),
|
||||
vendor=self.sorter(Vendor.name))
|
||||
|
||||
def query(self):
|
||||
q = self.make_query()
|
||||
q = q.options(joinedload(Product.brand))
|
||||
q = q.options(joinedload(Product.department))
|
||||
q = q.options(joinedload(Product.subdepartment))
|
||||
q = q.options(joinedload(Product.regular_price))
|
||||
q = q.options(joinedload(Product.current_price))
|
||||
q = q.options(joinedload(Product.vendor))
|
||||
return q
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.upc.set(renderer=GPCFieldRenderer)
|
||||
g.regular_price.set(renderer=PriceFieldRenderer)
|
||||
g.current_price.set(renderer=PriceFieldRenderer)
|
||||
g.configure(
|
||||
include=[
|
||||
g.upc.label("UPC"),
|
||||
g.brand,
|
||||
g.description,
|
||||
g.size,
|
||||
g.subdepartment,
|
||||
g.vendor,
|
||||
g.regular_price.label("Reg. Price"),
|
||||
g.current_price.label("Cur. Price"),
|
||||
],
|
||||
readonly=True)
|
||||
|
||||
if self.request.has_perm('products.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'product.read'
|
||||
if self.request.has_perm('products.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'product.update'
|
||||
if self.request.has_perm('products.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'product.delete'
|
||||
|
||||
q = Session.query(LabelProfile)
|
||||
if q.count():
|
||||
def labels(row):
|
||||
return link_to("Print", '#', class_='print-label')
|
||||
g.add_column('labels', "Labels", labels)
|
||||
|
||||
return g
|
||||
|
||||
def render_kwargs(self):
|
||||
q = Session.query(LabelProfile)
|
||||
q = q.filter(LabelProfile.visible == True)
|
||||
q = q.order_by(LabelProfile.ordinal)
|
||||
return {'label_profiles': q.all()}
|
||||
|
||||
|
||||
class ProductCrud(CrudView):
|
||||
|
||||
mapped_class = Product
|
||||
home_route = 'products'
|
||||
|
||||
def get_model(self, key):
|
||||
model = super(ProductCrud, self).get_model(key)
|
||||
if model:
|
||||
return model
|
||||
model = Session.query(ProductPrice).get(key)
|
||||
if model:
|
||||
return model.product
|
||||
return None
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.upc.set(renderer=GPCFieldRenderer)
|
||||
fs.brand.set(renderer=AutocompleteFieldRenderer(
|
||||
self.request.route_url('brands.autocomplete')))
|
||||
fs.regular_price.set(renderer=PriceFieldRenderer)
|
||||
fs.current_price.set(renderer=PriceFieldRenderer)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.upc.label("UPC"),
|
||||
fs.brand,
|
||||
fs.description,
|
||||
fs.size,
|
||||
fs.department,
|
||||
fs.subdepartment,
|
||||
fs.regular_price,
|
||||
fs.current_price,
|
||||
])
|
||||
if not self.readonly:
|
||||
del fs.regular_price
|
||||
del fs.current_price
|
||||
return fs
|
||||
|
||||
|
||||
def print_labels(request):
|
||||
profile = request.params.get('profile')
|
||||
profile = Session.query(LabelProfile).get(profile) if profile else None
|
||||
if not profile:
|
||||
return {'error': "Label profile not found"}
|
||||
|
||||
product = request.params.get('product')
|
||||
product = Session.query(Product).get(product) if product else None
|
||||
if not product:
|
||||
return {'error': "Product not found"}
|
||||
|
||||
quantity = request.params.get('quantity')
|
||||
if not quantity.isdigit():
|
||||
return {'error': "Quantity must be numeric"}
|
||||
quantity = int(quantity)
|
||||
|
||||
printer = profile.get_printer()
|
||||
if not printer:
|
||||
return {'error': "Couldn't get printer from label profile"}
|
||||
|
||||
try:
|
||||
printer.print_labels([(product, quantity)])
|
||||
except Exception, error:
|
||||
return {'error': str(error)}
|
||||
return {}
|
||||
|
||||
|
||||
class CreateProductsBatch(ProductsGrid):
|
||||
|
||||
def make_batch(self, provider, progress):
|
||||
from rattail.db import Session
|
||||
session = Session()
|
||||
|
||||
self._filter_config = self.filter_config()
|
||||
self._sort_config = self.sort_config()
|
||||
products = self.make_query(session)
|
||||
|
||||
batch = provider.make_batch(session, products, progress)
|
||||
if not batch:
|
||||
session.rollback()
|
||||
session.close()
|
||||
return
|
||||
|
||||
session.commit()
|
||||
session.refresh(batch)
|
||||
session.close()
|
||||
|
||||
progress.session.load()
|
||||
progress.session['complete'] = True
|
||||
progress.session['success_url'] = self.request.route_url('batch.read', uuid=batch.uuid)
|
||||
progress.session['success_msg'] = "Batch \"%s\" has been created." % batch.description
|
||||
progress.session.save()
|
||||
|
||||
def __call__(self):
|
||||
if self.request.POST:
|
||||
provider = self.request.POST.get('provider')
|
||||
if provider:
|
||||
provider = batches.get_provider(provider)
|
||||
if provider:
|
||||
|
||||
if self.request.POST.get('params') == 'True':
|
||||
provider.set_params(Session(), **self.request.POST)
|
||||
|
||||
else:
|
||||
try:
|
||||
url = self.request.route_url('batch_params.%s' % provider.name)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
self.request.session['referer'] = self.request.current_route_url()
|
||||
return HTTPFound(location=url)
|
||||
|
||||
progress = SessionProgress(self.request.session, 'products.batch')
|
||||
thread = Thread(target=self.make_batch, args=(provider, progress))
|
||||
thread.start()
|
||||
kwargs = {
|
||||
'key': 'products.batch',
|
||||
'cancel_url': self.request.route_url('products'),
|
||||
'cancel_msg': "Batch creation was canceled.",
|
||||
}
|
||||
return render_to_response('/progress.mako', kwargs, request=self.request)
|
||||
|
||||
enabled = edbob.config.get('rattail.pyramid', 'batches.providers')
|
||||
if enabled:
|
||||
enabled = enabled.split()
|
||||
|
||||
providers = []
|
||||
for provider in batches.iter_providers():
|
||||
if not enabled or provider.name in enabled:
|
||||
providers.append((provider.name, provider.description))
|
||||
|
||||
return {'providers': providers}
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('products', '/products')
|
||||
config.add_route('products.print_labels', '/products/labels')
|
||||
config.add_route('products.create_batch', '/products/batch')
|
||||
config.add_route('product.create', '/products/new')
|
||||
config.add_route('product.read', '/products/{uuid}')
|
||||
config.add_route('product.update', '/products/{uuid}/edit')
|
||||
config.add_route('product.delete', '/products/{uuid}/delete')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(ProductsGrid, route_name='products',
|
||||
renderer='/products/index.mako',
|
||||
permission='products.list')
|
||||
config.add_view(print_labels, route_name='products.print_labels',
|
||||
renderer='json', permission='products.print_labels')
|
||||
config.add_view(CreateProductsBatch, route_name='products.create_batch',
|
||||
renderer='/products/batch.mako',
|
||||
permission='batches.create')
|
||||
config.add_view(ProductCrud, attr='create', route_name='product.create',
|
||||
renderer='/products/crud.mako',
|
||||
permission='products.create')
|
||||
config.add_view(ProductCrud, attr='read', route_name='product.read',
|
||||
renderer='/products/read.mako',
|
||||
permission='products.read')
|
||||
config.add_view(ProductCrud, attr='update', route_name='product.update',
|
||||
renderer='/products/crud.mako',
|
||||
permission='products.update')
|
||||
config.add_view(ProductCrud, attr='delete', route_name='product.delete',
|
||||
permission='products.delete')
|
197
tailbone/views/reports.py
Normal file
197
tailbone/views/reports.py
Normal file
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Report Views
|
||||
"""
|
||||
|
||||
from .core import View
|
||||
from mako.template import Template
|
||||
from pyramid.response import Response
|
||||
|
||||
from .. import Session
|
||||
from rattail.db.model import Vendor, Department, Product, ProductCost
|
||||
|
||||
import re
|
||||
import rattail
|
||||
from edbob.time import local_time
|
||||
from rattail.files import resource_path
|
||||
|
||||
|
||||
plu_upc_pattern = re.compile(r'^000000000(\d{5})$')
|
||||
weighted_upc_pattern = re.compile(r'^002(\d{5})00000\d$')
|
||||
|
||||
def get_upc(product):
|
||||
upc = '%014u' % product.upc
|
||||
m = plu_upc_pattern.match(upc)
|
||||
if m:
|
||||
return str(int(m.group(1)))
|
||||
m = weighted_upc_pattern.match(upc)
|
||||
if m:
|
||||
return str(int(m.group(1)))
|
||||
return upc
|
||||
|
||||
|
||||
class OrderingWorksheet(View):
|
||||
"""
|
||||
This is the "Ordering Worksheet" report.
|
||||
"""
|
||||
|
||||
report_template_path = 'tailbone:reports/ordering_worksheet.mako'
|
||||
|
||||
upc_getter = staticmethod(get_upc)
|
||||
|
||||
def __call__(self):
|
||||
if self.request.params.get('vendor'):
|
||||
vendor = Session.query(Vendor).get(self.request.params['vendor'])
|
||||
if vendor:
|
||||
departments = []
|
||||
uuids = self.request.params.get('departments')
|
||||
if uuids:
|
||||
for uuid in uuids.split(','):
|
||||
dept = Session.query(Department).get(uuid)
|
||||
if dept:
|
||||
departments.append(dept)
|
||||
preferred_only = self.request.params.get('preferred_only') == '1'
|
||||
body = self.write_report(vendor, departments, preferred_only)
|
||||
response = Response(content_type='text/html')
|
||||
response.headers['Content-Length'] = len(body)
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=ordering.html'
|
||||
response.text = body
|
||||
return response
|
||||
return {}
|
||||
|
||||
def write_report(self, vendor, departments, preferred_only):
|
||||
"""
|
||||
Rendering engine for the ordering worksheet report.
|
||||
"""
|
||||
|
||||
q = Session.query(ProductCost)
|
||||
q = q.join(Product)
|
||||
q = q.filter(ProductCost.vendor == vendor)
|
||||
q = q.filter(Product.department_uuid.in_([x.uuid for x in departments]))
|
||||
if preferred_only:
|
||||
q = q.filter(ProductCost.preference == 1)
|
||||
|
||||
costs = {}
|
||||
for cost in q:
|
||||
dept = cost.product.department
|
||||
subdept = cost.product.subdepartment
|
||||
costs.setdefault(dept, {})
|
||||
costs[dept].setdefault(subdept, [])
|
||||
costs[dept][subdept].append(cost)
|
||||
|
||||
def cost_sort_key(cost):
|
||||
product = cost.product
|
||||
brand = product.brand.name if product.brand else ''
|
||||
key = '{0} {1}'.format(brand, product.description)
|
||||
return key
|
||||
|
||||
now = local_time()
|
||||
data = dict(
|
||||
vendor=vendor,
|
||||
costs=costs,
|
||||
cost_sort_key=cost_sort_key,
|
||||
date=now.strftime('%a %d %b %Y'),
|
||||
time=now.strftime('%I:%M %p'),
|
||||
get_upc=self.upc_getter,
|
||||
rattail=rattail,
|
||||
)
|
||||
|
||||
template_path = resource_path(self.report_template_path)
|
||||
template = Template(filename=template_path)
|
||||
return template.render(**data)
|
||||
|
||||
|
||||
class InventoryWorksheet(View):
|
||||
"""
|
||||
This is the "Inventory Worksheet" report.
|
||||
"""
|
||||
|
||||
report_template_path = 'tailbone:reports/inventory_worksheet.mako'
|
||||
|
||||
upc_getter = staticmethod(get_upc)
|
||||
|
||||
def __call__(self):
|
||||
"""
|
||||
This is the "Inventory Worksheet" report.
|
||||
"""
|
||||
|
||||
departments = Session.query(Department)
|
||||
|
||||
if self.request.params.get('department'):
|
||||
department = departments.get(self.request.params['department'])
|
||||
if department:
|
||||
body = self.write_report(department)
|
||||
response = Response(content_type='text/html')
|
||||
response.headers['Content-Length'] = len(body)
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=inventory.html'
|
||||
response.text = body
|
||||
return response
|
||||
|
||||
departments = departments.order_by(rattail.Department.name)
|
||||
departments = departments.all()
|
||||
return{'departments': departments}
|
||||
|
||||
def write_report(self, department):
|
||||
"""
|
||||
Generates the Inventory Worksheet report.
|
||||
"""
|
||||
|
||||
def get_products(subdepartment):
|
||||
q = Session.query(rattail.Product)
|
||||
q = q.outerjoin(rattail.Brand)
|
||||
q = q.filter(rattail.Product.subdepartment == subdepartment)
|
||||
if self.request.params.get('weighted-only'):
|
||||
q = q.filter(rattail.Product.unit_of_measure == rattail.UNIT_OF_MEASURE_POUND)
|
||||
q = q.order_by(rattail.Brand.name, rattail.Product.description)
|
||||
return q.all()
|
||||
|
||||
now = local_time()
|
||||
data = dict(
|
||||
date=now.strftime('%a %d %b %Y'),
|
||||
time=now.strftime('%I:%M %p'),
|
||||
department=department,
|
||||
get_products=get_products,
|
||||
get_upc=self.upc_getter,
|
||||
)
|
||||
|
||||
template_path = resource_path(self.report_template_path)
|
||||
template = Template(filename=template_path)
|
||||
return template.render(**data)
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('reports.ordering', '/reports/ordering')
|
||||
config.add_route('reports.inventory', '/reports/inventory')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(OrderingWorksheet, route_name='reports.ordering',
|
||||
renderer='/reports/ordering.mako')
|
||||
|
||||
config.add_view(InventoryWorksheet, route_name='reports.inventory',
|
||||
renderer='/reports/inventory.mako')
|
218
tailbone/views/roles.py
Normal file
218
tailbone/views/roles.py
Normal file
|
@ -0,0 +1,218 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Role Views
|
||||
"""
|
||||
|
||||
from pyramid.httpexceptions import HTTPFound
|
||||
|
||||
import formalchemy
|
||||
from webhelpers.html import tags
|
||||
from webhelpers.html.builder import HTML
|
||||
|
||||
from edbob.db import auth
|
||||
|
||||
from .. import Session
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from rattail.db.model import Role
|
||||
|
||||
|
||||
default_permissions = [
|
||||
|
||||
("People", [
|
||||
('people.list', "List People"),
|
||||
('people.read', "View Person"),
|
||||
('people.create', "Create Person"),
|
||||
('people.update', "Edit Person"),
|
||||
('people.delete', "Delete Person"),
|
||||
]),
|
||||
|
||||
("Roles", [
|
||||
('roles.list', "List Roles"),
|
||||
('roles.read', "View Role"),
|
||||
('roles.create', "Create Role"),
|
||||
('roles.update', "Edit Role"),
|
||||
('roles.delete', "Delete Role"),
|
||||
]),
|
||||
|
||||
("Users", [
|
||||
('users.list', "List Users"),
|
||||
('users.read', "View User"),
|
||||
('users.create', "Create User"),
|
||||
('users.update', "Edit User"),
|
||||
('users.delete', "Delete User"),
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
class RolesGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Role
|
||||
config_prefix = 'roles'
|
||||
sort = 'name'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(ilike=['name'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.name,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('roles.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'role.read'
|
||||
if self.request.has_perm('roles.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'role.update'
|
||||
if self.request.has_perm('roles.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'role.delete'
|
||||
return g
|
||||
|
||||
|
||||
class PermissionsField(formalchemy.Field):
|
||||
|
||||
def sync(self):
|
||||
if not self.is_readonly():
|
||||
role = self.model
|
||||
role.permissions = self.renderer.deserialize()
|
||||
|
||||
|
||||
def PermissionsFieldRenderer(permissions, *args, **kwargs):
|
||||
|
||||
perms = permissions
|
||||
|
||||
class PermissionsFieldRenderer(formalchemy.FieldRenderer):
|
||||
|
||||
permissions = perms
|
||||
|
||||
def deserialize(self):
|
||||
perms = []
|
||||
i = len(self.name) + 1
|
||||
for key in self.params:
|
||||
if key.startswith(self.name):
|
||||
perms.append(key[i:])
|
||||
return perms
|
||||
|
||||
def _render(self, readonly=False, **kwargs):
|
||||
role = self.field.model
|
||||
admin = auth.administrator_role(Session())
|
||||
if role is admin:
|
||||
html = HTML.tag('p', c="This is the administrative role; "
|
||||
"it has full access to the entire system.")
|
||||
if not readonly:
|
||||
html += tags.hidden(self.name, value='') # ugly hack..or good idea?
|
||||
else:
|
||||
html = ''
|
||||
for group, perms in self.permissions:
|
||||
inner = HTML.tag('p', c=group)
|
||||
for perm, title in perms:
|
||||
checked = auth.has_permission(
|
||||
role, perm, include_guest=False, session=Session())
|
||||
if readonly:
|
||||
span = HTML.tag('span', c="[X]" if checked else "[ ]")
|
||||
inner += HTML.tag('p', class_='perm', c=span + ' ' + title)
|
||||
else:
|
||||
inner += tags.checkbox(self.name + '-' + perm,
|
||||
checked=checked, label=title)
|
||||
html += HTML.tag('div', class_='group', c=inner)
|
||||
return html
|
||||
|
||||
def render(self, **kwargs):
|
||||
return self._render(**kwargs)
|
||||
|
||||
def render_readonly(self, **kwargs):
|
||||
return self._render(readonly=True, **kwargs)
|
||||
|
||||
return PermissionsFieldRenderer
|
||||
|
||||
|
||||
class RoleCrud(CrudView):
|
||||
|
||||
mapped_class = Role
|
||||
home_route = 'roles'
|
||||
permissions = default_permissions
|
||||
|
||||
def fieldset(self, role):
|
||||
fs = self.make_fieldset(role)
|
||||
fs.append(PermissionsField(
|
||||
'permissions',
|
||||
renderer=PermissionsFieldRenderer(self.permissions)))
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.name,
|
||||
fs.permissions,
|
||||
])
|
||||
return fs
|
||||
|
||||
def pre_delete(self, model):
|
||||
admin = auth.administrator_role(Session())
|
||||
guest = auth.guest_role(Session())
|
||||
if model in (admin, guest):
|
||||
self.request.session.flash("You may not delete the %s role." % str(model), 'error')
|
||||
return HTTPFound(location=self.request.get_referrer())
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('roles', '/roles')
|
||||
config.add_view(RolesGrid, route_name='roles',
|
||||
renderer='/roles/index.mako',
|
||||
permission='roles.list')
|
||||
|
||||
settings = config.get_settings()
|
||||
perms = settings.get('edbob.permissions')
|
||||
if perms:
|
||||
RoleCrud.permissions = perms
|
||||
|
||||
config.add_route('role.create', '/roles/new')
|
||||
config.add_view(RoleCrud, attr='create', route_name='role.create',
|
||||
renderer='/roles/crud.mako',
|
||||
permission='roles.create')
|
||||
|
||||
config.add_route('role.read', '/roles/{uuid}')
|
||||
config.add_view(RoleCrud, attr='read', route_name='role.read',
|
||||
renderer='/roles/crud.mako',
|
||||
permission='roles.read')
|
||||
|
||||
config.add_route('role.update', '/roles/{uuid}/edit')
|
||||
config.add_view(RoleCrud, attr='update', route_name='role.update',
|
||||
renderer='/roles/crud.mako',
|
||||
permission='roles.update')
|
||||
|
||||
config.add_route('role.delete', '/roles/{uuid}/delete')
|
||||
config.add_view(RoleCrud, attr='delete', route_name='role.delete',
|
||||
permission='roles.delete')
|
134
tailbone/views/stores.py
Normal file
134
tailbone/views/stores.py
Normal file
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Store Views
|
||||
"""
|
||||
|
||||
from sqlalchemy import and_
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from rattail.db.model import Store, StoreEmailAddress, StorePhoneNumber
|
||||
|
||||
|
||||
class StoresGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Store
|
||||
config_prefix = 'stores'
|
||||
sort = 'id'
|
||||
|
||||
def join_map(self):
|
||||
return {
|
||||
'email':
|
||||
lambda q: q.outerjoin(StoreEmailAddress, and_(
|
||||
StoreEmailAddress.parent_uuid == Store.uuid,
|
||||
StoreEmailAddress.preference == 1)),
|
||||
'phone':
|
||||
lambda q: q.outerjoin(StorePhoneNumber, and_(
|
||||
StorePhoneNumber.parent_uuid == Store.uuid,
|
||||
StorePhoneNumber.preference == 1)),
|
||||
}
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(
|
||||
exact=['id'],
|
||||
ilike=['name'],
|
||||
email=self.filter_ilike(StoreEmailAddress.address),
|
||||
phone=self.filter_ilike(StorePhoneNumber.number))
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk',
|
||||
filter_label_id="ID")
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map(
|
||||
'id', 'name',
|
||||
email=self.sorter(StoreEmailAddress.address),
|
||||
phone=self.sorter(StorePhoneNumber.number))
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.id.label("ID"),
|
||||
g.name,
|
||||
g.phone.label("Phone Number"),
|
||||
g.email.label("Email Address"),
|
||||
],
|
||||
readonly=True)
|
||||
g.viewable = True
|
||||
g.view_route_name = 'store.read'
|
||||
if self.request.has_perm('stores.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'store.update'
|
||||
if self.request.has_perm('stores.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'store.delete'
|
||||
return g
|
||||
|
||||
|
||||
class StoreCrud(CrudView):
|
||||
|
||||
mapped_class = Store
|
||||
home_route = 'stores'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.id.label("ID"),
|
||||
fs.name,
|
||||
fs.phone.label("Phone Number").readonly(),
|
||||
fs.email.label("Email Address").readonly(),
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('stores', '/stores')
|
||||
config.add_view(StoresGrid, route_name='stores',
|
||||
renderer='/stores/index.mako',
|
||||
permission='stores.list')
|
||||
|
||||
config.add_route('store.create', '/stores/new')
|
||||
config.add_view(StoreCrud, attr='create', route_name='store.create',
|
||||
renderer='/stores/crud.mako',
|
||||
permission='stores.create')
|
||||
|
||||
config.add_route('store.read', '/stores/{uuid}')
|
||||
config.add_view(StoreCrud, attr='read', route_name='store.read',
|
||||
renderer='/stores/crud.mako',
|
||||
permission='stores.read')
|
||||
|
||||
config.add_route('store.update', '/stores/{uuid}/edit')
|
||||
config.add_view(StoreCrud, attr='update', route_name='store.update',
|
||||
renderer='/stores/crud.mako',
|
||||
permission='stores.update')
|
||||
|
||||
config.add_route('store.delete', '/stores/{uuid}/delete')
|
||||
config.add_view(StoreCrud, attr='delete', route_name='store.delete',
|
||||
permission='stores.delete')
|
116
tailbone/views/subdepartments.py
Normal file
116
tailbone/views/subdepartments.py
Normal file
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Subdepartment Views
|
||||
"""
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
|
||||
from rattail.db.model import Subdepartment
|
||||
|
||||
|
||||
class SubdepartmentsGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Subdepartment
|
||||
config_prefix = 'subdepartments'
|
||||
sort = 'name'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(ilike=['name'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk')
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('number', 'name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.configure(
|
||||
include=[
|
||||
g.number,
|
||||
g.name,
|
||||
g.department,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('subdepartments.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'subdepartment.read'
|
||||
if self.request.has_perm('subdepartments.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'subdepartment.update'
|
||||
if self.request.has_perm('subdepartments.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'subdepartment.delete'
|
||||
return g
|
||||
|
||||
|
||||
class SubdepartmentCrud(CrudView):
|
||||
|
||||
mapped_class = Subdepartment
|
||||
home_route = 'subdepartments'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.number,
|
||||
fs.name,
|
||||
fs.department,
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('subdepartments', '/subdepartments')
|
||||
config.add_view(SubdepartmentsGrid, route_name='subdepartments',
|
||||
renderer='/subdepartments/index.mako',
|
||||
permission='subdepartments.list')
|
||||
|
||||
config.add_route('subdepartment.create', '/subdepartments/new')
|
||||
config.add_view(SubdepartmentCrud, attr='create',
|
||||
route_name='subdepartment.create',
|
||||
renderer='/subdepartments/crud.mako',
|
||||
permission='subdepartments.create')
|
||||
|
||||
config.add_route('subdepartment.read', '/subdepartments/{uuid}')
|
||||
config.add_view(SubdepartmentCrud, attr='read',
|
||||
route_name='subdepartment.read',
|
||||
renderer='/subdepartments/crud.mako',
|
||||
permission='subdepartments.read')
|
||||
|
||||
config.add_route('subdepartment.update', '/subdepartments/{uuid}/edit')
|
||||
config.add_view(SubdepartmentCrud, attr='update',
|
||||
route_name='subdepartment.update',
|
||||
renderer='/subdepartments/crud.mako',
|
||||
permission='subdepartments.update')
|
||||
|
||||
config.add_route('subdepartment.delete', '/subdepartments/{uuid}/delete')
|
||||
config.add_view(SubdepartmentCrud, attr='delete',
|
||||
route_name='subdepartment.delete',
|
||||
permission='subdepartments.delete')
|
146
tailbone/views/users.py
Normal file
146
tailbone/views/users.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
User Views
|
||||
"""
|
||||
|
||||
import formalchemy
|
||||
|
||||
from edbob.pyramid.views import users
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView
|
||||
from ..forms import PersonFieldRenderer
|
||||
from rattail.db.model import User, Person
|
||||
|
||||
|
||||
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'],
|
||||
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')
|
||||
|
||||
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 UserCrud(CrudView):
|
||||
|
||||
mapped_class = User
|
||||
home_route = 'users'
|
||||
|
||||
def fieldset(self, user):
|
||||
fs = self.make_fieldset(user)
|
||||
|
||||
# Must set Person options to empty set to avoid unwanted magic.
|
||||
fs.person.set(options=[])
|
||||
fs.person.set(renderer=PersonFieldRenderer(
|
||||
self.request.route_url('people.autocomplete')))
|
||||
|
||||
fs.append(users.PasswordField('password'))
|
||||
fs.append(formalchemy.Field(
|
||||
'confirm_password', renderer=users.PasswordFieldRenderer))
|
||||
fs.append(users.RolesField(
|
||||
'roles', renderer=users.RolesFieldRenderer(self.request)))
|
||||
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.username,
|
||||
fs.person,
|
||||
fs.password.label("Set Password"),
|
||||
fs.confirm_password,
|
||||
fs.roles,
|
||||
])
|
||||
|
||||
if self.readonly:
|
||||
del fs.password
|
||||
del fs.confirm_password
|
||||
|
||||
return fs
|
||||
|
||||
|
||||
def includeme(config):
|
||||
|
||||
config.add_route('users', '/users')
|
||||
config.add_view(UsersGrid, route_name='users',
|
||||
renderer='/users/index.mako',
|
||||
permission='users.list')
|
||||
|
||||
config.add_route('user.create', '/users/new')
|
||||
config.add_view(UserCrud, attr='create', route_name='user.create',
|
||||
renderer='/users/crud.mako',
|
||||
permission='users.create')
|
||||
|
||||
config.add_route('user.read', '/users/{uuid}')
|
||||
config.add_view(UserCrud, attr='read', route_name='user.read',
|
||||
renderer='/users/crud.mako',
|
||||
permission='users.read')
|
||||
|
||||
config.add_route('user.update', '/users/{uuid}/edit')
|
||||
config.add_view(UserCrud, attr='update', route_name='user.update',
|
||||
renderer='/users/crud.mako',
|
||||
permission='users.update')
|
||||
|
||||
config.add_route('user.delete', '/users/{uuid}/delete')
|
||||
config.add_view(UserCrud, attr='delete', route_name='user.delete',
|
||||
permission='users.delete')
|
131
tailbone/views/vendors.py
Normal file
131
tailbone/views/vendors.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2012 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 Affero 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 Affero General Public License for
|
||||
# more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with Rattail. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
"""
|
||||
Vendor Views
|
||||
"""
|
||||
|
||||
from . import SearchableAlchemyGridView, CrudView, AutocompleteView
|
||||
from ..forms import AssociationProxyField, PersonFieldRenderer
|
||||
from rattail.db.model import Vendor
|
||||
|
||||
|
||||
class VendorsGrid(SearchableAlchemyGridView):
|
||||
|
||||
mapped_class = Vendor
|
||||
config_prefix = 'vendors'
|
||||
sort = 'name'
|
||||
|
||||
def filter_map(self):
|
||||
return self.make_filter_map(exact=['id'], ilike=['name'])
|
||||
|
||||
def filter_config(self):
|
||||
return self.make_filter_config(
|
||||
include_filter_name=True,
|
||||
filter_type_name='lk',
|
||||
filter_label_id="ID")
|
||||
|
||||
def sort_map(self):
|
||||
return self.make_sort_map('id', 'name')
|
||||
|
||||
def grid(self):
|
||||
g = self.make_grid()
|
||||
g.append(AssociationProxyField('contact'))
|
||||
g.configure(
|
||||
include=[
|
||||
g.id.label("ID"),
|
||||
g.name,
|
||||
g.phone.label("Phone Number"),
|
||||
g.email.label("Email Address"),
|
||||
g.contact,
|
||||
],
|
||||
readonly=True)
|
||||
if self.request.has_perm('vendors.read'):
|
||||
g.viewable = True
|
||||
g.view_route_name = 'vendor.read'
|
||||
if self.request.has_perm('vendors.update'):
|
||||
g.editable = True
|
||||
g.edit_route_name = 'vendor.update'
|
||||
if self.request.has_perm('vendors.delete'):
|
||||
g.deletable = True
|
||||
g.delete_route_name = 'vendor.delete'
|
||||
return g
|
||||
|
||||
|
||||
class VendorCrud(CrudView):
|
||||
|
||||
mapped_class = Vendor
|
||||
home_route = 'vendors'
|
||||
|
||||
def fieldset(self, model):
|
||||
fs = self.make_fieldset(model)
|
||||
fs.append(AssociationProxyField('contact'))
|
||||
fs.contact.set(renderer=PersonFieldRenderer(
|
||||
self.request.route_url('people.autocomplete')))
|
||||
fs.configure(
|
||||
include=[
|
||||
fs.id.label("ID"),
|
||||
fs.name,
|
||||
fs.special_discount,
|
||||
fs.phone.label("Phone Number").readonly(),
|
||||
fs.email.label("Email Address").readonly(),
|
||||
fs.contact.readonly(),
|
||||
])
|
||||
return fs
|
||||
|
||||
|
||||
class VendorsAutocomplete(AutocompleteView):
|
||||
|
||||
mapped_class = Vendor
|
||||
fieldname = 'name'
|
||||
|
||||
|
||||
def add_routes(config):
|
||||
config.add_route('vendors', '/vendors')
|
||||
config.add_route('vendors.autocomplete', '/vendors/autocomplete')
|
||||
config.add_route('vendor.create', '/vendors/new')
|
||||
config.add_route('vendor.read', '/vendors/{uuid}')
|
||||
config.add_route('vendor.update', '/vendors/{uuid}/edit')
|
||||
config.add_route('vendor.delete', '/vendors/{uuid}/delete')
|
||||
|
||||
|
||||
def includeme(config):
|
||||
add_routes(config)
|
||||
|
||||
config.add_view(VendorsGrid, route_name='vendors',
|
||||
renderer='/vendors/index.mako',
|
||||
permission='vendors.list')
|
||||
config.add_view(VendorsAutocomplete, route_name='vendors.autocomplete',
|
||||
renderer='json', permission='vendors.list')
|
||||
config.add_view(VendorCrud, attr='create', route_name='vendor.create',
|
||||
renderer='/vendors/crud.mako',
|
||||
permission='vendors.create')
|
||||
config.add_view(VendorCrud, attr='read', route_name='vendor.read',
|
||||
renderer='/vendors/crud.mako',
|
||||
permission='vendors.read')
|
||||
config.add_view(VendorCrud, attr='update', route_name='vendor.update',
|
||||
renderer='/vendors/crud.mako',
|
||||
permission='vendors.update')
|
||||
config.add_view(VendorCrud, attr='delete', route_name='vendor.delete',
|
||||
permission='vendors.delete')
|
Loading…
Add table
Add a link
Reference in a new issue