extensive commit (see note)

The following changes are included:

- Added support for GPC data type.

- Added eager import of ``rattail.sil`` in ``before_render`` hook.

- Removed ``rattail.pyramid.util`` module.

- Added initial batch support: views, templates, creation from Product grid.

- Added batch params template for ``PrintLabels`` provider.

- Added support for ``rattail.LabelProfile`` class.

- Improved Product grid to include filter/sort on Vendor.
This commit is contained in:
Lance Edgar 2012-08-29 11:37:17 -07:00
parent 563a10a283
commit cf10fe19e8
36 changed files with 1166 additions and 1529 deletions

View file

@ -29,39 +29,23 @@
from webhelpers.html import literal
import formalchemy
# from formalchemy.fields import SelectFieldRenderer
import edbob
from edbob.pyramid import Session
from edbob.pyramid.forms import pretty_datetime
import rattail
__all__ = ['PriceFieldRenderer', 'RegularPriceFieldRenderer', 'UpcFieldRenderer']
class BatchIdFieldRenderer(formalchemy.FieldRenderer):
class GPCFieldRenderer(formalchemy.TextFieldRenderer):
"""
Renders the :attr:`rattail.Batch.batch_id` field.
Renderer for :class:`rattail.barcodes.GPC` fields.
"""
def render_readonly(self, **kwargs):
value = self.raw_value
if value is None:
return ''
return '%08u' % int(value)
@property
def length(self):
# Hm, should maybe consider hard-coding this...?
return len(str(rattail.GPC(0)))
# class BatchTerminalFieldRenderer(SelectFieldRenderer):
# """
# Renders a field whose value is a relationship to a
# :class:`rattail.BatchTerminal` instance.
# """
# def render(self, options, **kwargs):
class PriceFieldRenderer(formalchemy.FieldRenderer):
"""
Renderer for fields which reference a :class:`ProductPrice` instance.
@ -98,42 +82,3 @@ class PriceWithExpirationFieldRenderer(PriceFieldRenderer):
if price.ends:
res += '  (%s)' % pretty_datetime(price.ends, from_='utc')
return res
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, long)):
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

@ -41,6 +41,9 @@ def before_render(event):
# Import labels module so it's available if/when needed.
import rattail.labels
# Import SIL module so it's available if/when needed.
import rattail.sil
request = event.get('request') or threadlocal.get_current_request()
renderer_globals = event

View file

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

View file

@ -1,14 +0,0 @@
<%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.list'))}</p>
% if fieldset.edit:
<p>${h.link_to("View Batch Details", url('batch.details', uuid=fieldset.model.uuid))}</p>
<p>${h.link_to("Execute this Batch", url('batch.execute', uuid=fieldset.model.uuid))}</p>
% endif
</%def>
${parent.body()}

View file

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

View file

@ -0,0 +1,8 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Batches", url('batches'))}</li>
<li>${h.link_to("View Batch Rows", url('batch.rows', uuid=form.fieldset.model.uuid))}</li>
</%def>
${parent.body()}

View file

@ -1,12 +0,0 @@
<%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.list'))}</p>
<p>${h.link_to("View Batch Properties", url('batch.edit', uuid=batch.uuid))}</p>
<p>${h.link_to("Execute this Batch", url('batch.execute', uuid=batch.uuid))}</p>
</%def>
${parent.body()}

View file

@ -1,11 +0,0 @@
<%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.list'))}</p>
## <p>${h.link_to("Create a New Dictionary", url('batch_dictionary'))}</p>
</%def>
${parent.body()}

View file

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

View file

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

View file

@ -1,38 +1,5 @@
<%inherit file="/batches/base.mako" />
<%inherit file="/index.mako" />
<%inherit file="/grid.mako" />
<%def name="title()">Batches</%def>
<%def name="head_tags()">
<style type="text/css">
div.grid table tbody td.rowcount {
text-align: right;
}
</style>
<script language="javascript" type="text/javascript">
$(function() {
$('div.grid table tbody td.action.execute a').live('click', function() {
var tr = $(this).parents('tr:first');
var desc = tr.find('td.description').text();
if (confirm("Do you really wish to execute this batch?\n\n" + desc)) {
var url = '${url('batch.execute', uuid='{uuid}')}';
location.href = url.replace(/%7Buuid%7D/, get_uuid(this));
}
return false;
});
});
</script>
</%def>
<%def name="menu()">
## <p>${h.link_to("Create a New Batch", url('batch.new'))}</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,42 @@
<%inherit file="/base.mako" />
<%def name="title()">Batch Parameters</%def>
<%def name="head_tags()">
${parent.head_tags()}
<script language="javascript" type="text/javascript">
$(function() {
$('#create-batch').click(function() {
disable_button(this, "Creating batch");
disable_button('#cancel');
$('form').submit();
});
});
</script>
</%def>
<%def name="batch_params()"></%def>
<p>Please provide the following values for your new batch:</p>
<br />
<div class="form">
${h.form(request.get_referrer())}
${h.hidden('provider', value=provider)}
${h.hidden('params', value='True')}
${self.batch_params()}
<div class="buttons">
<button type="button" id="create-batch">Create Batch</button>
<button type="button" id="cancel" onclick="location.href = '${request.get_referrer()}';">Cancel</button>
</div>
${h.end_form()}
</div>

View file

@ -0,0 +1,19 @@
<%inherit file="/batches/params.mako" />
<%def name="batch_params()">
<div class="field-wrapper">
<label for="profile">Label Type</label>
<div class="field">
${h.select('profile', None, label_profiles)}
</div>
</div>
<div class="field-wrapper">
<label for="quantity">Quantity</label>
<div class="field">${h.text('quantity', value=1)}</div>
</div>
</%def>
${parent.body()}

View file

@ -0,0 +1,40 @@
<%inherit file="/batches/crud.mako" />
<%def name="context_menu_items()">
${parent.context_menu_items()}
<li>${h.link_to("Edit this Batch", url('batch.update', uuid=form.fieldset.model.uuid))}</li>
<li>${h.link_to("Delete this Batch", url('batch.delete', uuid=form.fieldset.model.uuid))}</li>
</%def>
${parent.body()}
<% batch = form.fieldset.model %>
<h2>Columns</h2>
<div class="grid full hoverable">
<table>
<thead>
<tr>
<th>Name</th>
<th>SIL Name</th>
<th>Display Name</th>
<th>Description</th>
<th>Data Type</th>
<th>Visible</th>
</tr>
</thead>
<tbody>
% for i, column in enumerate(batch.columns, 1):
<tr class="${'odd' if i % 2 else 'even'}">
<td>${column.name}</td>
<td>${column.sil_name}</td>
<td>${column.display_name}</td>
<td>${column.description}</td>
<td>${column.data_type}</td>
<td>${column.visible}</td>
</tr>
% endfor
</tbody>
</table>
</div>

View file

@ -0,0 +1,8 @@
<%inherit file="/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Batch", url('batch', uuid=form.fieldset.model.batch.uuid))}</li>
<li>${h.link_to("Back to Batch Rows", url('batch.rows', uuid=form.fieldset.model.batch.uuid))}</li>
</%def>
${parent.body()}

View file

@ -0,0 +1,46 @@
<%inherit file="/grid.mako" />
<%def name="title()">Batch Rows : ${batch.description}</%def>
<%def name="head_tags()">
${parent.head_tags()}
<script language="javascript" type="text/javascript">
$(function() {
$('#delete-results').click(function() {
var msg = "This will delete all rows matching the current search.\n\n"
+ "PLEASE NOTE that this may include some rows which are not visible "
+ "on your screen.\n(I.e., if there is more than one \"page\" of results.)\n\n"
+ "Are you sure you wish to delete these rows?";
if (confirm(msg)) {
disable_button(this, "Deleting rows");
location.href = '${url('batch.rows.delete', uuid=batch.uuid)}';
}
});
$('#execute-batch').click(function() {
if (confirm("Are you sure you wish to execute this batch?")) {
disable_button(this, "Executing batch");
location.href = '${url('batch.execute', uuid=batch.uuid)}';
}
});
});
</script>
</%def>
<%def name="context_menu_items()">
<li>${h.link_to("Back to Batches", url('batches'))}</li>
<li>${h.link_to("Back to Batch", url('batch', uuid=batch.uuid))}</li>
</%def>
<%def name="tools()">
<div class="buttons">
<button type="button" id="delete-results">Delete Results</button>
<button type="button" id="execute-batch">Execute Batch</button>
</div>
</%def>
${parent.body()}

View file

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

View file

@ -1,10 +0,0 @@
<%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.list'))}</p>
</%def>
${parent.body()}

View file

@ -1,24 +0,0 @@
<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

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

View file

@ -1,11 +0,0 @@
<%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.list'))}</p>
<p>${h.link_to("Create a New Terminal", url('batch_terminal.new'))}</p>
</%def>
${parent.body()}

View file

@ -0,0 +1,26 @@
<%inherit file="/crud.mako" />
<%def name="head_tags()">
${parent.head_tags()}
<style type="text/css">
div.form div.field-wrapper.format textarea {
width: auto;
}
</style>
</%def>
<%def name="context_menu_items()">
<li>${h.link_to("Back to Label Profiles", url('label_profiles'))}</li>
% if form.updating:
<% profile = form.fieldset.model %>
<% printer = profile.get_printer() %>
% if printer.required_settings:
<li>${h.link_to("Edit Printer Settings", url('label_profile.printer_settings', uuid=profile.uuid))}</li>
% endif
<li>${h.link_to("View this Label Profile", url('label_profile.read', uuid=profile.uuid))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,11 @@
<%inherit file="/grid.mako" />
<%def name="title()">Label Profiles</%def>
<%def name="context_menu_items()">
% if request.has_perm('label_profiles.create'):
<li>${h.link_to("Create a new Label Profile", url('label_profile.create'))}</li>
% endif
</%def>
${parent.body()}

View file

@ -0,0 +1,48 @@
<%inherit file="/base.mako" />
<%def name="title()">Printer Settings</%def>
<%def name="context_menu_items()">
<li>${h.link_to("Back to Label Profiles", url('label_profiles'))}</li>
<li>${h.link_to("View this Label Profile", url('label_profile.read', uuid=profile.uuid))}</li>
<li>${h.link_to("Edit this Label Profile", url('label_profile.update', uuid=profile.uuid))}</li>
</%def>
<div class="form-wrapper">
<ul class="context-menu">
${self.context_menu_items()}
</ul>
<div class="form">
<div class="field-wrapper">
<label>Label Profile</label>
<div class="field">${profile.description}</div>
</div>
<div class="field-wrapper">
<label>Printer Spec</label>
<div class="field">${profile.printer_spec}</div>
</div>
${h.form(request.current_route_url())}
% for name, display in printer.required_settings.iteritems():
<div class="field-wrapper">
<label for="${name}">${display}</label>
<div class="field">
${h.text(name, value=profile.get_printer_setting(name))}
</div>
</div>
% endfor
<div class="buttons">
${h.submit('update', "Update")}
<button type="button" onclick="location.href = '${url('label_profile.read', uuid=profile.uuid)}';">Cancel</button>
</div>
${h.end_form()}
</div>
</div>

View file

@ -0,0 +1,32 @@
<%inherit file="/labels/profiles/crud.mako" />
<%def name="context_menu_items()">
<li>${h.link_to("Back to Label Profiles", url('label_profiles'))}</li>
% if form.readonly and request.has_perm('label_profiles.update'):
<% profile = form.fieldset.model %>
<% printer = profile.get_printer() %>
<li>${h.link_to("Edit this Label Profile", url('label_profile.update', uuid=form.fieldset.model.uuid))}</li>
% if printer.required_settings:
<li>${h.link_to("Edit Printer Settings", url('label_profile.printer_settings', uuid=profile.uuid))}</li>
% endif
% endif
</%def>
${parent.body()}
<% profile = form.fieldset.model %>
<% printer = profile.get_printer() %>
% if printer.required_settings:
<h2>Printer Settings</h2>
<div class="form">
% for name, display in printer.required_settings.iteritems():
<div class="field-wrapper">
<label>${display}</label>
<div class="field">${profile.get_printer_setting(name) or ''}</div>
</div>
% endfor
</div>
% endif

View file

@ -0,0 +1,27 @@
<%inherit file="/base.mako" />
<%def name="title()">Create Products Batch</%def>
<%def name="context_menu_items()">
<li>${h.link_to("Back to Products", url('products'))}</li>
</%def>
<div class="form">
${h.form(request.current_route_url())}
<div class="field-wrapper">
<label for="provider">Batch Type</label>
<div class="field">
${h.select('provider', None, providers)}
</div>
</div>
<div class="buttons">
${h.submit('create', "Create Batch")}
<button type="button" onclick="location.href = '${url('products')}';">Cancel</button>
</div>
${h.end_form()}
</div>

View file

@ -33,7 +33,7 @@
}
</style>
% if edbob.config.getboolean('rattail.labels', 'enabled', default=False):
% if label_profiles and request.has_perm('products.print_labels'):
<script language="javascript" type="text/javascript">
$(function() {
@ -45,18 +45,22 @@
$('#label-quantity').focus();
} else {
$.ajax({
url: '${url('products.print_label')}',
url: '${url('products.print_labels')}',
data: {
'uuid': get_uuid(this),
'product': get_uuid(this),
'profile': $('#label-profile').val(),
'quantity': quantity,
},
success: function(data) {
if (data.error) {
alert("An error occurred while attempting to print:\n\n" + data.error);
} else if (quantity == '1') {
alert("1 label has been printed.");
} else {
alert(quantity + " labels have been printed.");
}
},
});
if (quantity == '1') {
alert("1 label has been printed.");
} else {
alert(quantity + " labels have been printed.");
}
}
return false;
});
@ -67,7 +71,7 @@
</%def>
<%def name="tools()">
% if edbob.config.getboolean('rattail.labels', 'enabled', default=False):
% if label_profiles and request.has_perm('products.print_labels'):
<table>
<thead>
<tr>
@ -78,8 +82,8 @@
<tbody>
<td>
<select name="label-profile" id="label-profile">
% for profile in rattail.labels.iter_profiles():
<option value="${profile.name}">${profile.display_name}</option>
% for profile in label_profiles:
<option value="${profile.uuid}">${profile.description}</option>
% endfor
</select>
</td>
@ -91,4 +95,10 @@
% endif
</%def>
<%def name="context_menu_items()">
% if request.has_perm('batches.create'):
<li>${h.link_to("Create Batch from Results", url('products.create_batch'))}</li>
% endif
</%def>
${parent.body()}

View file

@ -1,96 +0,0 @@
#!/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.util`` -- Utilities
"""
from pyramid import threadlocal
from webhelpers.html import tags
import edbob
from edbob.pyramid import Session
import rattail
def get_column(sil_name):
"""
Returns the :class:`rattail.SilColumn` instance with the given SIL name.
"""
q = Session.query(rattail.SilColumn)
q = q.filter(rattail.SilColumn.sil_name == sil_name)
if q.count() == 1:
return q.one()
def get_dictionary(name, flash=False):
"""
Returns the :class:`rattail.BatchDictionary` instance with the given name.
"""
q = Session.query(rattail.BatchDictionary)
q = q.filter(rattail.BatchDictionary.name == name)
if q.count() == 1:
return q.one()
if flash:
request = threadlocal.get_current_request()
dct = tags.link_to("Batch Dictionary", request.route_url('batch_dictionaries'))
request.session.flash("Hm, I couldn't find the '%s' %s." % (name, dct))
def get_terminal(key=None, default='rattail', title="Rattail"):
"""
Returns the :class:`rattail.BatchTerminal` instance with the given SIL ID.
If ``key`` is specified, it will be used to obtain the SIL ID from config.
If no key is given, or config contains no appropriate value, then
``default`` will be used as the SIL ID.
``title`` is used for a flash message, should no such terminal be found.
.. highlight:: ini
Given a ``key`` value of ``'products'``, a SIL ID of ``'rattail.locsms'``
should be configured like this::
[rattail.pyramid]
batch_terminal.products = rattail.locsms
"""
if key:
sil_id = edbob.config.get('rattail.pyramid', 'batch_terminal.%s' % key,
default=default)
assert sil_id
q = Session.query(rattail.BatchTerminal)
q = q.filter(rattail.BatchTerminal.sil_id == sil_id)
if q.count() == 1:
return q.one()
request = threadlocal.get_current_request()
terminal = tags.link_to("Batch Terminal", request.route_url('batch_terminals'))
request.session.flash("Hm, I couldn't find a %s for %s." % (terminal, title))

File diff suppressed because it is too large Load diff

View 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/>.
#
################################################################################
"""
``rattail.pyramid.views.batches`` -- Batch Views
"""
from rattail.pyramid.views.batches.params import *
def includeme(config):
config.include('rattail.pyramid.views.batches.core')
config.include('rattail.pyramid.views.batches.params')
config.include('rattail.pyramid.views.batches.rows')

View file

@ -0,0 +1,174 @@
#!/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.core`` -- Core Batch Views
"""
import threading
from pyramid.httpexceptions import HTTPFound
from pyramid.renderers import render_to_response
from webhelpers.html import tags
import edbob
from edbob.pyramid import Session
from edbob.pyramid.forms import EnumFieldRenderer
from edbob.pyramid.progress import SessionProgress
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView, View
import rattail
from rattail import batches
class BatchesGrid(SearchableAlchemyGridView):
mapped_class = rattail.Batch
config_prefix = 'batches'
sort = 'id'
def filter_map(self):
return self.make_filter_map(
exact=['id'],
ilike=['source', 'destination', 'description'])
def filter_config(self):
return self.make_filter_config(
filter_label_id="ID")
def sort_map(self):
return self.make_sort_map('source', 'id', 'destination', 'description')
def query(self):
q = self.make_query()
q = q.filter(rattail.Batch.executed == None)
return q
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.source,
g.id.label("ID"),
g.destination,
g.description,
g.rowcount.label("Row Count"),
],
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.clickable = True
g.click_route_name = 'batch'
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 = rattail.Batch
home_route = 'batches'
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.action_type.set(renderer=EnumFieldRenderer(rattail.BATCH_ACTION))
fs.configure(
include=[
fs.source,
fs.id.label("ID"),
fs.destination,
fs.action_type,
fs.description,
fs.rowcount.label("Row Count").readonly(),
])
return fs
def post_delete(self, batch):
batch.drop_table()
class ExecuteBatch(View):
def execute_batch(self, batch):
session = edbob.Session()
batch = session.merge(batch)
progress = SessionProgress(self.request.session, 'batch.execute')
progress.session['success_msg'] = "Batch \"%s\" has been executed." % batch.description
progress.session['success_url'] = self.request.route_url('batches')
if batch.execute(progress):
session.commit()
else:
session.rollback()
session.close()
def __call__(self):
uuid = self.request.matchdict['uuid']
batch = Session.query(rattail.Batch).get(uuid) if uuid else None
if not batch:
return HTTPFound(location=self.request.route_url('batches'))
thread = threading.Thread(target=self.execute_batch, args=(batch,))
thread.start()
kwargs = {
'key': 'batch.execute',
'cancel_url': self.request.route_url('batch.rows', uuid=batch.uuid),
'cancel_msg': "Batch execution was cancelled.",
}
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', '/batches/{uuid}')
config.add_view(BatchCrud, attr='read', route_name='batch',
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')

View 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/>.
#
################################################################################
"""
``rattail.pyramid.views.batches.params`` -- Batch Parameter Views
"""
from edbob.pyramid.views 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('rattail.pyramid.views.batches.params.labels')

View 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/>.
#
################################################################################
"""
``rattail.pyramid.views.batches.params.printlabels`` -- Print Labels Batch
"""
from edbob.pyramid import Session
import rattail
from rattail.pyramid.views.batches.params 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')

View file

@ -0,0 +1,219 @@
#!/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.rows`` -- Batch Row Views
"""
from pyramid.httpexceptions import HTTPFound
from edbob.pyramid import Session
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView
import rattail
from rattail.pyramid.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.clickable = True
g.click_route_name = 'batch_row.read'
g.click_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):
grid = BatchRowsGrid(request)
return grid()
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)
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', 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')

View file

@ -0,0 +1,172 @@
#!/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.labels`` -- Label Views
"""
from pyramid.httpexceptions import HTTPFound
import formalchemy
from webhelpers.html import HTML
from edbob.pyramid import Session
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView
import rattail
class ProfilesGrid(SearchableAlchemyGridView):
mapped_class = rattail.LabelProfile
config_prefix = 'label_profiles'
sort = 'ordinal'
def filter_map(self):
return self.make_filter_map(
exact=['code'],
ilike=['description'])
def sort_map(self):
return self.make_sort_map('ordinal', 'code', 'description')
def grid(self):
g = self.make_grid()
g.configure(
include=[
g.ordinal,
g.code,
g.description,
],
readonly=True)
if self.request.has_perm('label_profiles.read'):
g.clickable = True
g.click_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 = rattail.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.format.set(renderer=FormatFieldRenderer)
fs.configure(
include=[
fs.ordinal,
fs.code,
fs.description,
fs.printer_spec,
fs.formatter_spec,
fs.format,
])
return fs
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(rattail.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')

View file

@ -26,17 +26,27 @@
``rattail.pyramid.views.products`` -- Product Views
"""
import threading
from sqlalchemy import and_
from sqlalchemy.orm import joinedload
from webhelpers.html.tags import link_to
from sqlalchemy.orm import joinedload
from pyramid.httpexceptions import HTTPFound
from pyramid.renderers import render_to_response
import edbob
from edbob.pyramid import Session
from edbob.pyramid.progress import SessionProgress
from edbob.pyramid.views import SearchableAlchemyGridView, CrudView
import rattail
import rattail.labels
from rattail.pyramid.forms import UpcFieldRenderer, PriceFieldRenderer
from rattail import sil
from rattail import batches
from rattail.exceptions import LabelPrintingError
from rattail.pyramid.forms import GPCFieldRenderer, PriceFieldRenderer
class ProductsGrid(SearchableAlchemyGridView):
@ -47,6 +57,17 @@ class ProductsGrid(SearchableAlchemyGridView):
clickable = True
def join_map(self):
def join_vendor(q):
q = q.outerjoin(
rattail.ProductCost,
and_(
rattail.ProductCost.product_uuid == rattail.Product.uuid,
rattail.ProductCost.preference == 1,
))
q = q.outerjoin(rattail.Vendor)
return q
return {
'brand':
lambda q: q.outerjoin(rattail.Brand),
@ -62,6 +83,8 @@ class ProductsGrid(SearchableAlchemyGridView):
'current_price':
lambda q: q.outerjoin(rattail.ProductPrice,
rattail.ProductPrice.uuid == rattail.Product.current_price_uuid),
'vendor':
join_vendor,
}
def filter_map(self):
@ -70,7 +93,8 @@ class ProductsGrid(SearchableAlchemyGridView):
ilike=['description', 'size'],
brand=self.filter_ilike(rattail.Brand.name),
department=self.filter_ilike(rattail.Department.name),
subdepartment=self.filter_ilike(rattail.Subdepartment.name))
subdepartment=self.filter_ilike(rattail.Subdepartment.name),
vendor=self.filter_ilike(rattail.Vendor.name))
def filter_config(self):
return self.make_filter_config(
@ -82,7 +106,9 @@ class ProductsGrid(SearchableAlchemyGridView):
include_filter_description=True,
filter_type_description='lk',
include_filter_department=True,
filter_type_department='lk')
filter_type_department='lk',
include_filter_vendor=True,
filter_type_vendor='lk')
def sort_map(self):
return self.make_sort_map(
@ -91,7 +117,8 @@ class ProductsGrid(SearchableAlchemyGridView):
department=self.sorter(rattail.Department.name),
subdepartment=self.sorter(rattail.Subdepartment.name),
regular_price=self.sorter(rattail.ProductPrice.price),
current_price=self.sorter(rattail.ProductPrice.price))
current_price=self.sorter(rattail.ProductPrice.price),
vendor=self.sorter(rattail.Vendor.name))
def query(self):
q = self.make_query()
@ -100,11 +127,12 @@ class ProductsGrid(SearchableAlchemyGridView):
q = q.options(joinedload(rattail.Product.subdepartment))
q = q.options(joinedload(rattail.Product.regular_price))
q = q.options(joinedload(rattail.Product.current_price))
q = q.options(joinedload(rattail.Product.vendor))
return q
def grid(self):
g = self.make_grid()
g.upc.set(renderer=UpcFieldRenderer)
g.upc.set(renderer=GPCFieldRenderer)
g.regular_price.set(renderer=PriceFieldRenderer)
g.current_price.set(renderer=PriceFieldRenderer)
g.configure(
@ -114,6 +142,7 @@ class ProductsGrid(SearchableAlchemyGridView):
g.description,
g.size,
g.subdepartment,
g.vendor,
g.regular_price.label("Reg. Price"),
g.current_price.label("Cur. Price"),
],
@ -121,13 +150,19 @@ class ProductsGrid(SearchableAlchemyGridView):
g.click_route_name = 'product.read'
if edbob.config.getboolean('rattail.labels', 'enabled', default=False):
q = Session.query(rattail.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(rattail.LabelProfile)
q = q.order_by(rattail.LabelProfile.ordinal)
return {'label_profiles': q.all()}
class ProductCrud(CrudView):
@ -136,7 +171,7 @@ class ProductCrud(CrudView):
def fieldset(self, model):
fs = self.make_fieldset(model)
fs.upc.set(renderer=UpcFieldRenderer)
fs.upc.set(renderer=GPCFieldRenderer)
fs.regular_price.set(renderer=PriceFieldRenderer)
fs.current_price.set(renderer=PriceFieldRenderer)
fs.configure(
@ -153,24 +188,90 @@ class ProductCrud(CrudView):
return fs
def print_label(request):
def print_labels(request):
profile = request.params.get('profile')
profile = rattail.labels.get_profile(profile) if profile else None
assert profile
profile = Session.query(rattail.LabelProfile).get(profile) if profile else None
if not profile:
return {'error': "Label profile not found"}
uuid = request.params.get('uuid')
product = Session.query(rattail.Product).get(uuid) if uuid else None
assert product
product = request.params.get('product')
product = Session.query(rattail.Product).get(product) if product else None
if not product:
return {'error': "Product not found"}
quantity = request.params.get('quantity')
assert quantity.isdigit()
if not quantity.isdigit():
return {'error': "Quantity must be numeric"}
quantity = int(quantity)
printer = profile.get_printer()
printer.print_labels([(product, quantity)])
if not printer:
return {'error': "Couldn't get printer from label profile"}
try:
printer.print_labels([(product, quantity)])
except LabelPrintingError, error:
return {'error': str(error)}
return {}
class CreateProductsBatch(ProductsGrid):
def make_batch(self, provider):
session = edbob.Session()
self._filter_config = self.filter_config()
self._sort_config = self.sort_config()
products = self.make_query(session)
progress = SessionProgress(self.request.session, 'products.batch')
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['success_url'] = self.request.route_url('batch', 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)
thread = threading.Thread(target=self.make_batch, args=(provider,))
thread.start()
kwargs = {
'key': 'products.batch',
'cancel_url': self.request.route_url('products'),
'cancel_msg': "Batch creation was cancelled.",
}
return render_to_response('/progress.mako', kwargs, request=self.request)
providers = [(x.name, x.description) for x in batches.iter_providers()]
return {'providers': providers}
def includeme(config):
config.add_route('products', '/products')
@ -178,158 +279,16 @@ def includeme(config):
renderer='/products/index.mako',
permission='products.list')
config.add_route('products.print_label', '/products/label')
config.add_view(print_label, route_name='products.print_label',
renderer='json')
config.add_route('products.print_labels', '/products/labels')
config.add_view(print_labels, route_name='products.print_labels',
renderer='json', permission='products.print_labels')
config.add_route('products.create_batch', '/products/batch')
config.add_view(CreateProductsBatch, route_name='products.create_batch',
renderer='/products/batch.mako',
permission='batches.create')
config.add_route('product.read', '/products/{uuid}')
config.add_view(ProductCrud, attr='read', route_name='product.read',
renderer='/products/read.mako',
permission='products.read')
# from sqlalchemy.orm import joinedload
# import transaction
# from pyramid.httpexceptions import HTTPFound
# 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.batches import next_batch_id
# from rattail.pyramid import util
# from rattail.pyramid.forms import UpcFieldRenderer
# def filter_map():
# return filters.get_filter_map(
# rattail.Product,
# exact=['upc'],
# ilike=['description', 'size'],
# department=filters.filter_ilike(rattail.Department.name),
# brand=filters.filter_ilike(rattail.Brand.name))
# def search_config(request, fmap):
# return filters.get_search_config(
# 'products.list', request, fmap,
# include_filter_brand=True,
# filter_type_brand='lk',
# include_filter_description=True,
# filter_type_description='lk',
# include_filter_department=True,
# filter_type_department='lk')
# def search_form(config):
# return filters.get_search_form(
# config, upc="UPC")
# def grid_config(request, search, fmap):
# return grids.get_grid_config(
# 'products.list', request, search,
# filter_map=fmap, sort='description')
# def sort_map():
# return grids.get_sort_map(
# rattail.Product,
# ['upc', 'description', 'size'],
# department=grids.sorter(rattail.Department.name),
# brand=grids.sorter(rattail.Brand.name))
# def query(config):
# jmap = {
# 'department': lambda q: q.outerjoin(rattail.Department),
# 'brand': lambda q: q.outerjoin(rattail.Brand),
# }
# smap = sort_map()
# q = Session.query(rattail.Product)
# q = q.options(joinedload(rattail.Product.department))
# q = q.options(joinedload(rattail.Product.brand))
# q = filters.filter_query(q, config, jmap)
# q = grids.sort_query(q, config, smap, jmap)
# return q
# @view_config(route_name='products.list', renderer='/products/index.mako')
# def products(context, request):
# fmap = filter_map()
# config = search_config(request, fmap)
# search = search_form(config)
# config = grid_config(request, search, fmap)
# products = grids.get_pager(query, config)
# g = forms.AlchemyGrid(
# rattail.Product, products, config,
# gridurl=request.route_url('products.list'))
# g.configure(
# include=[
# g.upc.with_renderer(UpcFieldRenderer).label("UPC"),
# g.brand,
# g.description,
# g.size,
# g.department,
# ],
# readonly=True)
# grid = g.render(class_='clickable products')
# return grids.render_grid(request, grid, search)
# @view_config(route_name='products.batch')
# def batch(context, request):
# fmap = filter_map()
# config = search_config(request, fmap)
# search = search_form(config)
# config = grid_config(request, search, fmap)
# products = query(config)
# home = HTTPFound(location=request.route_url('products.list'))
# source = util.get_terminal('rattail')
# if not source:
# return home
# dct = util.get_dictionary('ITEM_DCT')
# if not dct:
# return home
# with transaction.manager:
# batch = rattail.Batch()
# Session.add(batch)
# batch.source = source
# batch.source_description = source.description
# batch.batch_id = next_batch_id(source.sil_id, consume=True,
# session=Session())
# batch.name = '%s.%08u' % (source.sil_id, batch.batch_id)
# batch.dictionary = dct
# batch.action_type = rattail.BATCH_ADD
# batch.description = "products from Rattail"
# for i, col in enumerate(source.source_columns(dct), 1):
# batch.columns.append(rattail.BatchColumn(
# ordinal=i,
# sil_column=col.sil_column,
# source=source,
# targeted=True,
# ))
# batch.create_table()
# batch.add_rows(source, dct, query=products)
# batch.rowcount = products.count()
# url = request.route_url('batch.edit', uuid=batch.uuid)
# return HTTPFound(location=url)
# def includeme(config):
# config.add_route('products.list', '/products')
# config.add_route('products.batch', '/products/batch')
# config.scan(__name__)