initial commit (save point)

This commit is contained in:
Lance Edgar 2012-04-10 12:39:30 -05:00
commit 81f6b57a63
29 changed files with 1488 additions and 0 deletions

5
CHANGES.txt Normal file
View file

@ -0,0 +1,5 @@
0.3a1
-----
- Initial port to Rattail v0.3.

2
MANIFEST.in Normal file
View file

@ -0,0 +1,2 @@
include *.txt *.ini *.cfg *.rst
recursive-include rattail/pyramid *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml

11
README.txt Normal file
View file

@ -0,0 +1,11 @@
rattail.pyramid
===============
Rattail is a retail software framework based on `edbob <http://edbob.org/>`_,
and released under the GNU Affero General Public License.
This package contains Pyramid views, etc., for managing a Rattail system.
Please see Rattail's `home page <http://rattail.edbob.org/>`_ for more
information.

2
rattail/__init__.py Normal file
View file

@ -0,0 +1,2 @@
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View file

@ -0,0 +1,78 @@
#!/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/>.
#
################################################################################
"""
``rattail.pyramid`` -- Rattail's Pyramid Framework
"""
# import os.path
# import pyramid_beaker
# from pyramid.config import Configurator
# import edbob
from rattail.pyramid._version import __version__
# def main(global_config, **settings):
# """
# This function returns a Pyramid WSGI application.
# """
# # Here you can insert any code to modify the ``settings`` dict.
# # You can:
# # * Add additional keys to serve as constants or "global variables" in the
# # application.
# # * Set default values for settings that may have been omitted.
# # * Override settings that you don't want the user to change.
# # * Raise an exception if a setting is missing or invalid.
# # * Convert values from strings to their intended type.
# settings['mako.directories'] = [
# 'something:templates',
# 'edbob.pyramid:templates',
# ]
# # Configure Pyramid
# config = Configurator(settings=settings)
# config.include('edbob.pyramid')
# config.include('something.subscribers')
# config.scan()
# # Configure Beaker
# session_factory = pyramid_beaker.session_factory_from_settings(settings)
# config.set_session_factory(session_factory)
# pyramid_beaker.set_cache_regions_from_settings(settings)
# # Configure edbob
# edbob.basic_logging()
# edbob.init('something', os.path.abspath(settings['edbob.config']))
# # Add static views
# # config.add_static_view('css', 'static/css', cache_max_age=3600)
# # config.add_static_view('img', 'static/img', cache_max_age=3600)
# # config.add_static_view('js', 'static/js', cache_max_age=3600)
# return config.make_wsgi_app()

View file

@ -0,0 +1 @@
__version__ = '0.3a1'

98
rattail/pyramid/forms.py Normal file
View file

@ -0,0 +1,98 @@
#!/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/>.
#
################################################################################
"""
``rattail.pyramid.forms`` -- Rattail Forms
"""
import formalchemy
# from formalchemy.fields import SelectFieldRenderer
from edbob.pyramid import Session
import rattail
__all__ = ['UpcFieldRenderer']
class BatchIdFieldRenderer(formalchemy.FieldRenderer):
"""
Renders the :attr:`rattail.Batch.batch_id` field.
"""
def render_readonly(self, **kwargs):
value = self.raw_value
if value is None:
return ''
return '%08u' % int(value)
# class BatchTerminalFieldRenderer(SelectFieldRenderer):
# """
# Renders a field whose value is a relationship to a
# :class:`rattail.BatchTerminal` instance.
# """
# def render(self, options, **kwargs):
class UpcFieldRenderer(formalchemy.TextFieldRenderer):
"""
Handles rendering for the product UPC field.
"""
def render_readonly(self, **kwargs):
value = self.raw_value
if not value:
return ''
if isinstance(value, basestring):
if value.isdigit():
value = int(value)
if isinstance(value, int):
return '%013u' % value
return self.stringify_value(value, as_html=True)
def unique_batch_terminal_id(value, field=None):
"""
.. highlight:: python
Validator for the :class:`rattail.BatchTerminal` class to ensure that SIL
IDs are not duplicated. For example::
from rattail.pyramid.forms import unique_batch_terminal_id
# fieldset = some_batch_terminal_fieldset_factory()
fieldset.sil_id.set(validate=unique_batch_terminal_id)
"""
if value:
q = Session.query(rattail.BatchTerminal)
q = q.filter(rattail.BatchTerminal.sil_id == value)
if field.parent.edit:
q = q.filter(rattail.BatchTerminal.uuid != field.parent.model.uuid)
if q.count():
raise formalchemy.ValidationError("SIL ID value must be unique within the system")

View file

@ -0,0 +1,49 @@
#!/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/>.
#
################################################################################
"""
``rattail.pyramid.subscribers`` -- Event Subscribers
"""
from pyramid import threadlocal
import rattail
def before_render(event):
"""
Adds goodies to the global template renderer context:
* ``rattail``
"""
request = event.get('request') or threadlocal.get_current_request()
renderer_globals = event
renderer_globals['rattail'] = rattail
def includeme(config):
config.add_subscriber('rattail.pyramid.subscribers:before_render',
'pyramid.events.BeforeRender')

View file

@ -0,0 +1,2 @@
<%inherit file="/base.mako" />
${parent.body()}

View file

@ -0,0 +1,13 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/crud.mako" />
<%def name="crud_name()">Batch</%def>
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
% if fieldset.edit and fieldset.model.rowcount:
<p>${h.link_to("View Batch Details", url('batch.details', uuid=fieldset.model.uuid))}</p>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,9 @@
<table class="fieldset">
<tr>
<td class="label">Columns</td>
<td>
${columns|n}
</td>
</tr>
</table>

View file

@ -0,0 +1,12 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/index.mako" />
<%def name="title()">Batch : ${batch.name}</%def>
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
<p>${h.link_to("View Batch Properties", url('batch') + '?uuid=' + batch.uuid)}</p>
<p>${h.link_to("Execute this Batch", url('batch.execute', uuid=batch.uuid))}</p>
</%def>
${parent.body()}

View file

@ -0,0 +1,11 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/index.mako" />
<%def name="title()">Batch Dictionaries</%def>
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
## <p>${h.link_to("Create a New Dictionary", url('batch_dictionary'))}</p>
</%def>
${parent.body()}

View file

@ -0,0 +1,11 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/crud.mako" />
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
<p>${h.link_to("Back to Dictionaries", url('batch_dictionaries'))}</p>
</%def>
${parent.body()}
${columns|n}

View file

@ -0,0 +1,8 @@
<table class="fieldset">
<tr class="columns">
<td class="label">Supported Columns</td>
<td>
${grid|n}
</td>
</tr>
</table>

View file

@ -0,0 +1,13 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/index.mako" />
<%def name="title()">Batches</%def>
<%def name="menu()">
<p>${h.link_to("Create a New Batch", url('batch'))}</p>
<p>${h.link_to("Manage Terminals", url('batch_terminals'))}</p>
<p>${h.link_to("View Dictionaries", url('batch_dictionaries'))}</p>
<p>${h.link_to("SIL Columns", url('sil_columns'))}</p>
</%def>
${parent.body()}

View file

@ -0,0 +1,9 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/crud.mako" />
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
<p>${h.link_to("Back to SIL Columns", url('sil_columns'))}</p>
</%def>
${parent.body()}

View file

@ -0,0 +1,10 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/index.mako" />
<%def name="title()">SIL Columns</%def>
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
</%def>
${parent.body()}

View file

@ -0,0 +1,24 @@
<table class="wrapper">
<tr>
<td><h2>Supported Fields</h2></td>
## <td class="right">${h.link_to("Update Field List", '#', id='update-fields')}</td>
</tr>
</table>
${grid|n}
##<script language="javascript" type="text/javascript">
##
##$(function() {
##
## $('#update-fields').click(function() {
## $('div.grid').load('${url('batches', action='update_terminal')}?uuid=${fieldset.model.uuid}');
## return false;
## });
##
##});
##
##</script>

View file

@ -0,0 +1,13 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/crud.mako" />
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
<p>${h.link_to("Back to Batch Terminals", url('batch_terminals'))}</p>
</%def>
${parent.body()}
% if fieldset.edit:
${terminal_columns|n}
% endif

View file

@ -0,0 +1,17 @@
<h2>Supported Columns</h2>
<table class="fieldset">
<tr class="source_columns">
<td class="label">Source</td>
<td>
${source|n}
</td>
</tr>
<tr class="target_columns">
<td class="label">Target</td>
<td>
${target|n}
</td>
</tr>
</table>

View file

@ -0,0 +1,11 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/index.mako" />
<%def name="title()">Batch Terminals</%def>
<%def name="menu()">
<p>${h.link_to("Back to Batches", url('batches'))}</p>
<p>${h.link_to("Create a New Terminal", url('batch_terminal'))}</p>
</%def>
${parent.body()}

View file

@ -0,0 +1,2 @@
<%inherit file="/base.mako" />
${parent.body()}

View file

@ -0,0 +1,9 @@
<%inherit file="/products/base.mako" />
<%inherit file="/index.mako" />
<%def name="title()">Products</%def>
<%def name="menu()">
</%def>
${parent.body()}

View file

View file

@ -0,0 +1,858 @@
#!/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/>.
#
################################################################################
"""
``rattail.pyramid.views.batches`` -- Batch (et al.) Views
"""
import formalchemy
from sqlalchemy import and_, or_
from pyramid.httpexceptions import HTTPFound
from pyramid.renderers import render
from pyramid.response import Response
from pyramid.view import view_config
from edbob.exceptions import LoadSpecError
from edbob.pyramid import filters
from edbob.pyramid import forms
from edbob.pyramid import grids
from edbob.pyramid import Session
from edbob.pyramid.views.crud import crud
from edbob.util import prettify
import rattail
from rattail import sil
from rattail.batches import next_batch_id
from rattail.pyramid.forms import (
BatchIdFieldRenderer,
unique_batch_terminal_id,
)
@view_config(route_name='sil_columns', renderer='/batches/sil_columns.mako')
def sil_columns(context, request):
"""
Index page for SIL columns (:class:`rattail.SilColumn`).
"""
fmap = filters.get_filter_map(
rattail.SilColumn,
exact=['sil_name'],
ilike=['display'])
config = filters.get_search_config(
'sil_columns', request, fmap,
include_filter_sil_name=True,
filter_type_sil_name='eq',
include_filter_display=True,
filter_type_display='lk')
search = filters.get_search_form(
config, sil_name="SIL Name")
config = grids.get_grid_config(
'sil_columns', request, search,
filter_map=fmap, sort='sil_name')
smap = grids.get_sort_map(
rattail.SilColumn,
['sil_name', 'display', 'data_type'])
def query(config):
q = Session.query(rattail.SilColumn)
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
columns = grids.get_pager(query, config)
g = forms.AlchemyGrid(
rattail.SilColumn, columns, config,
request.route_url('sil_columns'),
url_object=request.route_url('sil_column'),
url_delete=request.route_url('sil_column'))
g.configure(
include=[
g.sil_name.label("SIL Name"),
g.display,
g.data_type,
])
g.readonly = True
grid = g.render(class_='clickable batch-columns')
return grids.render_grid(request, grid, search)
@view_config(route_name='sil_column', renderer='/batches/sil_column.mako')
def sil_column(context, request):
def fieldset(column):
fs = forms.make_fieldset(
column, crud_title="Batch Column",
url_action=request.route_url('sil_column'),
url_cancel=request.route_url('sil_columns'))
fs.configure(
include=[
fs.sil_name.label("SIL Name"),
fs.display,
fs.data_type,
])
if fs.edit:
fs.sil_name.set(readonly=True)
return fs
return crud(
request, rattail.SilColumn, fieldset,
home=request.route_url('sil_columns'),
delete=request.route_url('sil_columns'))
@view_config(route_name='batch_dictionaries', renderer='/batches/dictionaries.mako')
def batch_dictionaries(context, request):
"""
Index page for batch dictionaries (:class:`rattail.BatchDictionary`).
"""
fmap = filters.get_filter_map(
rattail.BatchDictionary,
exact=['name'],
ilike=['description'])
config = filters.get_search_config(
'batch_dictionaries', request, fmap,
include_filter_name=True,
filter_type_name='eq',
include_filter_description=True,
filter_type_description='lk')
search = filters.get_search_form(config)
config = grids.get_grid_config(
'batch_dictionaries', request, search,
filter_map=fmap, sort='description')
smap = grids.get_sort_map(
rattail.BatchDictionary,
['name', 'description'])
def query(config):
q = Session.query(rattail.BatchDictionary)
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
dictionaries = grids.get_pager(query, config)
g = forms.AlchemyGrid(
rattail.BatchDictionary, dictionaries, config,
request.route_url('batch_dictionaries'),
url_object=request.route_url('batch_dictionary'),
url_delete=request.route_url('batch_dictionary'))
g.configure(
include=[
g.name,
g.description,
])
g.readonly = True
grid = g.render(class_='clickable batch-dictionaries')
return grids.render_grid(request, grid, search)
def _dictionary_columns(request, uuid=None):
"""
Returns a rendered grid of columns for a batch dictionary.
"""
if not uuid:
uuid = request.params.get('uuid')
dictionary = Session.query(rattail.BatchDictionary).get(uuid) if uuid else None
assert dictionary
fmap = filters.get_filter_map(
rattail.BatchDictionaryColumn,
exact=['key'])
fmap['column'] = filters.filter_exact(rattail.SilColumn.sil_name)
fmap['display'] = filters.filter_ilike(rattail.SilColumn.display)
config = grids.get_grid_config(
'batch_dictionary.columns', request,
filter_map=fmap, sort='column')
smap = grids.get_sort_map(
rattail.BatchDictionaryColumn,
['key'])
smap['column'] = grids.sorter(rattail.SilColumn.sil_name)
smap['display'] = grids.sorter(rattail.SilColumn.display)
def query(config):
q = Session.query(rattail.BatchDictionaryColumn)
q = q.join(rattail.SilColumn)
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
columns = query(config)
url = (request.route_url('batch_dictionary.columns')
+ '?uuid=' + dictionary.uuid)
g = forms.AlchemyGrid(
rattail.BatchDictionaryColumn, columns, config, url)
g.append(
formalchemy.Field(
'display',
value=lambda x: x.column.display))
g.configure(
include=[
g.column,
g.display,
g.key,
])
g.readonly = True
return g.render(class_='hoverable batch-dictionary-columns')
@view_config(route_name='batch_dictionary.columns')
def batch_dictionary_columns(context, request):
return Response(body=_dictionary_columns(request),
content_type='text/html')
@view_config(route_name='batch_dictionary', renderer='/batches/dictionary.mako')
def batch_dictionary(context, request):
def fieldset(dictionary):
fs = forms.make_fieldset(
dictionary, crud_title="Batch Dictionary",
url_action=request.route_url('batch_dictionary'),
url_cancel=request.route_url('batch_dictionaries'))
fs.configure(
include=[
fs.name,
fs.description,
])
fs.readonly = True
return fs
def pre_render(fs):
if fs.edit:
g = _dictionary_columns(request, fs.model.uuid)
return {'columns':
render('/batches/dictionary_columns.mako', {'grid':g})}
return crud(
request, rattail.BatchDictionary, fieldset,
home=request.route_url('batch_dictionaries'),
delete=request.route_url('batch_dictionaries'),
pre_render=pre_render)
def _terminal_columns(request, terminal, column_type):
"""
Returns a rendered grid of either
:class:`rattail.BatchTerminalSourceColumn` or
:class:`rattail.BatchTerminalTargetColumn` instances, depending on
``column_type`` (and of cource ``terminal``).
"""
cls = getattr(rattail, 'BatchTerminal%sColumn' % column_type.title())
config = grids.get_grid_config(
'batch_terminal.columns', request, sort='dictionary')
smap = {
'dictionary': grids.sorter(rattail.BatchDictionary.name),
'column': grids.sorter(rattail.SilColumn.sil_name),
'display': grids.sorter(rattail.SilColumn.display),
}
def query(config):
q = Session.query(cls)
q = q.join(rattail.BatchDictionary)
q = q.join(rattail.SilColumn)
q = q.filter(cls.terminal == terminal)
q = grids.sort_query(q, config, smap)
return q
url = (request.route_url('batch_terminal.columns')
+ '?uuid=' + terminal.uuid + '&type=' + column_type)
columns = query(config)
g = forms.AlchemyGrid(cls, columns, config, url)
g.append(
formalchemy.Field(
'display',
value=lambda x: x.column.display))
g.configure(
include=[
g.dictionary,
g.column,
g.display,
])
g.readonly = True
return g.render(class_='hoverable batch-terminal-columns')
@view_config(route_name='batch_terminal.columns')
def batch_terminal_columns(context, request):
uuid = request.params.get('uuid')
terminal = Session.query(rattail.BatchTerminal).get(uuid) if uuid else None
assert terminal
return Response(
body=_terminal_columns(request, terminal, request.params.get('type')),
content_type='text/html')
# @view_config(route_name='batch_terminal.source_columns')
# def batch_terminal_source_columns(context, request):
# return Response(body=_terminal_columns(request, 'source'),
# content_type='text/html')
# @view_config(route_name='batch_terminal.target_columns')
# def batch_terminal_target_columns(context, request):
# return Response(body=_terminal_columns(request, 'target'),
# content_type='text/html')
def update_terminal_info(terminal):
"""
Updates the terminal's list of supported source and target columns, based
on feedback from the actual :class:`rattail.batch.BatchTerminal` instance.
Also sets the terminal's ``functional`` flag (et al.) to reflect reality.
"""
terminal.functional = True
try:
true_terminal = terminal.get_terminal()
except LoadSpecError, err:
terminal.functional = False
return err
else:
if true_terminal:
for column_type in ('source', 'target'):
declared_columns = getattr(true_terminal, '%s_columns' % column_type)
stored_columns = getattr(terminal, '%s_columns' % column_type)
cls = getattr(rattail, 'BatchTerminal%sColumn' % column_type.title())
for dictionary, columns in declared_columns.iteritems():
q = Session.query(rattail.BatchDictionary)
q = q.filter_by(name=dictionary)
dictionary = q.one()
for col in columns:
q = Session.query(rattail.SilColumn)
q = q.filter_by(sil_name=col)
col = q.one()
stored_columns.append(cls(
dictionary=dictionary,
column=col))
setattr(terminal, column_type, bool(declared_columns))
return true_terminal
else:
terminal.functional = False
@view_config(route_name='batch_terminal', renderer='/batches/terminal.mako')
def batch_terminal(context, request):
def fieldset(terminal):
fs = forms.make_fieldset(
terminal, crud_title="Batch Terminal",
url_action=request.route_url('batch_terminal'),
url_cancel=request.route_url('batch_terminals'))
fs.configure(
include=[
fs.sil_id.label("SIL ID").validate(unique_batch_terminal_id),
fs.description,
fs.class_spec.validate(forms.required),
fs.functional.readonly(),
fs.source_kwargs,
fs.target_kwargs,
])
if fs.edit:
fs.sil_id.set(readonly=True)
else:
del fs.source_kwargs
del fs.target_kwargs
return fs
def pre_render(fs):
if fs.edit:
data = {
'source': _terminal_columns(request, fs.model, 'source'),
'target': _terminal_columns(request, fs.model, 'target'),
}
return {'terminal_columns':
render('/batches/terminal_columns.mako', data)}
def post_sync(fs):
terminal = update_terminal_info(fs.model)
if isinstance(terminal, LoadSpecError):
request.session.flash(str(terminal))
elif terminal:
if not fs.model.sil_id:
fs.model.sil_id = terminal.name
if not fs.model.description:
fs.model.description = terminal.description
return crud(
request, rattail.BatchTerminal, fieldset,
delete=request.route_url('batch_terminals'),
pre_render=pre_render, post_sync=post_sync)
@view_config(route_name='batch_terminals', renderer='/batches/terminals.mako')
def batch_terminals(context, request):
fmap = filters.get_filter_map(
rattail.BatchTerminal,
exact=['sil_id'],
ilike=['description'])
config = filters.get_search_config(
'batch_terminals', request, fmap,
include_filter_description=True,
filter_type_description='lk')
search = filters.get_search_form(
config, sil_id="SIL ID")
config = grids.get_grid_config(
'batch_terminals', request, search,
filter_map=fmap, sort='description', deletable=True)
smap = grids.get_sort_map(
rattail.BatchTerminal,
['sil_id', 'description'])
def query(config):
q = Session.query(rattail.BatchTerminal)
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
terminals = grids.get_pager(query, config)
g = forms.AlchemyGrid(
rattail.BatchTerminal, terminals, config,
request.route_url('batch_terminals'),
url_object=request.route_url('batch_terminal'),
url_delete=request.route_url('batch_terminal'))
g.configure(
include=[
g.sil_id.label("SIL ID"),
g.description,
])
g.readonly = True
grid = g.render(class_='clickable batch-terminals')
return grids.render_grid(request, grid, search)
@view_config(route_name='batches', renderer='/batches/index.mako')
def batches(context, request):
fmap = filters.get_filter_map(
rattail.Batch,
ilike=['description', 'source_description'],
target=filters.filter_ilike(rattail.BatchTerminal.description))
config = filters.get_search_config(
'batches', request, fmap,
include_filter_description=True,
filter_type_description='lk',
include_filter_source_description=True,
filter_type_source_description='lk',
include_filter_target=True,
filter_type_target='lk')
search = filters.get_search_form(
config, source_description="Source")
config = grids.get_grid_config(
'batches', request, search,
filter_map=fmap, deletable=True, sort='target')
smap = grids.get_sort_map(
rattail.Batch,
['source_description', 'batch_id', 'action_type',
'description', 'rowcount', 'effective'],
target=grids.sorter(rattail.BatchTerminal.description))
def query(config):
q = Session.query(rattail.Batch)
q = q.outerjoin((rattail.BatchTerminal,
rattail.BatchTerminal.uuid == rattail.Batch.target_uuid))
q = q.filter(or_(
rattail.Batch.deleted == None,
and_(
rattail.Batch.deleted != None,
rattail.Batch.deleted != True,
),
))
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
batches = grids.get_pager(query, config)
g = forms.AlchemyGrid(
rattail.Batch, batches, config,
request.route_url('batches'),
url_object=request.route_url('batch'),
url_delete=request.route_url('batch'))
g.configure(
include=[
g.source_description.label("Source"),
g.batch_id.with_renderer(BatchIdFieldRenderer).label("Batch ID"),
g.target,
g.action_type.with_renderer(forms.EnumFieldRenderer(rattail.BATCH_ACTION_TYPE)).label("Action"),
g.description,
g.rowcount.label("Rows"),
g.effective.with_renderer(forms.PrettyDateTimeFieldRenderer),
])
g.readonly = True
grid = g.render(class_='clickable batches')
return grids.render_grid(request, grid, search)
def _batch_columns(request, batch):
"""
Returns a rendered grid of :class:`rattail.BatchColumn` instances for a
given :class:`rattail.Batch` instance.
"""
config = grids.get_grid_config(
'batch.columns', request, sort='ordinal')
smap = grids.get_sort_map(
rattail.BatchColumn,
['ordinal', 'targeted'])
smap['column'] = grids.sorter(rattail.SilColumn.sil_name)
smap['display'] = grids.sorter(rattail.SilColumn.display)
smap['source'] = grids.sorter(rattail.BatchTerminal.description)
def query(config):
q = Session.query(rattail.BatchColumn)
q = q.join(rattail.SilColumn)
q = q.join(rattail.BatchTerminal)
q = q.filter(rattail.BatchColumn.batch == batch)
q = grids.sort_query(q, config, smap)
return q
url = request.route_url('batch.columns', uuid=batch.uuid)
columns = query(config)
g = forms.AlchemyGrid(rattail.BatchColumn, columns, config, url)
g.append(formalchemy.Field('display', value=lambda x: x.column.display))
g.configure(
include=[
g.ordinal,
g.column.label("SIL Name"),
g.display,
g.source,
g.targeted,
])
g.readonly = True
return g.render(class_='hoverable batch-columns')
@view_config(route_name='batch.columns')
def batch_columns(context, request):
uuid = request.matchdict['uuid']
batch = Session.query(rattail.Batch).get(uuid) if uuid else None
assert batch
return Response(
body=_batch_columns(request, batch),
content_type='text/html')
@view_config(route_name='batch', renderer='/batches/batch.mako')
def batch(context, request):
def fieldset(batch):
fs = forms.make_fieldset(batch, url=request.route_url,
url_action=request.route_url('batch'),
route_name='batches')
# Remove unsupported action types...for now.
enum = rattail.BATCH_ACTION_TYPE.copy()
del enum[rattail.BATCH_ADD_REPLACE]
del enum[rattail.BATCH_CHANGE]
del enum[rattail.BATCH_LOAD]
del enum[rattail.BATCH_REMOVE]
fs.configure(
include=[
fs.source.validate(forms.required),
fs.batch_id.with_renderer(BatchIdFieldRenderer).label("Batch ID").readonly(),
fs.target.validate(forms.required),
fs.dictionary.validate(forms.required),
fs.action_type.with_renderer(forms.EnumFieldRenderer(enum)).validate(forms.required).label("Action"),
fs.description,
fs.effective.with_renderer(forms.PrettyDateTimeFieldRenderer),
fs.rowcount.label("Rows").readonly(),
])
if fs.edit:
fs.source.set(readonly=True)
fs.dictionary.set(readonly=True)
if fs.model.target:
fs.target.set(readonly=True)
fs.action_type.set(readonly=True)
fs.effective.set(readonly=True)
fs.append(forms.ChildGridField('columns', _batch_columns(request, fs.model)))
else:
del fs.batch_id
del fs.effective
del fs.rowcount
return fs
def post_sync(fs):
if not fs.edit:
source = Session.query(rattail.BatchTerminal).get(fs.model.source_uuid)
target = Session.query(rattail.BatchTerminal).get(fs.model.target_uuid)
assert source and target
batch = fs.model
batch.batch_id = next_batch_id(source.sil_id, consume=True,
session=Session())
batch.name = '%s.%08u' % (source.sil_id, batch.batch_id)
batch.source_description = source.description
for i, col in enumerate(source.source_columns, 1):
batch.columns.append(rattail.BatchColumn(
ordinal=i,
column=col.column,
source=source,
targeted=True,
))
def pre_render(fs):
if not fs.edit:
for field in ('source', 'target'):
if not fs.render_fields[field].value:
q = Session.query(rattail.BatchTerminal)
q = q.filter(getattr(rattail.BatchTerminal, field) == True)
count = q.count()
if count == 1:
setattr(fs.model, field, q.first())
fs.render_fields[field].set(required=True) # hack to avoid null option
elif not count:
request.session.flash("You must create at least one %s Batch Terminal "
"before you may create a Batch." % prettify(field))
return HTTPFound(location=request.route_url('batches'))
# else:
# data = {'columns': _batch_columns(request, fs.model)}
# return {'columns': render('/batches/columns.mako', data)}
# def delete(batch):
# # jct = batch.target_junction.display if batch.target_junction else batch.target
# # batch.table.drop()
# # self.request.session.flash("Batch was deleted: %s" % self.batch_display(batch))
# self.Session.delete(batch)
# # self.Session.flush()
return crud(request, rattail.Batch, fieldset,
delete=request.route_url('batches'),
post_sync=post_sync, pre_render=pre_render)
@view_config(route_name='batch.details', renderer='/batches/details.mako')
def batch_details(context, request):
uuid = request.matchdict['uuid']
batch = Session.query(rattail.Batch).get(uuid) if uuid else None
assert batch
request.session['batches.uuid'] = batch.uuid
name = 'batches.%s' % batch.name
# def batch_search_form(batch, name):
# """
# Returns a dynamically-assembled search form, specific to the current
# batch.
# """
# config = filters.get_search_config(name, request, batch_filter_map(batch))
# labels = {}
# form = filters.get_search_form(config, **labels)
# return form
# def batch_query(batch):
# def query(config):
# q = Session.query(batch.rowclass)
# q = filters.filter_query(q, config, batch_filter_map(batch))
# q = grids.sort_query(q, config, batch_sort_map(batch))
# return q
# return query
# def batch_grid(batch):
# cls = batch.rowclass
# def grid(query, config):
# rows = grids.get_pager(query, config)
# g = grids.get_grid(cls, rows, config)
# include = [getattr(g, x.column.sil_name) for x in batch.columns]
# # if batch.target_junction:
# # fnmap = batch.target_junction.fieldname_map_ui
# # for name in fnmap:
# # getattr(g, name).set(label=fnmap[name])
# g.configure(include=include)
# return g
# return grid
cls = batch.rowclass
fmap = filters.get_filter_map(cls, exact=['F01'])
for col in batch.columns:
fmap.setdefault(col.column.sil_name,
filters.filter_ilike(getattr(cls, col.column.sil_name)))
config = filters.get_search_config('batch.%s' % batch.name, request, fmap)
search = filters.get_search_form(config)
config = grids.get_grid_config(
'batch.%s' % batch.name, request, search, filter_map=fmap,
url=request.route_url('batch.details', uuid=batch.uuid),
sort=batch.columns[0].column.sil_name)
# url = request.route_url('batch.details')
# grid = forms.AlchemyGrid(name, batch_grid(batch), batch_query(batch),
# search, url=url, sort='F01', per_page=20)
# query = batch_query(batch)
smap = grids.get_sort_map(cls)
# cls = batch.rowclass
# # return {
# # 'F01': util.sorter(cls.F01),
# # }
# sort_map = {}
# for name in batch.elements.split(','):
# sort_map[name] = util.sorter(getattr(cls, name))
def query(config):
q = Session.query(batch.rowclass)
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
rows = grids.get_pager(query, config)
grid = forms.AlchemyGrid(
cls, rows, config,
request.route_url('batch.details', uuid=batch.uuid))
grid.readonly = True
grid = grid.render(class_='clickable batch-details')
return grids.render_grid(request, grid, search, batch=batch,
pretty_datetime=forms.pretty_datetime)
@view_config(route_name='batch.execute')
def execute(context, request):
"""
Executes a batch.
"""
home = HTTPFound(location=request.route_url('batches'))
# batch = self.request.params.get('uuid')
# batch = self.Session.query(rattail.Batch).get(batch) if batch else None
# if not batch:
# return home
# print 'got a batch'
uuid = request.matchdict['uuid']
batch = Session.query(rattail.Batch).get(uuid) if uuid else None
if not batch:
return home
# print 'got a batch'
# jct = batch.target_junction
# if not jct:
# self.request.session.flash("Batch does not have a valid target")
# return home
# print 'got a target'
# if not batch.target:
# request.session.flash("Batch does not have a valid target")
# return home
# jct = jct()
# if batch.dictionary == rattail.BATCH_MAIN_ITEM:
# print 'exporting main item'
# jct.export_main_item(batch, self.Session)
# print 'exported main item'
batch.execute()
# table = batch.table
# rowclass = batch.rowclass
# # self.Session.delete(batch)
# batch.deleted = True
# self.Session.flush()
# print 'deleted batch'
# q = self.Session.query(rowclass)
# q.delete(synchronize_session=False)
# print 'deleted rows'
# # table.drop(bind=self.Session().bind, checkfirst=False)
# # print 'dropped table'
request.session.flash("The batch was executed: %s, %s" % (
batch.target, rattail.BATCH_ACTION_TYPE[batch.action_type]))
return home
def includeme(config):
config.add_route('sil_columns', '/batches/sil_columns')
config.add_route('sil_column', '/batches/sil_column')
config.add_route('batch_dictionaries', '/batches/dictionaries')
config.add_route('batch_dictionary', '/batches/dictionary')
config.add_route('batch_dictionary.columns', '/batches/dictionary-columns')
config.add_route('batch_terminals', '/batches/terminals')
config.add_route('batch_terminal', '/batches/terminal')
config.add_route('batch_terminal.columns', '/batches/terminal-columns')
config.add_route('batches', '/batches')
config.add_route('batch', '/batch')
config.add_route('batch.columns', '/batch/{uuid}/columns')
config.add_route('batch.details', '/batch/{uuid}/details')
config.add_route('batch.execute', '/batch/{uuid}/execute')
config.scan(__name__)

View file

@ -0,0 +1,88 @@
#!/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/>.
#
################################################################################
"""
``rattail.pyramid.views.products`` -- Product Views
"""
from pyramid.view import view_config
from edbob.pyramid import filters
from edbob.pyramid import forms
from edbob.pyramid import grids
from edbob.pyramid import Session
import rattail
from rattail.pyramid.forms import UpcFieldRenderer
@view_config(route_name='products', renderer='/products/index.mako')
def products(context, request):
fmap = filters.get_filter_map(
rattail.Product,
exact=['upc'],
ilike=['description'])
config = filters.get_search_config(
'products', request, fmap,
include_filter_description=True,
filter_type_description='lk')
search = filters.get_search_form(
config, upc="UPC")
config = grids.get_grid_config(
'products', request, search,
filter_map=fmap, sort='description')
smap = grids.get_sort_map(
rattail.Product,
['upc', 'description'])
def query(config):
q = Session.query(rattail.Product)
q = filters.filter_query(q, config)
q = grids.sort_query(q, config, smap)
return q
products = grids.get_pager(query, config)
g = forms.AlchemyGrid(
rattail.Product, products, config,
request.route_url('products'))
g.configure(
include=[
g.upc.with_renderer(UpcFieldRenderer).label("UPC"),
g.description,
])
g.readonly = True
grid = g.render(class_='clickable products')
return grids.render_grid(request, grid, search)
def includeme(config):
config.add_route('products', '/products')
config.scan(__name__)

2
setup.cfg Normal file
View file

@ -0,0 +1,2 @@
[egg_info]
tag_build = .dev

120
setup.py Normal file
View file

@ -0,0 +1,120 @@
#!/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/>.
#
################################################################################
import os.path
from setuptools import setup, find_packages
here = os.path.abspath(os.path.dirname(__file__))
execfile(os.path.join(here, 'rattail', 'pyramid', '_version.py'))
README = open(os.path.join(here, 'README.txt')).read()
CHANGES = open(os.path.join(here, 'CHANGES.txt')).read()
requires = [
#
# Version numbers within comments below have specific meanings.
# Basically the 'low' value is a "soft low," and 'high' a "soft high."
# In other words:
#
# If either a 'low' or 'high' value exists, the primary point to be
# made about the value is that it represents the most current (stable)
# version available for the package (assuming typical public access
# methods) whenever this project was started and/or documented.
# Therefore:
#
# If a 'low' version is present, you should know that attempts to use
# versions of the package significantly older than the 'low' version
# may not yield happy results. (A "hard" high limit may or may not be
# indicated by a true version requirement.)
#
# Similarly, if a 'high' version is present, and especially if this
# project has laid dormant for a while, you may need to refactor a bit
# when attempting to support a more recent version of the package. (A
# "hard" low limit should be indicated by a true version requirement
# when a 'high' version is present.)
#
# In any case, developers and other users are encouraged to play
# outside the lines with regard to these soft limits. If bugs are
# encountered then they should be filed as such.
#
# package # low high
# Beaker dependency included here because 'pyramid_beaker' uses incorrect
# case in its requirement declaration.
'Beaker', # 1.6.3
# Pyramid 1.3 introduced 'pcreate' command (and friends) to replace
# deprecated 'paster create' (and friends).
'pyramid>=1.3a1', # 1.3b2
'decorator', # 3.3.2
'Mako', # 0.6.2
'pyramid_beaker', # 0.6.1
'pyramid_debugtoolbar', # 1.0
'pyramid_tm', # 0.3
'rattail>=0.3a1.dev', # 0.3a1.dev
'SQLAlchemy', # 0.7.6
'Tempita', # 0.5.1
'transaction', # 1.2.0
'waitress', # 0.8.1
'WebHelpers', # 1.3
'zope.sqlalchemy', # 0.7
]
setup(
name = "rattail.pyramid",
version = __version__,
author = "Lance Edgar",
author_email = "lance@edbob.org",
url = "http://rattail.edbob.org/",
license = "GNU Affero GPL v3",
description = "Rattail Pyramid Framework",
long_description = README + '\n\n' + CHANGES,
classifiers = [
'Development Status :: 3 - Alpha',
'Environment :: Web Environment',
'Framework :: Pylons',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU Affero General Public License v3',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Office/Business',
'Topic :: Software Development :: Libraries :: Python Modules',
],
install_requires = requires,
namespace_packages = ['rattail'],
packages = find_packages(),
include_package_data = True,
zip_safe = False,
)