diff --git a/docs/structure.rst b/docs/structure.rst
index b741475e..5585f71a 100644
--- a/docs/structure.rst
+++ b/docs/structure.rst
@@ -117,7 +117,6 @@ of course supply the web app layer.
│ │ │ └── foobatch/
│ │ ├── customers/
│ │ ├── menu.mako
- │ │ ├── mobile/
│ │ └── products/
│ └── views/
│ ├── __init__.py
diff --git a/tailbone/config.py b/tailbone/config.py
index 6be175ae..90799016 100644
--- a/tailbone/config.py
+++ b/tailbone/config.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -65,10 +65,5 @@ def global_help_url(config):
return config.get('tailbone', 'global_help_url')
-def legacy_mobile_enabled(config):
- return config.getbool('tailbone', 'legacy_mobile.enabled',
- default=True)
-
-
def protected_usernames(config):
return config.getlist('tailbone', 'protected_usernames')
diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py
index ebad3f74..d35b8a35 100644
--- a/tailbone/forms/core.py
+++ b/tailbone/forms/core.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -339,7 +339,7 @@ class Form(object):
auto_disable_save = True
auto_disable_cancel = True
- def __init__(self, fields=None, schema=None, request=None, mobile=False, readonly=False, readonly_fields=[],
+ def __init__(self, fields=None, schema=None, request=None, readonly=False, readonly_fields=[],
model_instance=None, model_class=None, appstruct=UNSPECIFIED, nodes={}, enums={}, labels={},
assume_local_times=False, renderers=None,
hidden={}, widgets={}, defaults={}, validators={}, required={}, helptext={}, focus_spec=None,
@@ -352,7 +352,6 @@ class Form(object):
if self.fields is None and self.schema:
self.set_fields([f.name for f in self.schema])
self.request = request
- self.mobile = mobile
self.readonly = readonly
self.readonly_fields = set(readonly_fields or [])
self.model_instance = model_instance
diff --git a/tailbone/grids/__init__.py b/tailbone/grids/__init__.py
index 0d4970c8..7db22b26 100644
--- a/tailbone/grids/__init__.py
+++ b/tailbone/grids/__init__.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2017 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -28,4 +28,3 @@ from __future__ import unicode_literals, absolute_import
from . import filters
from .core import Grid, GridAction
-from .mobile import MobileGrid
diff --git a/tailbone/grids/core.py b/tailbone/grids/core.py
index a6672270..dde02d19 100644
--- a/tailbone/grids/core.py
+++ b/tailbone/grids/core.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -67,7 +67,7 @@ class Grid(object):
Core grid class. In sore need of documentation.
"""
- def __init__(self, key, data, columns=None, width='auto', request=None, mobile=False,
+ def __init__(self, key, data, columns=None, width='auto', request=None,
model_class=None, model_title=None, model_title_plural=None,
enums={}, labels={}, assume_local_times=False, renderers={},
extra_row_class=None, linked_columns=[], url='#',
@@ -84,7 +84,6 @@ class Grid(object):
self.columns = FieldList(columns) if columns is not None else None
self.width = width
self.request = request
- self.mobile = mobile
self.model_class = model_class
if self.model_class and self.columns is None:
self.columns = self.make_columns()
@@ -341,7 +340,6 @@ class Grid(object):
def make_webhelpers_grid(self):
kwargs = dict(self._whgrid_kwargs)
kwargs['request'] = self.request
- kwargs['mobile'] = self.mobile
kwargs['url'] = self.make_url
columns = list(self.columns)
@@ -1302,17 +1300,11 @@ class CustomWebhelpersGrid(webhelpers2_grid.Grid):
"""
def __init__(self, itemlist, columns, **kwargs):
- self.mobile = kwargs.pop('mobile', False)
self.renderers = kwargs.pop('renderers', {})
self.linked_columns = kwargs.pop('linked_columns', [])
self.extra_record_class = kwargs.pop('extra_record_class', None)
super(CustomWebhelpersGrid, self).__init__(itemlist, columns, **kwargs)
- def default_header_record_format(self, headers):
- if self.mobile:
- return HTML('')
- return super(CustomWebhelpersGrid, self).default_header_record_format(headers)
-
def generate_header_link(self, column_number, column, label_text):
# display column header as simple no-op link; client-side JS takes care
@@ -1329,8 +1321,6 @@ class CustomWebhelpersGrid(webhelpers2_grid.Grid):
label_text)
def default_record_format(self, i, record, columns):
- if self.mobile:
- return columns
kwargs = {
'class_': self.get_record_class(i, record, columns),
}
@@ -1359,12 +1349,6 @@ class CustomWebhelpersGrid(webhelpers2_grid.Grid):
def default_column_format(self, column_number, i, record, column_name):
value = self.get_column_value(column_number, i, record, column_name)
- if self.mobile:
- url = self.url_generator(record, i)
- attrs = {}
- if hasattr(record, 'uuid'):
- attrs['data_uuid'] = record.uuid
- return HTML.tag('li', tags.link_to(value, url), **attrs)
if self.linked_columns and column_name in self.linked_columns and (
value is not None and value != ''):
url = self.url_generator(record, i)
diff --git a/tailbone/grids/filters.py b/tailbone/grids/filters.py
index 02ca9130..0aa5046d 100644
--- a/tailbone/grids/filters.py
+++ b/tailbone/grids/filters.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -294,18 +294,6 @@ class GridFilter(object):
return self.value_renderer.render(value=value, **kwargs)
-class MobileFilter(GridFilter):
- """
- Base class for mobile grid filters.
- """
- default_verbs = ['equal']
-
- def __init__(self, key, **kwargs):
- kwargs.setdefault('default_active', True)
- kwargs.setdefault('default_verb', 'equal')
- super(MobileFilter, self).__init__(key, **kwargs)
-
-
class AlchemyGridFilter(GridFilter):
"""
Base class for SQLAlchemy grid filters.
diff --git a/tailbone/grids/mobile.py b/tailbone/grids/mobile.py
deleted file mode 100644
index dc6a04b9..00000000
--- a/tailbone/grids/mobile.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# -*- coding: utf-8; -*-
-################################################################################
-#
-# Rattail -- Retail Software Framework
-# Copyright © 2010-2017 Lance Edgar
-#
-# This file is part of Rattail.
-#
-# Rattail is free software: you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation, either version 3 of the License, or (at your option) any later
-# version.
-#
-# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-# details.
-#
-# You should have received a copy of the GNU General Public License along with
-# Rattail. If not, see .
-#
-################################################################################
-"""
-Mobile Grids
-"""
-
-from __future__ import unicode_literals, absolute_import
-
-from pyramid.renderers import render
-
-from .core import Grid
-
-
-class MobileGrid(Grid):
- """
- Base class for all mobile grids
- """
-
- def render_filters(self, template='/mobile/grids/filters_simple.mako', **kwargs):
- context = kwargs
- context['request'] = self.request
- context['grid'] = self
- return render(template, context)
-
- def render_grid(self, template='/mobile/grids/grid.mako', **kwargs):
- context = kwargs
- context['grid'] = self
- return render(template, context)
-
- def render_complete(self, template='/mobile/grids/complete.mako', **kwargs):
- context = kwargs
- context['grid'] = self
- return render(template, context)
diff --git a/tailbone/static/css/mobile.css b/tailbone/static/css/mobile.css
deleted file mode 100644
index 9ebfbc8b..00000000
--- a/tailbone/static/css/mobile.css
+++ /dev/null
@@ -1,57 +0,0 @@
-
-/****************************************
- * Global styles for mobile templates
- ****************************************/
-
-/* main user menu button when root */
-[data-role="header"] a.root-user,
-[data-role="header"] a.root-user:hover {
- background-color: red;
-}
-
-/* become/stop root menu links */
-#usermenu .root-user a {
- background-color: red;
-}
-
-/* normal flash messages */
-.flash {
- color: green;
- margin-bottom: 1em;
-}
-
-/* error flash messages */
-.error,
-.error-messages {
- color: red;
- margin-bottom: 1em;
-}
-
-/* receiving warning flash messages */
-.receiving-warning {
- color: red;
-}
-
-.replacement-header {
- display: none;
-}
-
-.field-wrapper.with-error {
- background-color: #ddcccc;
- border: 2px solid #dd6666;
- margin-bottom: 1em;
-}
-
-.field-wrapper label {
- font-weight: bold;
- margin-top: 1em;
-}
-
-.field-error .error-msg {
- color: Red;
-}
-
-/* make sure space comes between simple filter and "grid" list */
-.simple-filter {
- margin-bottom: 1.5em;
-}
diff --git a/tailbone/static/js/jquery.ui.tailbone.mobile.js b/tailbone/static/js/jquery.ui.tailbone.mobile.js
deleted file mode 100644
index 79eecb9a..00000000
--- a/tailbone/static/js/jquery.ui.tailbone.mobile.js
+++ /dev/null
@@ -1,81 +0,0 @@
-
-/******************************************
- * jQuery Mobile plugins for Tailbone
- *****************************************/
-
-/******************************************
- * mobile autocomplete
- *****************************************/
-
-(function($) {
-
- $.widget('tailbone.mobileautocomplete', {
-
- _create: function() {
- var that = this;
-
- // snag some element references
- this.search = this.element.find('.ui-input-search');
- this.hidden_field = this.element.find('input[type="hidden"]');
- this.text_field = this.element.find('input[type="text"]');
- this.ul = this.element.find('ul');
- this.button = this.element.find('button');
-
- // establish our autocomplete URL
- this.url = this.options.url || this.element.data('url');
-
- // NOTE: much of this code was copied from the jquery mobile demo site
- // https://demos.jquerymobile.com/1.4.5/listview-autocomplete-remote/
- this.ul.on('filterablebeforefilter', function(e, data) {
-
- var $input = $( data.input ),
- value = $input.val(),
- html = "";
- that.ul.html( "" );
- if ( value && value.length > 2 ) {
- that.ul.html( "
" );
- that.ul.listview( "refresh" );
- $.ajax({
- url: that.url,
- data: {
- term: $input.val()
- }
- })
- .then( function ( response ) {
- $.each( response, function ( i, val ) {
- html += '' + val.label + " ";
- });
- that.ul.html( html );
- that.ul.listview( "refresh" );
- that.ul.trigger( "updatelayout");
- });
- }
-
- });
-
- // when user clicks autocomplete result, hide search etc.
- this.ul.on('click', 'li', function() {
- var $li = $(this);
- var uuid = $li.data('uuid');
- that.search.hide();
- that.hidden_field.val(uuid);
- that.button.text($li.text()).show();
- that.ul.hide();
- that.element.trigger('autocompleteitemselected', uuid);
- });
-
- // when user clicks "change" button, show search etc.
- this.button.click(function() {
- that.button.hide();
- that.ul.empty().show();
- that.hidden_field.val('');
- that.search.show();
- that.text_field.focus();
- that.element.trigger('autocompleteitemcleared');
- });
-
- }
-
- });
-
-})( jQuery );
diff --git a/tailbone/static/js/tailbone.mobile.js b/tailbone/static/js/tailbone.mobile.js
deleted file mode 100644
index 432f3170..00000000
--- a/tailbone/static/js/tailbone.mobile.js
+++ /dev/null
@@ -1,308 +0,0 @@
-
-/************************************************************
- *
- * tailbone.mobile.js
- *
- * Global logic for mobile app
- *
- ************************************************************/
-
-
-$(function() {
-
- // must init header/footer toolbars since ours are "external"
- $('[data-role="header"], [data-role="footer"]').toolbar({theme: 'a'});
-});
-
-
-$(document).on('pagecontainerchange', function(event, ui) {
-
- // in some cases (i.e. when no user is logged in) we may want the (external)
- // header toolbar button to change between pages. here's how we do that.
- // note however that we do this *always* even when not technically needed
- var link = $('[data-role="header"] a:first');
- var newlink = ui.toPage.find('.replacement-header a');
- link.text(newlink.text());
- link.attr('href', newlink.attr('href'));
- link.removeClass('ui-icon-home ui-icon-user');
- link.addClass(newlink.attr('class'));
-});
-
-
-$(document).on('click', '#feedback-button', function() {
-
- // prepare and display 'feedback' popup dialog
- var popup = $('.ui-page-active #feedback-popup');
- popup.find('.referrer .field').html(location.href);
- popup.find('.referrer input').val(location.href);
- popup.find('.user_name input').val('');
- popup.find('.message textarea').val('');
- popup.data('feedback-sent', false);
- popup.popup('open');
-});
-
-
-$(document).on('click', '#feedback-popup .submit', function() {
-
- // send message when 'feedback' submit button pressed
- var popup = $('.ui-page-active #feedback-popup');
- var form = popup.find('form');
- $.post(form.attr('action'), form.serializeArray(), function(data) {
- if (data.ok) {
-
- // mark "feedback sent" flag, for popupafterclose
- popup.data('feedback-sent', true);
- popup.popup('close');
- }
- });
-
-});
-
-
-$(document).on('click', '#feedback-form-buttons .cancel', function() {
-
- // close 'feedback' popup when user clicks Cancel
- var popup = $('.ui-page-active #feedback-popup');
- popup.popup('close');
-});
-
-
-$(document).on('popupafterclose', '#feedback-popup', function() {
-
- // thank the user for their feedback, after msg is sent
- if ($(this).data('feedback-sent')) {
- var popup = $('.ui-page-active #feedback-thanks');
- popup.popup('open');
- }
-});
-
-
-$(document).on('pagecreate', function() {
-
- // setup any autocomplete fields
- $('.field.autocomplete').mobileautocomplete();
-
-});
-
-
-// submit "quick row" form upon autocomplete selection
-$(document).on('autocompleteitemselected', function(event, uuid) {
- var field = $(event.target);
- if (field.hasClass('quick-row')) {
- var form = field.parents('form:first');
- form.find('[name="quick_entry"]').val(uuid);
- form.submit();
- }
-});
-
-
-/**
- * Automatically set focus to certain fields, on various pages
- * TODO: should be letting the form declare a "focus spec" instead, to avoid
- * hard-coding these field names below!
- */
-function setfocus() {
- var el = null;
- var queries = [
- '#username',
- '#new-purchasing-batch-vendor-text',
- '#new-receiving-batch-vendor-text',
- ];
- $.each(queries, function(i, query) {
- el = $(query);
- if (el.is(':visible')) {
- el.focus();
- return false;
- }
- });
-}
-
-
-$(document).on('pageshow', function() {
-
- setfocus();
-
- // if current page has form, which has declared a "focus spec", then try to
- // set focus accordingly
- var form = $('.ui-page-active form');
- if (form) {
- var spec = form.data('focus');
- if (spec) {
- var input = $(spec);
- if (input) {
- if (input.is(':visible')) {
- input.focus();
- }
- }
- }
- }
-
-});
-
-
-// handle radio button value change for "simple" grid filter
-$(document).on('change', '.simple-filter .ui-radio', function() {
- $(this).parents('form:first').submit();
-});
-
-
-// vendor validation for new purchasing batch
-$(document).on('click', 'form[name="new-purchasing-batch"] input[type="submit"]', function() {
- var $form = $(this).parents('form');
- if (! $form.find('[name="vendor"]').val()) {
- alert("Please select a vendor");
- $form.find('[name="new-purchasing-batch-vendor-text"]').focus();
- return false;
- }
-});
-
-
-// disable datasync restart button when clicked
-$(document).on('click', '#datasync-restart', function() {
- $(this).button('disable');
-});
-
-
-// TODO: this should go away in favor of quick_row approach
-// handle global keypress on product batch "row" page, for sake of scanner wedge
-var product_batch_routes = [
- 'mobile.batch.inventory.view',
-];
-$(document).on('keypress', function(event) {
- var current_route = $('.ui-page-active [role="main"]').data('route');
- for (var route of product_batch_routes) {
- if (current_route == route) {
- var upc = $('.ui-page-active #upc-search');
- if (upc.length) {
- if (upc.is(':focus')) {
- if (event.which == 13) {
- if (upc.val()) {
- $.mobile.navigate(upc.data('url') + '?upc=' + upc.val());
- }
- }
- } else {
- if (event.which >= 48 && event.which <= 57) { // numeric (qwerty)
- upc.val(upc.val() + event.key);
- // TODO: these codes are correct for 'keydown' but apparently not 'keypress' ?
- // } else if (event.which >= 96 && event.which <= 105) { // numeric (10-key)
- // upc.val(upc.val() + event.key);
- } else if (event.which == 13) {
- if (upc.val()) {
- $.mobile.navigate(upc.data('url') + '?upc=' + upc.val());
- }
- }
- return false;
- }
- }
- }
- }
-});
-
-
-// handle various keypress events for quick entry forms
-$(document).on('keypress', function(event) {
- var quick_entry = $('.ui-page-active #quick_entry');
- if (quick_entry.length) {
-
- // if user hits enter with quick row input focused, submit form
- if (quick_entry.is(':focus')) {
- if (event.which == 13) { // ENTER
- if (quick_entry.val()) {
- var form = quick_entry.parents('form:first');
- form.submit();
- return false;
- }
- }
-
- } else { // quick row input not focused
-
- // mimic keyboard wedge if we're so instructed
- if (quick_entry.data('wedge')) {
-
- if (event.which >= 48 && event.which <= 57) { // numeric (qwerty)
- if (!event.altKey && !event.ctrlKey && !event.metaKey) {
- quick_entry.val(quick_entry.val() + event.key);
- return false;
- }
-
- // TODO: these codes are correct for 'keydown' but apparently not 'keypress' ?
- // } else if (event.which >= 96 && event.which <= 105) { // numeric (10-key)
- // upc.val(upc.val() + event.key);
-
- } else if (event.which == 13) { // ENTER
- // submit form when ENTER is received via keyboard "wedge"
- if (quick_entry.val()) {
- var form = quick_entry.parents('form:first');
- form.submit();
- return false;
- }
- }
- }
- }
- }
-});
-
-
-// when numeric keypad button is clicked, update quantity accordingly
-$(document).on('click', '.quantity-keypad-thingy .keypad-button', function() {
- var keypad = $(this).parents('.quantity-keypad-thingy');
- var quantity = keypad.find('.keypad-quantity');
- var value = quantity.text();
- var key = $(this).text();
- var changed = keypad.data('changed');
- if (key == 'Del') {
- if (value.length == 1) {
- quantity.text('0');
- } else {
- quantity.text(value.substring(0, value.length - 1));
- }
- changed = true;
- } else if (key == '.') {
- if (value.indexOf('.') == -1) {
- if (changed) {
- quantity.text(value + '.');
- } else {
- quantity.text('0.');
- changed = true;
- }
- }
- } else {
- if (value == '0') {
- quantity.text(key);
- changed = true;
- } else if (changed) {
- quantity.text(value + key);
- } else {
- quantity.text(key);
- changed = true;
- }
- }
- if (changed) {
- keypad.data('changed', true);
- }
-});
-
-
-// show/hide expiration date per receiving mode selection
-$(document).on('change', 'fieldset.receiving-mode input[name="mode"]', function() {
- var mode = $(this).val();
- if (mode == 'expired') {
- $('#expiration-row').show();
- } else {
- $('#expiration-row').hide();
- }
-});
-
-
-// handle inventory save button
-$(document).on('click', '.inventory-actions button.save', function() {
- var form = $(this).parents('form:first');
- var uom = form.find('[name="keypad-uom"]:checked').val();
- var qty = form.find('.keypad-quantity').text();
- if (uom == 'CS') {
- form.find('input[name="cases"]').val(qty);
- } else { // units
- form.find('input[name="units"]').val(qty);
- }
- form.submit();
-});
diff --git a/tailbone/static/js/tailbone.mobile.receiving.js b/tailbone/static/js/tailbone.mobile.receiving.js
deleted file mode 100644
index d46740ac..00000000
--- a/tailbone/static/js/tailbone.mobile.receiving.js
+++ /dev/null
@@ -1,92 +0,0 @@
-
-/************************************************************
- *
- * tailbone.mobile.receiving.js
- *
- * Global logic for mobile receiving feature
- *
- ************************************************************/
-
-
-// toggle visibility of "Receive" type buttons based on whether vendor is set
-$(document).on('autocompleteitemselected', 'form[name="new-receiving-batch"] .vendor', function(event, uuid) {
- $('#new-receiving-types').show();
-});
-$(document).on('autocompleteitemcleared', 'form[name="new-receiving-batch"] .vendor', function(event) {
- $('#new-receiving-types').hide();
-});
-$(document).on('change', 'form[name="new-receiving-batch"] select[name="vendor"]', function(event) {
- if ($(this).val()) {
- $('#new-receiving-types').show();
- } else {
- $('#new-receiving-types').hide();
- }
-});
-
-
-// submit new receiving batch form when user clicks "Receive" type button
-$(document).on('click', 'form[name="new-receiving-batch"] .start-receiving', function() {
- var form = $(this).parents('form');
- form.find('input[name="workflow"]').val($(this).data('workflow'));
- form.submit();
-});
-
-
-// submit new receiving batch form when user clicks Purchase Order option
-$(document).on('click', 'form[name="new-receiving-batch"] [data-role="listview"] a', function() {
- var form = $(this).parents('form');
- var key = $(this).parents('li').data('key');
- form.find('[name="workflow"]').val('from_po');
- form.find('.purchase-order-field').val(key);
- form.submit();
- return false;
-});
-
-
-// handle receiving action buttons
-$(document).on('click', 'form.receiving-update .receiving-actions button', function() {
- var action = $(this).data('action');
- var form = $(this).parents('form:first');
- var uom = form.find('[name="keypad-uom"]:checked').val();
- var mode = form.find('[name="mode"]:checked').val();
- var qty = form.find('.keypad-quantity').text();
- if (action == 'add' || action == 'subtract') {
- if (qty != '0') {
- if (action == 'subtract') {
- qty = '-' + qty;
- }
-
- if (uom == 'CS') {
- form.find('[name="cases"]').val(qty);
- } else { // units
- form.find('[name="units"]').val(qty);
- }
-
- if (action == 'add' && mode == 'expired') {
- var expiry = form.find('input[name="expiration_date"]');
- if (! /^\d{4}-\d{2}-\d{2}$/.test(expiry.val())) {
- alert("Please enter a valid expiration date.");
- expiry.focus();
- return;
- }
- }
-
- form.submit();
- }
- }
-});
-
-
-// quick-receive (1 case or unit)
-$(document).on('click', 'form.receiving-update .quick-receive', function() {
- var form = $(this).parents('form:first');
- form.find('[name="mode"]').val('received');
- var quantity = $(this).data('quantity');
- if ($(this).data('uom') == 'CS') {
- form.find('[name="cases"]').val(quantity);
- } else {
- form.find('[name="units"]').val(quantity);
- }
- form.find('input[name="quick_receive"]').val('true');
- form.submit();
-});
diff --git a/tailbone/static/themes/bobcat/css/base.css b/tailbone/static/themes/bobcat/css/base.css
deleted file mode 100644
index 758ea304..00000000
--- a/tailbone/static/themes/bobcat/css/base.css
+++ /dev/null
@@ -1,114 +0,0 @@
-
-/* /\****************************** */
-/* * General */
-/* ******************************\/ */
-
-/* * { */
-/* margin: 0px; */
-/* } */
-
-/* body { */
-/* font-family: Verdana, Arial, sans-serif; */
-/* font-size: 11pt; */
-/* } */
-
-/* a { */
-/* color: #0972a5; */
-/* text-decoration: none; */
-/* } */
-
-/* a:hover { */
-/* text-decoration: underline; */
-/* } */
-
-/* h1 { */
-/* margin-bottom: 15px; */
-/* } */
-
-/* h2 { */
-/* font-size: 12pt; */
-/* margin: 20px auto 10px auto; */
-/* } */
-
-/* li { */
-/* line-height: 2em; */
-/* } */
-
-/* p { */
-/* margin-bottom: 5px; */
-/* } */
-
-/* .left { */
-/* float: left; */
-/* text-align: left; */
-/* } */
-
-/* .right { */
-/* text-align: right; */
-/* } */
-
-/* .wrapper { */
-/* overflow: auto; */
-/* } */
-
-/* div.buttons { */
-/* clear: both; */
-/* margin-top: 10px; */
-/* } */
-
-/* div.dialog { */
-/* display: none; */
-/* } */
-
-/* div.flash-message { */
-/* background-color: #dddddd; */
-/* margin-bottom: 8px; */
-/* padding: 3px; */
-/* } */
-
-/* div.flash-messages div.ui-state-highlight { */
-/* padding: .3em; */
-/* margin-bottom: 8px; */
-/* } */
-
-/* div.error-messages div.ui-state-error { */
-/* padding: .3em; */
-/* margin-bottom: 8px; */
-/* } */
-
-/* .flash-messages, */
-/* .error-messages { */
-/* margin: 0.5em 0 0 0; */
-/* } */
-
-/* ul.error { */
-/* color: #dd6666; */
-/* font-weight: bold; */
-/* padding: 0px; */
-/* } */
-
-/* ul.error li { */
-/* list-style-type: none; */
-/* } */
-
-/* /\****************************** */
-/* * jQuery UI tweaks */
-/* ******************************\/ */
-
-/* ul.ui-menu { */
-/* max-height: 30em; */
-/* } */
-
-/******************************
- * tweaks for root user
- ******************************/
-
-.navbar .navbar-end .navbar-link.root-user,
-.navbar .navbar-end .navbar-link.root-user:hover,
-.navbar .navbar-end .navbar-link.root-user.is_active,
-.navbar .navbar-end .navbar-item.root-user,
-.navbar .navbar-end .navbar-item.root-user:hover,
-.navbar .navbar-end .navbar-item.root-user.is_active {
- background-color: red;
- font-weight: bold;
-}
diff --git a/tailbone/static/themes/bobcat/css/forms.css b/tailbone/static/themes/bobcat/css/forms.css
deleted file mode 100644
index 3ae22da3..00000000
--- a/tailbone/static/themes/bobcat/css/forms.css
+++ /dev/null
@@ -1,141 +0,0 @@
-
-/* /\****************************** */
-/* * Form Wrapper */
-/* ******************************\/ */
-
-/* div.form-wrapper { */
-/* overflow: auto; */
-/* } */
-
-
-/******************************
- * context menu
- ******************************/
-
-/* #context-menu { */
-/* /\* background-color: #ddcccc; *\/ */
-/* /\* background-color: green; *\/ */
-/* float: right; */
-/* /\* list-style-type: none; *\/ */
-/* /\* margin: 0px; *\/ */
-/* text-align: right; */
-/* } */
-
-/* div.form-wrapper ul.context-menu li { */
-/* line-height: 2em; */
-/* } */
-
-
-/* /\****************************** */
-/* * "object helper" panel */
-/* ******************************\/ */
-
-/* .object-helper { */
-/* border: 1px solid black; */
-/* float: right; */
-/* margin-top: 1em; */
-/* padding: 1em; */
-/* width: 20em; */
-/* } */
-
-/* .object-helper-content { */
-/* margin-top: 1em; */
-/* } */
-
-
-/******************************
- * forms
- ******************************/
-
-/* div.form, */
-/* div.fieldset-form, */
-/* div.fieldset { */
-/* clear: left; */
-/* float: left; */
-/* margin-top: 10px; */
-/* } */
-
-/* TODO: replace this with bulma equivalent */
-.form {
- padding-left: 5em;
-}
-
-
-/******************************
- * fieldsets
- ******************************/
-
-/* TODO: replace this with bulma equivalent */
-.field-wrapper {
- clear: both;
- min-height: 30px;
- overflow: auto;
- margin: 15px;
-}
-
-/* .field-wrapper.with-error { */
-/* background-color: #ddcccc; */
-/* border: 2px solid #dd6666; */
-/* padding-bottom: 1em; */
-/* } */
-
-/* TODO: replace this with bulma equivalent */
-.field-wrapper .field-row {
- display: table-row;
-}
-
-/* TODO: replace this with bulma equivalent */
-.field-wrapper label {
- display: table-cell;
- vertical-align: top;
- width: 18em;
- font-weight: bold;
- padding-top: 2px;
- white-space: nowrap;
-}
-
-/* .field-wrapper.with-error label { */
-/* padding-left: 1em; */
-/* } */
-
-/* .field-wrapper .field-error { */
-/* padding: 1em 0 0.5em 1em; */
-/* } */
-
-/* .field-wrapper .field-error .error-msg { */
-/* color: #dd6666; */
-/* font-weight: bold; */
-/* } */
-
-/* TODO: replace this with bulma equivalent */
-.field-wrapper .field {
- display: table-cell;
- line-height: 25px;
-}
-
-/* .field-wrapper .field input[type=text], */
-/* .field-wrapper .field input[type=password], */
-/* .field-wrapper .field select, */
-/* .field-wrapper .field textarea { */
-/* width: 320px; */
-/* } */
-
-/* label input[type="checkbox"], */
-/* label input[type="radio"] { */
-/* margin-right: 0.5em; */
-/* } */
-
-/* .field ul { */
-/* margin: 0px; */
-/* padding-left: 15px; */
-/* } */
-
-
-/* /\****************************** */
-/* * Buttons */
-/* ******************************\/ */
-
-/* div.buttons { */
-/* clear: both; */
-/* margin: 10px 0px; */
-/* } */
diff --git a/tailbone/static/themes/bobcat/css/layout.css b/tailbone/static/themes/bobcat/css/layout.css
deleted file mode 100644
index 1c490cbe..00000000
--- a/tailbone/static/themes/bobcat/css/layout.css
+++ /dev/null
@@ -1,208 +0,0 @@
-
-/******************************
- * main layout
- ******************************/
-
-body {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
-}
-
-.content-wrapper {
- display: flex;
- flex: 1;
- flex-direction: column;
- justify-content: space-between;
-}
-
-
-/******************************
- * header
- ******************************/
-
-header .level {
- /* height: 60px; */
- line-height: 60px;
- padding-left: 0.5em;
- padding-right: 0.5em;
-}
-
-header .level #header-logo {
- display: inline-block;
-}
-
-header .level .global-title,
-header .level-left .global-title {
- font-size: 2em;
- font-weight: bold;
-}
-
-header .level #current-context,
-header .level-left #current-context {
- font-size: 2em;
- font-weight: bold;
-}
-
-header .level #current-context span,
-header .level-left #current-context span {
- margin-right: 10px;
-}
-
-header .level .theme-picker {
- display: inline-flex;
-}
-
-/* header .global .grid-nav { */
-/* display: inline-block; */
-/* font-size: 16px; */
-/* font-weight: bold; */
-/* line-height: 60px; */
-/* margin-left: 5em; */
-/* } */
-
-/* header .global .grid-nav .ui-button, */
-/* header .global .grid-nav span.viewing { */
-/* margin-left: 1em; */
-/* } */
-
-#content-title h1 {
- font-size: 2em;
-}
-
-/* /\****************************** */
-/* * Logo */
-/* ******************************\/ */
-
-/* #logo { */
-/* display: block; */
-/* margin: 40px auto; */
-/* } */
-
-
-/******************************
- * content
- ******************************/
-
-#page-body {
- padding: 0.4em;
-}
-
-/* body > #body-wrapper { */
-/* margin: 0px; */
-/* position: relative; */
-/* } */
-
-/* .content-wrapper { */
-/* height: 100%; */
-/* padding-bottom: 30px; */
-/* } */
-
-/* #scrollpane { */
-/* height: 100%; */
-/* } */
-
-/* #scrollpane .inner-content { */
-/* padding: 0 0.5em 0.5em 0.5em; */
-/* } */
-
-
-/******************************
- * context menu
- ******************************/
-
-#context-menu {
- text-align: right;
- white-space: nowrap;
-}
-
-/******************************
- * "object helper" panel
- ******************************/
-
-.object-helper {
- border: 1px solid black;
- margin: 1em;
- padding: 1em;
- min-width: 20em;
-}
-
-.object-helper-content {
- margin-top: 1em;
-}
-
-/* /\****************************** */
-/* * Panels */
-/* ******************************\/ */
-
-/* .panel-wrapper { */
-/* float: left; */
-/* margin-right: 15px; */
-/* width: 40%; */
-/* } */
-
-/* .panel, */
-/* .panel-grid { */
-/* border-left: 1px solid Black; */
-/* margin-bottom: 15px; */
-/* } */
-
-/* .panel { */
-/* border-bottom: 1px solid Black; */
-/* border-right: 1px solid Black; */
-/* padding: 0px; */
-/* } */
-
-/* .panel h2, */
-/* .panel-grid h2 { */
-/* border-bottom: 1px solid Black; */
-/* border-top: 1px solid Black; */
-/* padding: 5px; */
-/* margin: 0px; */
-/* } */
-
-/* .panel-grid h2 { */
-/* border-right: 1px solid Black; */
-/* } */
-
-/* .panel-body { */
-/* overflow: auto; */
-/* padding: 5px; */
-/* } */
-
-/******************************
- * feedback
- ******************************/
-
-#feedback-dialog {
- display: none;
-}
-
-#feedback-dialog p {
- margin-top: 1em;
-}
-
-#feedback-dialog .red {
- color: red;
- font-weight: bold;
-}
-
-#feedback-dialog .field-wrapper {
- margin-top: 1em;
- padding: 0;
-}
-
-#feedback-dialog .field {
- margin-bottom: 0;
- margin-top: 0.5em;
-}
-
-#feedback-dialog .referrer .field {
- clear: both;
- float: none;
- margin-top: 1em;
-}
-
-#feedback-dialog textarea {
- width: auto;
-}
diff --git a/tailbone/static/themes/dodo/css/admin.css b/tailbone/static/themes/dodo/css/admin.css
deleted file mode 100644
index a362b64f..00000000
--- a/tailbone/static/themes/dodo/css/admin.css
+++ /dev/null
@@ -1,84 +0,0 @@
-/* copied from https://github.com/dansup/bulma-templates/blob/master/css/admin.css */
-
-html, body {
- font-family: 'Open Sans', serif;
- font-size: 16px;
- line-height: 1.5;
- height: 100%;
- background: #ECF0F3;
-}
-nav.navbar {
- border-top: 4px solid #276cda;
- margin-bottom: 1rem;
-}
-.navbar-item.brand-text {
- font-weight: 300;
-}
-.navbar-item, .navbar-link {
- font-size: 14px;
- font-weight: 700;
-}
-.columns {
- width: 100%;
- height: 100%;
- margin-left: 0;
-}
-.menu-label {
- color: #8F99A3;
- letter-spacing: 1.3;
- font-weight: 700;
-}
-.menu-list a {
- color: #0F1D38;
- font-size: 14px;
- font-weight: 700;
-}
-.menu-list a:hover {
- background-color: transparent;
- color: #276cda;
-}
-.menu-list a.is-active {
- background-color: transparent;
- color: #276cda;
- font-weight: 700;
-}
-.card {
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.18);
- margin-bottom: 2rem;
-}
-.card-header-title {
- color: #8F99A3;
- font-weight: 400;
-}
-.info-tiles {
- margin: 1rem 0;
-}
-.info-tiles .subtitle {
- font-weight: 300;
- color: #8F99A3;
-}
-.hero.welcome.is-info {
- background: #36D1DC;
- background: -webkit-linear-gradient(to right, #5B86E5, #36D1DC);
- background: linear-gradient(to right, #5B86E5, #36D1DC);
-}
-.hero.welcome .title, .hero.welcome .subtitle {
- color: hsl(192, 17%, 99%);
-}
-.card .content {
- font-size: 14px;
-}
-.card-footer-item {
- font-size: 14px;
- font-weight: 700;
- color: #8F99A3;
-}
-.card-footer-item:hover {
-}
-.card-table .table {
- margin-bottom: 0;
-}
-.events-card .card-table {
- max-height: 250px;
- overflow-y: scroll;
-}
\ No newline at end of file
diff --git a/tailbone/static/themes/dodo/css/base.css b/tailbone/static/themes/dodo/css/base.css
deleted file mode 100644
index 27f44c9f..00000000
--- a/tailbone/static/themes/dodo/css/base.css
+++ /dev/null
@@ -1,11 +0,0 @@
-
-/******************************
- * tweaks for root user
- ******************************/
-
-.navbar .navbar-menu .navbar-link.root-user,
-.navbar .navbar-menu .navbar-item.root-user,
-.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link.root-user,
-.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link.root-user {
- background-color: red;
-}
diff --git a/tailbone/static/themes/dodo/js/bulma.js b/tailbone/static/themes/dodo/js/bulma.js
deleted file mode 100644
index a2e2dc9a..00000000
--- a/tailbone/static/themes/dodo/js/bulma.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// copied from https://github.com/dansup/bulma-templates/blob/master/js/bulma.js
-
-// The following code is based off a toggle menu by @Bradcomp
-// source: https://gist.github.com/Bradcomp/a9ef2ef322a8e8017443b626208999c1
-(function() {
- var burger = document.querySelector('.burger');
- var menu = document.querySelector('#'+burger.dataset.target);
- burger.addEventListener('click', function() {
- burger.classList.toggle('is-active');
- menu.classList.toggle('is-active');
- });
-})();
diff --git a/tailbone/static/themes/falafel/css/forms.css b/tailbone/static/themes/falafel/css/forms.css
index b5b10c74..de4b1ebe 100644
--- a/tailbone/static/themes/falafel/css/forms.css
+++ b/tailbone/static/themes/falafel/css/forms.css
@@ -26,3 +26,36 @@
.form-wrapper .form .field.is-horizontal .field-body .select select {
width: 100%;
}
+
+/******************************
+ * field-wrappers
+ ******************************/
+
+/* TODO: replace this with bulma equivalent */
+.field-wrapper {
+ clear: both;
+ min-height: 30px;
+ overflow: auto;
+ margin: 15px;
+}
+
+/* TODO: replace this with bulma equivalent */
+.field-wrapper .field-row {
+ display: table-row;
+}
+
+/* TODO: replace this with bulma equivalent */
+.field-wrapper label {
+ display: table-cell;
+ vertical-align: top;
+ width: 18em;
+ font-weight: bold;
+ padding-top: 2px;
+ white-space: nowrap;
+}
+
+/* TODO: replace this with bulma equivalent */
+.field-wrapper .field {
+ display: table-cell;
+ line-height: 25px;
+}
diff --git a/tailbone/subscribers.py b/tailbone/subscribers.py
index 3deb9c1e..69aa29e5 100644
--- a/tailbone/subscribers.py
+++ b/tailbone/subscribers.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -208,7 +208,7 @@ def context_found(event):
return False
request.has_any_perm = has_any_perm
- def get_referrer(default=None, mobile=False):
+ def get_referrer(default=None, **kwargs):
if request.params.get('referrer'):
return request.params['referrer']
if request.session.get('referrer'):
@@ -218,8 +218,6 @@ def context_found(event):
or not referrer.startswith(request.host_url)):
if default:
referrer = default
- elif mobile:
- referrer = request.route_url('mobile.home')
else:
referrer = request.route_url('home')
return referrer
diff --git a/tailbone/templates/forms/deform.mako b/tailbone/templates/forms/deform.mako
index d6c99953..ede55f12 100644
--- a/tailbone/templates/forms/deform.mako
+++ b/tailbone/templates/forms/deform.mako
@@ -89,11 +89,7 @@ ${h.csrf_token(request)}
% endif
% if getattr(form, 'show_cancel', True):
- % if form.mobile:
- ${h.link_to("Cancel", form.cancel_url, class_='ui-btn ui-corner-all')}
- % else:
- ${h.link_to("Cancel", form.cancel_url, class_='cancel button{}'.format(' autodisable' if form.auto_disable_cancel else ''))}
- % endif
+ ${h.link_to("Cancel", form.cancel_url, class_='cancel button{}'.format(' autodisable' if form.auto_disable_cancel else ''))}
% endif
% endif
diff --git a/tailbone/templates/forms/deform_buefy.mako b/tailbone/templates/forms/deform_buefy.mako
index 514d520d..71684f1d 100644
--- a/tailbone/templates/forms/deform_buefy.mako
+++ b/tailbone/templates/forms/deform_buefy.mako
@@ -65,18 +65,14 @@
% endif
% if getattr(form, 'show_cancel', True):
- % if form.mobile:
- ${h.link_to("Cancel", form.cancel_url, class_='ui-btn ui-corner-all')}
+ % if form.auto_disable_cancel:
+
+
% else:
- % if form.auto_disable_cancel:
-
-
- % else:
-
- Cancel
-
- % endif
+
+ Cancel
+
% endif
% endif
diff --git a/tailbone/templates/mobile/about.mako b/tailbone/templates/mobile/about.mako
deleted file mode 100644
index bfa55379..00000000
--- a/tailbone/templates/mobile/about.mako
+++ /dev/null
@@ -1,13 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/mobile/base.mako" />
-<%namespace name="base_meta" file="/base_meta.mako" />
-
-<%def name="title()">About ${base_meta.app_title()}%def>
-
-${project_title} ${project_version}
-
-% for name, version in packages.items():
- ${name} ${version}
-% endfor
-
-Please see rattailproject.org for more info.
diff --git a/tailbone/templates/mobile/base.mako b/tailbone/templates/mobile/base.mako
deleted file mode 100644
index c05c2100..00000000
--- a/tailbone/templates/mobile/base.mako
+++ /dev/null
@@ -1,208 +0,0 @@
-## -*- coding: utf-8 -*-
-<%namespace name="base_meta" file="/base_meta.mako" />
-
-
-
-
- ${base_meta.global_title()} » ${self.title()}
-
-
- ${self.jquery()}
- ${h.javascript_link(request.static_url('tailbone:static/js/jquery.ui.tailbone.mobile.js') + '?ver={}'.format(tailbone.__version__))}
- ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.mobile.js') + '?ver={}'.format(tailbone.__version__))}
- ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.mobile.receiving.js') + '?ver={}'.format(tailbone.__version__))}
- ${self.extra_javascript()}
-
- ## since jquery mobile will "utterly cache" the first page which is loaded
- ## by the client, we must make sure that is always the home page. so if
- ## user tries to e.g. "refresh" some other page, redirect to home page
- % if request.matched_route.name != 'mobile.home' and request.rattail_config.getbool('tailbone', 'mobile.force_home', default=True):
-
- % endif
-
- % if request.rattail_config.getbool('tailbone', 'mobile.flash.autodismiss', default=True):
-
- % endif
-
- ${self.jquery_theme()}
- ${h.stylesheet_link(request.static_url('tailbone:static/css/mobile.css') + '?ver={}'.format(tailbone.__version__))}
- % if not request.rattail_config.production():
-
- % endif
- ${self.extra_styles()}
-
-
- ${self.mobile_body()}
-
-
-<%def name="mobile_body()">
-
-
- ## note that our toolbars are *external* (in jqm-speak) by default
-
- ${self.mobile_header()}
-
-
-
- ${self.mobile_usermenu()}
-
- ${self.mobile_page_body()}
-
-
-
- ${self.mobile_footer()}
-
-
-%def>
-
-<%def name="page_url()">${request.current_route_url()}%def>
-
-<%def name="page_title()">${self.title()}%def>
-
-<%def name="jquery()">
- ${h.javascript_link('https://code.jquery.com/jquery-1.12.4.min.js')}
- ${h.javascript_link('https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js')}
-%def>
-
-<%def name="extra_javascript()">%def>
-
-<%def name="jquery_theme()">
- ${h.stylesheet_link('https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css')}
-%def>
-
-<%def name="extra_styles()">%def>
-
-<%def name="mobile_header()">
-
- ${self.mobile_header_link()}
-
${base_meta.global_title()}
- ${self.mobile_header_feedback()}
-
-%def>
-
-<%def name="mobile_header_link()">
- <% classes = 'ui-btn-left ui-btn ui-btn-inline ui-mini ui-corner-all ui-btn-icon-left ' %>
- % if request.user:
- ${h.link_to(request.user.get_short_name(), '#usermenu', data_role='button', data_icon='user',
- class_=' root-user' if request.is_root else '')}
- % elif request.matched_route.name in ('mobile.login', 'mobile.about'):
- ${h.link_to("Home", url('mobile.home'), data_role='button', data_icon='home')}
- % else:
- ${h.link_to("Login", url('mobile.login'), data_role='button', data_icon='user')}
- % endif
-%def>
-
-<%def name="mobile_header_feedback()">
- ${h.link_to("Feedback", '#', id='feedback-button', data_role='button', data_icon='recycle')}
-%def>
-
-<%def name="mobile_usermenu()">
-
-%def>
-
-<%def name="mobile_page_body()">
-
-
- % if request.session.peek_flash('error'):
- % for error in request.session.pop_flash('error'):
-
${error}
- % endfor
- % endif
-
- % if request.session.peek_flash():
- % for msg in request.session.pop_flash():
-
${msg|n}
- % endfor
- % endif
-
-
${self.page_title()}
-
- ${self.body()}
-
-
-
-
- Thank you for your feedback.
-
-
-
-
-
-%def>
-
-<%def name="mobile_footer()">
-
-
powered by ${h.link_to("Rattail", url('mobile.about'))}
-
-%def>
-
-<%def name="mobile_feedback_form()">
- ${h.form(url('mobile.feedback'))}
- ${h.csrf_token(request)}
- ${h.hidden('user', value=request.user.uuid if request.user else None)}
-
-
- Questions, suggestions, comments, complaints, etc. regarding this website
- are welcome and may be submitted below.
-
-
-
-
Referring URL
-
- ${h.hidden('referrer')}
-
-
- % if request.user:
- ${h.hidden('user_name', value=six.text_type(request.user))}
- % else:
-
-
Your Name
-
- ${h.text('user_name')}
-
-
- % endif
-
-
-
Message
-
- ${h.textarea('message', cols=45, rows=15)}
-
-
-
-
- Send Note
- Cancel
-
-
- ${h.end_form()}
-%def>
diff --git a/tailbone/templates/mobile/base_internal_toolbars.mako b/tailbone/templates/mobile/base_internal_toolbars.mako
deleted file mode 100644
index 107ca928..00000000
--- a/tailbone/templates/mobile/base_internal_toolbars.mako
+++ /dev/null
@@ -1,20 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="tailbone:templates/mobile/base.mako" />
-
-<%def name="mobile_body()">
-
-
-
-
- ${self.mobile_usermenu()}
-
- ${self.mobile_header()}
-
- ${self.mobile_page_body()}
-
- ${self.mobile_footer()}
-
-
-
-
-%def>
diff --git a/tailbone/templates/mobile/batch/execute.mako b/tailbone/templates/mobile/batch/execute.mako
deleted file mode 100644
index a6c7c6ef..00000000
--- a/tailbone/templates/mobile/batch/execute.mako
+++ /dev/null
@@ -1,10 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">${index_title} » ${instance_title} » Execute%def>
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(instance_title, instance_url)} » Execute%def>
-
-
- ${form.render()|n}
-
diff --git a/tailbone/templates/mobile/batch/inventory/create.mako b/tailbone/templates/mobile/batch/inventory/create.mako
deleted file mode 100644
index 99c8106d..00000000
--- a/tailbone/templates/mobile/batch/inventory/create.mako
+++ /dev/null
@@ -1,6 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/create.mako" />
-
-<%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » New Batch%def>
-
-${parent.body()}
diff --git a/tailbone/templates/mobile/batch/inventory/index.mako b/tailbone/templates/mobile/batch/inventory/index.mako
deleted file mode 100644
index 29038208..00000000
--- a/tailbone/templates/mobile/batch/inventory/index.mako
+++ /dev/null
@@ -1,6 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/index.mako" />
-
-<%def name="title()">Inventory%def>
-
-${parent.body()}
diff --git a/tailbone/templates/mobile/batch/inventory/view.mako b/tailbone/templates/mobile/batch/inventory/view.mako
deleted file mode 100644
index 2c8f785c..00000000
--- a/tailbone/templates/mobile/batch/inventory/view.mako
+++ /dev/null
@@ -1,24 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/batch/view.mako" />
-
-<%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » ${batch.id_str}%def>
-
-${form.render()|n}
-
-% if not batch.executed and not batch.complete:
-
- ${h.text('upc-search', class_='inventory-upc-search', placeholder="Enter UPC", autocomplete='off', **{'data-type': 'search', 'data-url': url('mobile.batch.inventory.row_from_upc', uuid=batch.uuid)})}
-% endif
-
-% if master.has_rows:
-
- ${grid.render_complete()|n}
-% endif
-
-% if not batch.executed and not batch.complete:
-
- ${h.form(request.route_url('mobile.batch.inventory.mark_complete', uuid=batch.uuid))}
- ${h.csrf_token(request)}
- ${h.hidden('mark-complete', value='true')}
- Mark Batch as Complete
-% endif
diff --git a/tailbone/templates/mobile/batch/inventory/view_row.mako b/tailbone/templates/mobile/batch/inventory/view_row.mako
deleted file mode 100644
index bfb06dcf..00000000
--- a/tailbone/templates/mobile/batch/inventory/view_row.mako
+++ /dev/null
@@ -1,63 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/batch/view_row.mako" />
-<%namespace file="/mobile/keypad.mako" import="keypad" />
-
-## TODO: this is broken for actual page (header) title
-<%def name="title()">${h.link_to("Inventory", url('mobile.batch.inventory'))} » ${h.link_to(batch.id_str, url('mobile.batch.inventory.view', uuid=batch.uuid))} » ${row.upc.pretty()}%def>
-
-
-
- % if instance.product:
-
${row.brand_name or ""}
- ${row.description} ${row.size}
- ${h.pretty_quantity(row.case_quantity)} ${unit_uom} per CS
- % else:
- ${row.description}
- % endif
-
-
- ${h.image(product_image_url, "product image")}
-
-
-
-
- currently:
- % if uom == 'CS':
- ${h.pretty_quantity(row.cases or 0)}
- % else:
- ${h.pretty_quantity(row.units or 0)}
- % endif
- ${uom}
-
-
-% if not batch.executed and not batch.complete:
-
- ${h.form(request.current_route_url())}
- ${h.csrf_token(request)}
- ${h.hidden('row', value=row.uuid)}
- % if allow_cases:
- ${h.hidden('cases')}
- % endif
- ${h.hidden('units')}
-
- <%
- quantity = 1
- if allow_cases:
- if row.cases is not None:
- quantity = row.cases
- elif row.units is not None:
- quantity = row.units
- elif row.units is not None:
- quantity = row.units
- %>
- ${keypad(unit_uom, uom, quantity=quantity, allow_cases=allow_cases)}
-
-
- Save
- Delete
- ${h.link_to("Cancel", url('mobile.batch.inventory.view', uuid=batch.uuid), class_='ui-btn ui-btn-inline ui-corner-all')}
-
-
- ${h.end_form()}
-
-% endif
diff --git a/tailbone/templates/mobile/batch/view.mako b/tailbone/templates/mobile/batch/view.mako
deleted file mode 100644
index ff0bcc38..00000000
--- a/tailbone/templates/mobile/batch/view.mako
+++ /dev/null
@@ -1,32 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/view.mako" />
-
-${parent.body()}
-
-% if not batch.executed:
- % if request.has_perm('{}.edit'.format(permission_prefix)):
- % if batch.complete:
- ${h.form(url('mobile.{}.mark_pending'.format(route_prefix), uuid=batch.uuid))}
- ${h.csrf_token(request)}
- ${h.hidden('mark-pending', value='true')}
- ${h.submit('submit', "Mark Batch as Pending")}
- ${h.end_form()}
- % else:
- ${h.form(url('mobile.{}.mark_complete'.format(route_prefix), uuid=batch.uuid))}
- ${h.csrf_token(request)}
- ${h.hidden('mark-complete', value='true')}
- ${h.submit('submit', "Mark Batch as Complete")}
- ${h.end_form()}
- % endif
- % endif
- % if batch.complete and master.mobile_executable and request.has_perm('{}.execute'.format(permission_prefix)):
- % if master.has_execution_options(batch):
- ${h.link_to("Execute Batch", url('mobile.{}.execute'.format(route_prefix), uuid=batch.uuid), class_='ui-btn ui-corner-all')}
- % else:
- ${h.form(url('mobile.{}.execute'.format(route_prefix), uuid=batch.uuid))}
- ${h.csrf_token(request)}
- ${h.submit('submit', "Execute Batch")}
- ${h.end_form()}
- % endif
- % endif
-% endif
diff --git a/tailbone/templates/mobile/batch/view_row.mako b/tailbone/templates/mobile/batch/view_row.mako
deleted file mode 100644
index ad729169..00000000
--- a/tailbone/templates/mobile/batch/view_row.mako
+++ /dev/null
@@ -1,4 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/view_row.mako" />
-
-${parent.body()}
diff --git a/tailbone/templates/mobile/datasync.mako b/tailbone/templates/mobile/datasync.mako
deleted file mode 100644
index 2f21a2a2..00000000
--- a/tailbone/templates/mobile/datasync.mako
+++ /dev/null
@@ -1,9 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">DataSync%def>
-
-${h.form(url('datasync.restart'))}
-${h.csrf_token(request)}
-${h.submit('restart', "Restart DataSync Daemon", id='datasync-restart')}
-${h.end_form()}
diff --git a/tailbone/templates/mobile/grids/complete.mako b/tailbone/templates/mobile/grids/complete.mako
deleted file mode 100644
index ebb58334..00000000
--- a/tailbone/templates/mobile/grids/complete.mako
+++ /dev/null
@@ -1,7 +0,0 @@
-## -*- coding: utf-8; -*-
-
-% if grid.filterable:
- ${grid.render_filters()|n}
-% endif
-
-${grid.render_grid()|n}
diff --git a/tailbone/templates/mobile/grids/filters_simple.mako b/tailbone/templates/mobile/grids/filters_simple.mako
deleted file mode 100644
index 1286d99a..00000000
--- a/tailbone/templates/mobile/grids/filters_simple.mako
+++ /dev/null
@@ -1,15 +0,0 @@
-## -*- coding: utf-8; -*-
-
- ${h.form(request.current_route_url(_query=None), method='get')}
-
- % for filtr in grid.iter_filters():
- ${h.hidden('{}.verb'.format(filtr.key), value=filtr.verb)}
-
- % for value, label in filtr.iter_choices():
- ${h.radio(filtr.key, value=value, label=label, checked=value == filtr.value)}
- % endfor
-
- % endfor
-
- ${h.end_form()}
-
diff --git a/tailbone/templates/mobile/grids/grid.mako b/tailbone/templates/mobile/grids/grid.mako
deleted file mode 100644
index b7b029b5..00000000
--- a/tailbone/templates/mobile/grids/grid.mako
+++ /dev/null
@@ -1,36 +0,0 @@
-## -*- coding: utf-8; -*-
-
-
- ${grid.make_webhelpers_grid()}
-
-
-##
-##
-##
-## % for column in grid.iter_visible_columns():
-## ${grid.column_header(column)}
-## % endfor
-##
-##
-##
-## % for i, row in enumerate(grid.iter_rows(), 1):
-##
-## % for column in grid.iter_visible_columns():
-## ${grid.render_cell(row, column)}
-## % endfor
-##
-## % endfor
-##
-##
-
-% if grid.pageable and grid.pager:
-
-
- ${grid.pager.pager('$link_first $link_previous $link_next $link_last',
- symbol_first='<< first', symbol_last='last >>',
- symbol_previous='< prev', symbol_next='next >',
- link_attr={'class': 'ui-btn ui-corner-all'},
- curpage_attr={'class': 'ui-btn ui-corner-all'},
- dotdot_attr={'class': 'ui-btn ui-corner-all'})|n}
-
-% endif
diff --git a/tailbone/templates/mobile/home.mako b/tailbone/templates/mobile/home.mako
deleted file mode 100644
index 1daafd86..00000000
--- a/tailbone/templates/mobile/home.mako
+++ /dev/null
@@ -1,12 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/mobile/base.mako" />
-<%namespace name="base_meta" file="/base_meta.mako" />
-
-<%def name="title()">Home%def>
-
-<%def name="page_title()">%def>
-
-
- ${h.image(image_url, "{} logo".format(capture(base_meta.app_title)), id='logo', width=300)}
-
Welcome to ${base_meta.app_title()}
-
diff --git a/tailbone/templates/mobile/keypad.mako b/tailbone/templates/mobile/keypad.mako
deleted file mode 100644
index 38cb03da..00000000
--- a/tailbone/templates/mobile/keypad.mako
+++ /dev/null
@@ -1,41 +0,0 @@
-## -*- coding: utf-8; -*-
-
-<%def name="keypad(unit_uom, selected_uom, quantity=1, allow_cases=True)">
-
-
-
-
-
- ${h.link_to("7", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to("8", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to("9", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
-
-
- ${h.link_to("4", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to("5", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to("6", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
-
-
- ${h.link_to("1", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to("2", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to("3", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
-
-
- ${h.link_to("0", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to(".", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
- ${h.link_to("Del", '#', class_='keypad-button ui-btn ui-btn-inline ui-corner-all')}
-
-
-
-
-
- ${h.pretty_quantity(1 if quantity is None else quantity)}
-
- % if allow_cases:
- ${h.radio('keypad-uom', value='CS', checked=selected_uom == 'CS', label="CS")}
- % endif
- ${h.radio('keypad-uom', value=unit_uom, checked=selected_uom == unit_uom, label=unit_uom)}
-
-
-
-%def>
diff --git a/tailbone/templates/mobile/login.mako b/tailbone/templates/mobile/login.mako
deleted file mode 100644
index 5a5efb9f..00000000
--- a/tailbone/templates/mobile/login.mako
+++ /dev/null
@@ -1,7 +0,0 @@
-## -*- coding: utf-8 -*-
-<%inherit file="/mobile/base.mako" />
-<%namespace file="/login.mako" import="login_form" />
-
-<%def name="title()">Login%def>
-
-${login_form()}
diff --git a/tailbone/templates/mobile/master/create.mako b/tailbone/templates/mobile/master/create.mako
deleted file mode 100644
index 9bcca732..00000000
--- a/tailbone/templates/mobile/master/create.mako
+++ /dev/null
@@ -1,8 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">New ${model_title}%def>
-
-
- ${form.render()|n}
-
diff --git a/tailbone/templates/mobile/master/create_row.mako b/tailbone/templates/mobile/master/create_row.mako
deleted file mode 100644
index 7b5dae0c..00000000
--- a/tailbone/templates/mobile/master/create_row.mako
+++ /dev/null
@@ -1,6 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/create.mako" />
-
-<%def name="title()">New ${model_title} Row%def>
-
-${parent.body()}
diff --git a/tailbone/templates/mobile/master/edit.mako b/tailbone/templates/mobile/master/edit.mako
deleted file mode 100644
index 3c13a8e4..00000000
--- a/tailbone/templates/mobile/master/edit.mako
+++ /dev/null
@@ -1,10 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">${index_title} » ${instance_title} » Edit%def>
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(instance_title, instance_url)} » Edit%def>
-
-
- ${form.render()|n}
-
diff --git a/tailbone/templates/mobile/master/edit_row.mako b/tailbone/templates/mobile/master/edit_row.mako
deleted file mode 100644
index 93eb12e3..00000000
--- a/tailbone/templates/mobile/master/edit_row.mako
+++ /dev/null
@@ -1,17 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/edit.mako" />
-
-<%def name="title()">${index_title} » ${parent_title} » ${instance_title} » Edit%def>
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(parent_title, parent_url)} » ${h.link_to(instance_title, instance_url)} » Edit%def>
-
-
- ${form.render()|n}
-
-
-% if master.mobile_rows_deletable and request.has_perm('{}.delete_row'.format(permission_prefix)):
- ${h.form(url('mobile.{}.delete_row'.format(route_prefix), uuid=parent_instance.uuid, row_uuid=row.uuid))}
- ${h.csrf_token(request)}
- ${h.submit('submit', "Delete this Row")}
- ${h.end_form()}
-% endif
diff --git a/tailbone/templates/mobile/master/index.mako b/tailbone/templates/mobile/master/index.mako
deleted file mode 100644
index f54ac2ae..00000000
--- a/tailbone/templates/mobile/master/index.mako
+++ /dev/null
@@ -1,17 +0,0 @@
-## -*- coding: utf-8; -*-
-## ##############################################################################
-##
-## Default master 'index' template for mobile. Features a somewhat abbreviated
-## data table and (hopefully) exposes a way to filter and sort the data, etc.
-##
-## ##############################################################################
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">${index_title}%def>
-
-% if master.mobile_creatable and request.has_perm('{}.create'.format(permission_prefix)):
- ${h.link_to("New {}".format(model_title), url('mobile.{}.create'.format(route_prefix)), class_='ui-btn ui-corner-all')}
-
-% endif
-
-${grid.render_complete()|n}
diff --git a/tailbone/templates/mobile/master/view.mako b/tailbone/templates/mobile/master/view.mako
deleted file mode 100644
index 9f00d8af..00000000
--- a/tailbone/templates/mobile/master/view.mako
+++ /dev/null
@@ -1,48 +0,0 @@
-## -*- coding: utf-8; -*-
-## ##############################################################################
-##
-## Default master 'view' template for mobile. Features a basic field list, and
-## links to edit/delete the object when appropriate.
-##
-## ##############################################################################
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">${index_title} » ${instance_title}%def>
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » ${instance_title}%def>
-
-${form.render()|n}
-
-% if master.has_rows:
-
- % if master.mobile_rows_creatable and master.rows_creatable_for(instance):
- ## TODO: this seems like a poor choice of names? what are we really testing for here?
- % if master.mobile_rows_creatable_via_browse:
- <% add_title = "Add Record" if add_item_title is Undefined else add_item_title %>
- ${h.link_to(add_title, url('mobile.{}.create_row'.format(route_prefix), uuid=instance.uuid), class_='ui-btn ui-corner-all')}
- % endif
- % endif
- % if master.mobile_rows_quickable and master.rows_quickable_for(instance):
- <% placeholder = '' if quick_entry_placeholder is Undefined else quick_entry_placeholder %>
- ${h.form(url('mobile.{}.quick_row'.format(route_prefix), uuid=instance.uuid))}
- ${h.csrf_token(request)}
- % if quick_row_autocomplete:
-
- ${h.hidden('quick_entry')}
- ${h.text('quick_row_autocomplete_text', placeholder=placeholder, autocomplete='off', data_type='search')}
-
-
Change
-
- % else:
- ${h.text('quick_entry', placeholder=placeholder, autocomplete='off', **{'data-type': 'search', 'data-url': url('mobile.{}.quick_row'.format(route_prefix), uuid=instance.uuid), 'data-wedge': 'true' if quick_row_keyboard_wedge else 'false'})}
- % endif
- ${h.end_form()}
- % endif
-
-
- ${grid.render_complete()|n}
-% endif
-
-% if master.mobile_editable and instance_editable and request.has_perm('{}.edit'.format(permission_prefix)):
- ${h.link_to("Edit This", url('mobile.{}.edit'.format(route_prefix), uuid=instance.uuid), class_='ui-btn ui-corner-all')}
-% endif
diff --git a/tailbone/templates/mobile/master/view_row.mako b/tailbone/templates/mobile/master/view_row.mako
deleted file mode 100644
index 29a014e8..00000000
--- a/tailbone/templates/mobile/master/view_row.mako
+++ /dev/null
@@ -1,19 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/view.mako" />
-
-<%def name="title()">${index_title} » ${parent_title} » ${instance_title}%def>
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(parent_title, parent_url)} » ${instance_title}%def>
-
-${form.render()|n}
-
-% if master.mobile_rows_editable and instance_editable and request.has_perm('{}.edit_row'.format(permission_prefix)):
- ${h.link_to("Edit", url('mobile.{}.edit_row'.format(route_prefix), uuid=instance.batch_uuid, row_uuid=instance.uuid), class_='ui-btn')}
-% endif
-
-% if master.mobile_rows_deletable and master.row_deletable(row) and request.has_perm('{}.delete_row'.format(permission_prefix)):
- ${h.form(url('mobile.{}.delete_row'.format(route_prefix), uuid=parent_instance.uuid, row_uuid=row.uuid))}
- ${h.csrf_token(request)}
- ${h.submit('submit', "Delete this Row")}
- ${h.end_form()}
-% endif
diff --git a/tailbone/templates/mobile/ordering/create.mako b/tailbone/templates/mobile/ordering/create.mako
deleted file mode 100644
index ae292269..00000000
--- a/tailbone/templates/mobile/ordering/create.mako
+++ /dev/null
@@ -1,31 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">${index_title} » New Batch%def>
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » New Batch%def>
-
-${h.form(request.current_route_url(), class_='ui-filterable', name='new-purchasing-batch')}
-${h.csrf_token(request)}
-
-
- % if vendor_use_autocomplete:
-
- ${h.hidden('vendor')}
- ${h.text('new-purchasing-batch-vendor-text', placeholder="Vendor name", autocomplete='off', data_type='search')}
-
-
Change Vendor
-
- % else:
-
-
Vendor
-
- ${h.select('vendor', None, vendor_options)}
-
-
- % endif
-
-
-
-${h.submit('submit', "Make Batch")}
-${h.end_form()}
diff --git a/tailbone/templates/mobile/ordering/create_row.mako b/tailbone/templates/mobile/ordering/create_row.mako
deleted file mode 100644
index 79d83630..00000000
--- a/tailbone/templates/mobile/ordering/create_row.mako
+++ /dev/null
@@ -1,6 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/create_row.mako" />
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(instance_title, instance_url)} » Add Item%def>
-
-${parent.body()}
diff --git a/tailbone/templates/mobile/ordering/new_product.mako b/tailbone/templates/mobile/ordering/new_product.mako
deleted file mode 100644
index 79d83630..00000000
--- a/tailbone/templates/mobile/ordering/new_product.mako
+++ /dev/null
@@ -1,6 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/create_row.mako" />
-
-<%def name="page_title()">${h.link_to(index_title, index_url)} » ${h.link_to(instance_title, instance_url)} » Add Item%def>
-
-${parent.body()}
diff --git a/tailbone/templates/mobile/products/index.mako b/tailbone/templates/mobile/products/index.mako
deleted file mode 100644
index 01cb8320..00000000
--- a/tailbone/templates/mobile/products/index.mako
+++ /dev/null
@@ -1,17 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/index.mako" />
-
-% if master.mobile_creatable and request.has_perm('{}.create'.format(permission_prefix)):
- ${h.link_to("New {}".format(model_title), url('mobile.{}.create'.format(route_prefix)), class_='ui-btn ui-corner-all')}
-% endif
-
-% if quick_lookup:
-
- ${h.form(url('mobile.{}.quick_lookup'.format(route_prefix)))}
- ${h.csrf_token(request)}
- ${h.text('quick_entry', placeholder=placeholder, autocomplete='off', **{'data-type': 'search', 'data-url': url('mobile.{}.quick_lookup'.format(route_prefix)), 'data-wedge': 'true' if quick_lookup_keyboard_wedge else 'false'})}
- ${h.end_form()}
-
-% else: ## not quick_only
- ${grid.render_complete()|n}
-% endif
diff --git a/tailbone/templates/mobile/receiving/create.mako b/tailbone/templates/mobile/receiving/create.mako
deleted file mode 100644
index 97cb132d..00000000
--- a/tailbone/templates/mobile/receiving/create.mako
+++ /dev/null
@@ -1,85 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/base.mako" />
-
-<%def name="title()">Receiving » New Batch%def>
-
-<%def name="page_title()">${h.link_to("Receiving", url('mobile.receiving'))} » New Batch%def>
-
-${h.form(form.action_url, class_='ui-filterable', name='new-receiving-batch')}
-${h.csrf_token(request)}
-
-% if phase == 1:
-
- % if vendor_use_autocomplete:
-
-
- ${h.hidden('vendor')}
- ${h.text('new-receiving-batch-vendor-text', placeholder="Vendor name", autocomplete='off', **{'data-type': 'search'})}
-
-
Change Vendor
-
-
- % else:
-
-
Vendor
-
- ${h.select('vendor', None, vendor_options)}
-
-
- % endif
-
-
-
-
-
- ${h.hidden('workflow')}
- ${h.hidden('phase', value='1')}
-
- % if master.allow_from_po:
- Receive from PO
- % endif
-
- % if master.allow_from_scratch:
- Receive from Scratch
- % endif
-
- % if master.allow_truck_dump:
- Receive Truck Dump
- % endif
-
-
-
-% else: ## phase 2
-
- ${h.hidden('workflow')}
- ${h.hidden('phase', value='2')}
-
-
-
Vendor
-
- ${h.hidden('vendor', value=vendor.uuid)}
- ${vendor}
-
-
-
- % if purchases:
- ${h.hidden(purchase_order_fieldname, class_='purchase-order-field')}
- Please choose a Purchase Order to receive:
-
- % for key, purchase in purchases:
- ${h.link_to(purchase, '#')}
- % endfor
-
- % else:
- (no eligible purchases found)
- % endif
-
- % if master.allow_from_scratch:
- Receive from Scratch
- % endif
-
- ${h.link_to("Cancel", url('mobile.{}'.format(route_prefix)), class_='ui-btn ui-corner-all')}
-
-% endif
-
-${h.end_form()}
diff --git a/tailbone/templates/mobile/receiving/receive_row.mako b/tailbone/templates/mobile/receiving/receive_row.mako
deleted file mode 100644
index 7987e9de..00000000
--- a/tailbone/templates/mobile/receiving/receive_row.mako
+++ /dev/null
@@ -1,151 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/view_row.mako" />
-<%namespace file="/mobile/keypad.mako" import="keypad" />
-
-<%def name="title()">Receiving » ${batch.id_str} » ${master.render_product_key_value(row)}%def>
-
-<%def name="page_title()">${h.link_to("Receiving", url('mobile.receiving'))} » ${h.link_to(batch.id_str, url('mobile.receiving.view', uuid=batch.uuid))} » ${master.render_product_key_value(row)}%def>
-
-
-
-
- % if instance.product:
-
${instance.brand_name or ""}
- ${instance.description} ${instance.size or ''}
- % if allow_cases:
- 1 CS = ${h.pretty_quantity(row.case_quantity or 1)} ${unit_uom}
- % endif
- % else:
- ${instance.description}
- % endif
-
- % if product_image_url:
-
- ${h.image(product_image_url, "product image")}
-
- % endif
-
-
-
-
- % if batch.order_quantities_known:
-
- shipped
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_shipped or 0)} /
- % endif
- ${h.pretty_quantity(row.units_shipped or 0)}
-
-
- % endif
-
- received
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_received or 0)} /
- % endif
- ${h.pretty_quantity(row.units_received or 0)}
-
-
-
- damaged
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_damaged or 0)} /
- % endif
- ${h.pretty_quantity(row.units_damaged or 0)}
-
-
- % if allow_expired:
-
- expired
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_expired or 0)} /
- % endif
- ${h.pretty_quantity(row.units_expired or 0)}
-
-
- % endif
-
-
-
-% if request.session.peek_flash('receiving-warning'):
- % for error in request.session.pop_flash('receiving-warning'):
- ${error}
- % endfor
-% endif
-
-% if not batch.executed and not batch.complete:
-
- ${h.form(request.current_route_url(), class_='receiving-update')}
- ${h.csrf_token(request)}
- ${h.hidden('row', value=row.uuid)}
- ${h.hidden('cases')}
- ${h.hidden('units')}
-
- ## only show quick-receive if we have an identifiable product
- % if quick_receive and instance.product:
- % if quick_receive_all:
- ${quick_receive_text}
- % elif allow_cases:
- Receive 1 CS
-
- ## TODO: probably should make these optional / configurable
- 1 EA
- 3 EA
- 6 EA
-
-
- % else:
- Receive 1 ${unit_uom}
- % endif
- % endif
-
- ${keypad(unit_uom, uom, allow_cases=allow_cases)}
-
-
-
- ${h.hidden('quick_receive', value='false')}
- ${h.end_form()}
-
- % if master.mobile_rows_deletable and master.row_deletable(row) and request.has_perm('{}.delete_row'.format(permission_prefix)):
- ${h.form(url('mobile.{}.delete_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid), class_='receiving-update')}
- ${h.csrf_token(request)}
- ${h.submit('submit', "Delete this Row")}
- ${h.end_form()}
- % endif
-
-% endif
diff --git a/tailbone/templates/mobile/receiving/view_row.mako b/tailbone/templates/mobile/receiving/view_row.mako
deleted file mode 100644
index 53d8820f..00000000
--- a/tailbone/templates/mobile/receiving/view_row.mako
+++ /dev/null
@@ -1,151 +0,0 @@
-## -*- coding: utf-8; -*-
-<%inherit file="/mobile/master/view_row.mako" />
-<%namespace file="/mobile/keypad.mako" import="keypad" />
-
-<%def name="title()">Receiving » ${batch.id_str} » ${master.render_product_key_value(row)}%def>
-
-<%def name="page_title()">${h.link_to("Receiving", url('mobile.receiving'))} » ${h.link_to(batch.id_str, url('mobile.receiving.view', uuid=batch.uuid))} » ${master.render_product_key_value(row)}%def>
-
-
-
-
- % if instance.product:
-
${instance.brand_name or ""}
- ${instance.description} ${instance.size or ''}
- % if allow_cases:
- 1 CS = ${h.pretty_quantity(row.case_quantity or 1)} ${unit_uom}
- % endif
- % else:
- ${instance.description}
- % endif
-
- % if product_image_url:
-
- ${h.image(product_image_url, "product image")}
-
- % endif
-
-
-
-
- % if batch.order_quantities_known:
-
- ordered
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_ordered or 0)} /
- % endif
- ${h.pretty_quantity(row.units_ordered or 0)}
-
-
- % endif
-
- received
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_received or 0)} /
- % endif
- ${h.pretty_quantity(row.units_received or 0)}
-
-
-
- damaged
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_damaged or 0)} /
- % endif
- ${h.pretty_quantity(row.units_damaged or 0)}
-
-
- % if allow_expired:
-
- expired
-
- % if allow_cases:
- ${h.pretty_quantity(row.cases_expired or 0)} /
- % endif
- ${h.pretty_quantity(row.units_expired or 0)}
-
-
- % endif
-
-
-
-% if request.session.peek_flash('receiving-warning'):
- % for error in request.session.pop_flash('receiving-warning'):
- ${error}
- % endfor
-% endif
-
-% if not batch.executed and not batch.complete:
-
- ${h.form(request.current_route_url(), class_='receiving-update')}
- ${h.csrf_token(request)}
- ${h.hidden('row', value=row.uuid)}
- ${h.hidden('cases')}
- ${h.hidden('units')}
-
- ## only show quick-receive if we have an identifiable product
- % if quick_receive and instance.product:
- % if quick_receive_all:
- ${quick_receive_text}
- % elif allow_cases:
- Receive 1 CS
-
- ## TODO: probably should make these optional / configurable
- 1 EA
- 3 EA
- 6 EA
-
-
- % else:
- Receive 1 ${unit_uom}
- % endif
- % endif
-
- ${keypad(unit_uom, uom, allow_cases=allow_cases)}
-
-
-
- ${h.hidden('quick_receive', value='false')}
- ${h.end_form()}
-
- % if master.mobile_rows_deletable and master.row_deletable(row) and request.has_perm('{}.delete_row'.format(permission_prefix)):
- ${h.form(url('mobile.{}.delete_row'.format(route_prefix), uuid=batch.uuid, row_uuid=row.uuid), class_='receiving-update')}
- ${h.csrf_token(request)}
- ${h.submit('submit', "Delete this Row")}
- ${h.end_form()}
- % endif
-
-% endif
diff --git a/tailbone/templates/themes/bobcat/base.mako b/tailbone/templates/themes/bobcat/base.mako
deleted file mode 100644
index d67b390f..00000000
--- a/tailbone/templates/themes/bobcat/base.mako
+++ /dev/null
@@ -1,311 +0,0 @@
-## -*- coding: utf-8; -*-
-<%namespace file="/grids/nav.mako" import="grid_index_nav" />
-<%namespace file="/feedback_dialog.mako" import="feedback_dialog" />
-<%namespace name="base_meta" file="/base_meta.mako" />
-
-
-
-
- ${base_meta.global_title()} » ${capture(self.title)|n}
- ${base_meta.favicon()}
- ${self.header_core()}
-
- % if background_color:
-
- % endif
-
- % if not request.rattail_config.production():
-
- % endif
-
- ${self.head_tags()}
-
-
-
-
-
-
-
-
-
-
-
-
- ## App Logo / Name
-
-
- ## Current Context
-
- % if master:
- »
- % if master.listing:
- ${index_title}
- % else:
- ${h.link_to(index_title, index_url)}
- % if parent_url is not Undefined:
- »
- ${h.link_to(parent_title, parent_url)}
- % elif instance_url is not Undefined:
- »
- ${h.link_to(instance_title, instance_url)}
- % endif
- % if master.viewing and grid_index:
- ${grid_index_nav()}
- % endif
- % endif
- % elif index_title:
- »
- ${index_title}
- % endif
-
-
-
-
-
- ## Theme Picker
- % if expose_theme_picker and request.has_perm('common.change_app_theme'):
-
- ${h.form(url('change_theme'), method="post")}
- ${h.csrf_token(request)}
- Theme:
-
-
- ${h.select('theme', theme, options=theme_picker_options, id='theme-picker')}
-
-
- ${h.end_form()}
-
- % endif
-
- ## Help Button
- % if help_url is not Undefined and help_url:
-
- ${h.link_to("Help", help_url, target='_blank', class_='button')}
-
- % endif
-
- ## Feedback Button
-
- Feedback
-
-
-
-
-
-
- ## Page Title
-
-
- % if capture(self.content_title):
-
- % if show_prev_next is not Undefined and show_prev_next:
-
- % if prev_url:
- ${h.link_to("« Older", prev_url, class_='button autodisable')}
- % else:
- ${h.link_to("« Older", '#', class_='button', disabled='disabled')}
- % endif
- % if next_url:
- ${h.link_to("Newer »", next_url, class_='button autodisable')}
- % else:
- ${h.link_to("Newer »", '#', class_='button', disabled='disabled')}
- % endif
-
- % endif
-
-
${self.content_title()}
- % endif
-
-
-
-
-
- ## Page Body
-
-
- % if request.session.peek_flash('error'):
- % for error in request.session.pop_flash('error'):
-
-
- ${error}
-
- % endfor
- % endif
-
- % if request.session.peek_flash():
- % for msg in request.session.pop_flash():
-
-
- ${msg}
-
- % endfor
- % endif
-
- ${self.body()}
-
-
- ## Feedback Dialog
- ${feedback_dialog()}
-
- ## Footer
-
-
-
-
-
-
-
-<%def name="title()">%def>
-
-<%def name="content_title()">
- ${self.title()}
-%def>
-
-<%def name="header_core()">
-
- ${self.core_javascript()}
- ${self.extra_javascript()}
- ${self.core_styles()}
- ${self.extra_styles()}
-
- ## TODO: should this be elsewhere / more customizable?
- % if dform is not Undefined:
- <% resources = dform.get_widget_resources() %>
- % for path in resources['js']:
- ${h.javascript_link(request.static_url(path))}
- % endfor
- % for path in resources['css']:
- ${h.stylesheet_link(request.static_url(path))}
- % endfor
- % endif
-%def>
-
-<%def name="core_javascript()">
- ${self.jquery()}
- ${h.javascript_link(request.static_url('tailbone:static/js/lib/jquery.loadmask.min.js'))}
- ${h.javascript_link(request.static_url('tailbone:static/js/lib/jquery.ui.timepicker.js'))}
-
- ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.js') + '?ver={}'.format(tailbone.__version__))}
- ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}
- ${h.javascript_link(request.static_url('tailbone:static/js/jquery.ui.tailbone.js') + '?ver={}'.format(tailbone.__version__))}
-%def>
-
-<%def name="jquery()">
- ${h.javascript_link('https://code.jquery.com/jquery-1.12.4.min.js')}
- ${h.javascript_link('https://code.jquery.com/ui/{}/jquery-ui.min.js'.format(request.rattail_config.get('tailbone', 'jquery_ui.version', default='1.11.4')))}
-%def>
-
-<%def name="extra_javascript()">%def>
-
-<%def name="core_styles()">
-
- ${h.stylesheet_link('https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css')}
-
- ${self.jquery_theme()}
- ${h.stylesheet_link(request.static_url('tailbone:static/css/jquery.loadmask.css'))}
- ${h.stylesheet_link(request.static_url('tailbone:static/css/jquery.ui.timepicker.css'))}
- ${h.stylesheet_link(request.static_url('tailbone:static/css/jquery.ui.tailbone.css') + '?ver={}'.format(tailbone.__version__))}
-
- ${h.stylesheet_link(request.static_url('tailbone:static/themes/bobcat/css/base.css') + '?ver={}'.format(tailbone.__version__))}
- ${h.stylesheet_link(request.static_url('tailbone:static/themes/bobcat/css/layout.css') + '?ver={}'.format(tailbone.__version__))}
- ${h.stylesheet_link(request.static_url('tailbone:static/css/grids.css') + '?ver={}'.format(tailbone.__version__))}
- ${h.stylesheet_link(request.static_url('tailbone:static/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
- ${h.stylesheet_link(request.static_url('tailbone:static/themes/bobcat/css/forms.css') + '?ver={}'.format(tailbone.__version__))}
- ${h.stylesheet_link(request.static_url('tailbone:static/css/diffs.css') + '?ver={}'.format(tailbone.__version__))}
-%def>
-
-<%def name="jquery_theme()">
- ${h.stylesheet_link('https://code.jquery.com/ui/1.11.4/themes/dark-hive/jquery-ui.css')}
-%def>
-
-<%def name="extra_styles()">%def>
-
-<%def name="head_tags()">%def>
-
-<%def name="wtfield(form, name, **kwargs)">
-
-%def>
diff --git a/tailbone/templates/themes/dodo/base.mako b/tailbone/templates/themes/dodo/base.mako
deleted file mode 100644
index a5cfa3ec..00000000
--- a/tailbone/templates/themes/dodo/base.mako
+++ /dev/null
@@ -1,231 +0,0 @@
-## -*- coding: utf-8; -*-
-## largely copied from https://github.com/dansup/bulma-templates/blob/master/templates/admin.html
-## <%namespace file="/feedback_dialog.mako" import="feedback_dialog" />
-<%namespace name="base_meta" file="/base_meta.mako" />
-
-
-
-
- ##
-
- ${base_meta.global_title()} » ${capture(self.title)|n}
-
-
-
-
-
- ${h.stylesheet_link(request.static_url('tailbone:static/themes/dodo/css/admin.css') + '?ver={}'.format(tailbone.__version__))}
-
- ${h.stylesheet_link(request.static_url('tailbone:static/themes/dodo/css/base.css') + '?ver={}'.format(tailbone.__version__))}
-
- % if background_color:
-
- % endif
-
- % if not request.rattail_config.production():
-
- % endif
-
- ${h.javascript_link('https://code.jquery.com/jquery-1.12.4.min.js')}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ## Current Context
- % if master:
- % if master.listing:
- ${index_title}
- % else:
- ${h.link_to(index_title, index_url)}
- % if parent_url is not Undefined:
- ${h.link_to(parent_title, parent_url)}
- % elif instance_url is not Undefined:
- ${h.link_to(instance_title, instance_url)}
- % endif
-## % if master.viewing and grid_index:
-## ${grid_index_nav()}
-## % endif
- % endif
- % elif index_title:
- ${index_title}
- % endif
-
- % if capture(self.content_title):
-
-## % if show_prev_next is not Undefined and show_prev_next:
-##
-## % if prev_url:
-## ${h.link_to("« Older", prev_url, class_='button autodisable')}
-## % else:
-## ${h.link_to("« Older", '#', class_='button', disabled='disabled')}
-## % endif
-## % if next_url:
-## ${h.link_to("Newer »", next_url, class_='button autodisable')}
-## % else:
-## ${h.link_to("Newer »", '#', class_='button', disabled='disabled')}
-## % endif
-##
-## % endif
-
- ${self.content_title()}
- % endif
-
-
-
-
-
- % if request.session.peek_flash('error'):
- % for error in request.session.pop_flash('error'):
-
-
- ${error}
-
- % endfor
- % endif
-
- % if request.session.peek_flash():
- % for msg in request.session.pop_flash():
-
-
- ${msg}
-
- % endfor
- % endif
-
- ${self.body()}
-
-
-
-
-
-
- ${h.javascript_link(request.static_url('tailbone:static/themes/dodo/js/bulma.js'), async='true')}
-
-
-
-
-<%def name="title()">%def>
-
-<%def name="content_title()">
- ${self.title()}
-%def>
diff --git a/tailbone/templates/themes/falafel/base.mako b/tailbone/templates/themes/falafel/base.mako
index 8bee2119..b3e19fd8 100644
--- a/tailbone/templates/themes/falafel/base.mako
+++ b/tailbone/templates/themes/falafel/base.mako
@@ -136,7 +136,6 @@
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/grids.rowstatus.css') + '?ver={}'.format(tailbone.__version__))}
## ${h.stylesheet_link(request.static_url('tailbone:static/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/filters.css') + '?ver={}'.format(tailbone.__version__))}
- ${h.stylesheet_link(request.static_url('tailbone:static/themes/bobcat/css/forms.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/themes/falafel/css/forms.css') + '?ver={}'.format(tailbone.__version__))}
${h.stylesheet_link(request.static_url('tailbone:static/css/diffs.css') + '?ver={}'.format(tailbone.__version__))}
diff --git a/tailbone/views/auth.py b/tailbone/views/auth.py
index df4ecffa..51b27f14 100644
--- a/tailbone/views/auth.py
+++ b/tailbone/views/auth.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -87,12 +87,11 @@ class AuthenticationView(View):
self.request.session.flash(msg, allow_duplicate=False)
return self.redirect(next_url)
- def login(self, mobile=False):
+ def login(self, **kwargs):
"""
The login view, responsible for displaying and handling the login form.
"""
- home = 'mobile.home' if mobile else 'home'
- referrer = self.request.get_referrer(default=self.request.route_url(home))
+ referrer = self.request.get_referrer(default=self.request.route_url('home'))
# redirect if already logged in
if self.request.user:
@@ -138,10 +137,7 @@ class AuthenticationView(View):
def authenticate_user(self, username, password):
return authenticate_user(Session(), username, password)
- def mobile_login(self):
- return self.login(mobile=True)
-
- def logout(self, mobile=False):
+ def logout(self, **kwargs):
"""
View responsible for logging out the current user.
@@ -153,17 +149,12 @@ class AuthenticationView(View):
# redirect to home page after login, if so configured
if self.rattail_config.getbool('tailbone', 'home_after_logout', default=False):
- home = 'mobile.home' if mobile else 'home'
- return self.redirect(self.request.route_url(home), headers=headers)
+ return self.redirect(self.request.route_url('home'), headers=headers)
# otherwise redirect to referrer, with 'login' page as fallback
- login = 'mobile.login' if mobile else 'login'
- referrer = self.request.get_referrer(default=self.request.route_url(login))
+ referrer = self.request.get_referrer(default=self.request.route_url('login'))
return self.redirect(referrer, headers=headers)
- def mobile_logout(self):
- return self.logout(mobile=True)
-
def noop(self):
"""
View to serve as "no-op" / ping action to reset current user's session timer
@@ -216,7 +207,6 @@ class AuthenticationView(View):
@classmethod
def defaults(cls, config):
rattail_config = config.registry.settings.get('rattail_config')
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# forbidden
config.add_forbidden_view(cls, attr='forbidden')
@@ -224,16 +214,10 @@ class AuthenticationView(View):
# login
config.add_route('login', '/login')
config.add_view(cls, attr='login', route_name='login', renderer='/login.mako')
- if legacy_mobile:
- config.add_route('mobile.login', '/mobile/login')
- config.add_view(cls, attr='mobile_login', route_name='mobile.login', renderer='/mobile/login.mako')
# logout
config.add_route('logout', '/logout')
config.add_view(cls, attr='logout', route_name='logout')
- if legacy_mobile:
- config.add_route('mobile.logout', '/mobile/logout')
- config.add_view(cls, attr='mobile_logout', route_name='mobile.logout')
# no-op
config.add_route('noop', '/noop')
diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py
index 2f3a2f25..d48a7913 100644
--- a/tailbone/views/batch/core.py
+++ b/tailbone/views/batch/core.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -83,9 +83,6 @@ class BatchMasterView(MasterView):
executable = True
results_refreshable = False
results_executable = False
- supports_mobile = True
- mobile_filterable = True
- mobile_rows_viewable = True
has_worksheet = False
has_worksheet_file = False
@@ -175,12 +172,6 @@ class BatchMasterView(MasterView):
kwargs['execute_title'] = self.get_execute_title(batch)
kwargs['execute_enabled'] = self.instance_executable(batch)
- if kwargs['mobile']:
- if self.mobile_rows_creatable:
- kwargs.setdefault('add_item_title', "Add Item")
- if self.mobile_rows_quickable:
- kwargs.setdefault('quick_entry_placeholder', "Enter {}".format(
- self.rattail_config.product_key_title()))
if kwargs['execute_enabled']:
url = self.get_action_url('execute', batch)
kwargs['execute_form'] = self.make_execute_form(batch, action_url=url)
@@ -337,18 +328,6 @@ class BatchMasterView(MasterView):
return "{} {}".format(batch.id_str, batch.description)
return batch.id_str
- def get_mobile_data(self, session=None):
- return super(BatchMasterView, self).get_mobile_data(session=session)\
- .order_by(self.model_class.id.desc())
-
- def make_mobile_filters(self):
- """
- Returns a set of filters for the mobile grid.
- """
- filters = grids.filters.GridFilterSet()
- filters['status'] = MobileBatchStatusFilter(self.model_class, 'status', default_value='pending')
- return filters
-
def configure_form(self, f):
super(BatchMasterView, self).configure_form(f)
@@ -488,28 +467,6 @@ class BatchMasterView(MasterView):
url = self.request.route_url('users.view', uuid=user.uuid)
return tags.link_to(title, url)
- def configure_mobile_form(self, f):
- super(BatchMasterView, self).configure_mobile_form(f)
- batch = f.model_instance
-
- if self.creating:
- f.remove_fields('id',
- 'rowcount',
- 'created',
- 'created_by',
- 'cognized',
- 'cognized_by',
- 'executed',
- 'executed_by',
- 'purge')
-
- else: # not creating
- if not batch.executed:
- f.remove_fields('executed',
- 'executed_by')
- if not batch.complete:
- f.remove_field('complete')
-
def save_create_form(self, form):
uploads = self.normalize_uploads(form)
self.before_create(form)
@@ -547,28 +504,7 @@ class BatchMasterView(MasterView):
os.remove(upload['temp_path'])
os.rmdir(upload['tempdir'])
- def save_mobile_create_form(self, form):
- self.before_create(form)
- session = self.Session()
- with session.no_autoflush:
-
- # transfer form data to batch instance
- batch = self.objectify(form, self.form_deserialized)
-
- # current user is batch creator
- batch.created_by = self.request.user
-
- # TODO: is this still necessary with colander?
- # destroy initial batch and re-make using handler
- kwargs = self.get_batch_kwargs(batch)
- if batch in session:
- session.expunge(batch)
- batch = self.handler.make_batch(session, **kwargs)
-
- session.flush()
- return batch
-
- def get_batch_kwargs(self, batch, mobile=False):
+ def get_batch_kwargs(self, batch, **kwargs):
"""
Return a kwargs dict for use with ``self.handler.make_batch()``, using
the given batch as a template.
@@ -599,13 +535,13 @@ class BatchMasterView(MasterView):
"""
return True
- def redirect_after_create(self, batch, mobile=False):
+ def redirect_after_create(self, batch, **kwargs):
if self.handler.should_populate(batch):
- return self.redirect(self.get_action_url('prefill', batch, mobile=mobile))
+ return self.redirect(self.get_action_url('prefill', batch))
elif self.refresh_after_create:
- return self.redirect(self.get_action_url('refresh', batch, mobile=mobile))
+ return self.redirect(self.get_action_url('refresh', batch))
else:
- return self.redirect(self.get_action_url('view', batch, mobile=mobile))
+ return self.redirect(self.get_action_url('view', batch))
def template_kwargs_edit(self, **kwargs):
batch = kwargs['instance']
@@ -631,16 +567,6 @@ class BatchMasterView(MasterView):
def mark_batch_incomplete(self, batch):
self.handler.mark_incomplete(batch)
- def mobile_mark_complete(self):
- batch = self.get_instance()
- self.mark_batch_complete(batch)
- return self.redirect(self.get_index_url(mobile=True))
-
- def mobile_mark_pending(self):
- batch = self.get_instance()
- self.mark_batch_incomplete(batch)
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
def rows_creatable_for(self, batch):
"""
Only allow creating new rows on a batch if it hasn't yet been executed
@@ -703,16 +629,6 @@ class BatchMasterView(MasterView):
return self.redirect(self.get_action_url('view', batch))
return super(BatchMasterView, self).create_row()
- def mobile_create_row(self):
- """
- Only allow creating a new row if the batch hasn't yet been executed.
- """
- batch = self.get_instance()
- if batch.executed:
- self.request.session.flash("You cannot add new rows to a batch which has been executed")
- return self.redirect(self.get_action_url('view', batch, mobile=True))
- return super(BatchMasterView, self).mobile_create_row()
-
def save_create_row_form(self, form):
batch = self.get_instance()
row = self.objectify(form, self.form_deserialized)
@@ -739,19 +655,6 @@ class BatchMasterView(MasterView):
# status text
f.set_readonly('status_text')
- def configure_mobile_row_form(self, f):
- super(BatchMasterView, self).configure_mobile_row_form(f)
-
- # sequence
- f.set_readonly('sequence')
-
- # status_code
- if self.model_row_class:
- f.set_enum('status_code', self.model_row_class.STATUS)
- f.set_renderer('status_code', self.render_row_status)
- f.set_readonly('status_code')
- f.set_label('status_code', "Status")
-
def make_default_row_grid_tools(self, batch):
if self.rows_creatable and not batch.executed and not batch.complete:
permission_prefix = self.get_permission_prefix()
@@ -803,9 +706,6 @@ class BatchMasterView(MasterView):
def make_row_grid_tools(self, batch):
return (self.make_default_row_grid_tools(batch) or '') + (self.make_batch_row_grid_tools(batch) or '')
- def sort_mobile_row_data(self, query):
- return query.order_by(self.model_row_class.sequence)
-
def redirect_after_edit(self, batch):
"""
If refresh flag is set, do that; otherwise go (back) to view/edit page.
@@ -821,12 +721,7 @@ class BatchMasterView(MasterView):
self.handler.do_delete(batch)
super(BatchMasterView, self).delete_instance(batch)
- def get_fallback_templates(self, template, mobile=False):
- if mobile:
- return [
- '/mobile/batch/{}.mako'.format(template),
- '/mobile/master/{}.mako'.format(template),
- ]
+ def get_fallback_templates(self, template, **kwargs):
return [
'/batch/{}.mako'.format(template),
'/master/{}.mako'.format(template),
@@ -1374,49 +1269,6 @@ class BatchMasterView(MasterView):
self.request.session.flash("Invalid request: {}".format(form.make_deform_form().error), 'error')
return self.redirect(self.get_action_url('view', batch))
- def mobile_execute(self):
- """
- Mobile view which can prompt user for execution options if applicable,
- and/or execute a batch. For now this is done in a "blocking" fashion,
- i.e. no progress bar.
- """
- batch = self.get_instance()
- model_title = self.get_model_title()
- instance_title = self.get_instance_title(batch)
- view_url = self.get_action_url('view', batch, mobile=True)
- self.executing = True
- form = self.make_execute_form(batch)
- if form.validate(newstyle=True):
- kwargs = dict(form.validated)
-
- # cache options to use as defaults next time
- for key, value in form.validated.items():
- self.request.session['batch.{}.execute_option.{}'.format(batch.batch_key, key)] = value
-
- try:
- result = self.handler.execute(batch, user=self.request.user, **kwargs)
- except Exception as err:
- log.exception("failed to execute %s %s", model_title, batch.id_str)
- self.request.session.flash(self.execute_error_message(err), 'error')
- else:
- if result:
- batch.executed = datetime.datetime.utcnow()
- batch.executed_by = self.request.user
- self.request.session.flash("{} was executed: {}".format(model_title, instance_title))
- else:
- log.error("not sure why, but failed to execute %s %s: %s", model_title, batch.id_str, batch)
- self.request.session.flash("Failed to execute {}: {}".format(model_title, err), 'error')
- return self.redirect(view_url)
-
- form.mobile = True
- form.submit_label = "Execute"
- form.cancel_url = view_url
- return self.render_to_response('execute', {
- 'form': form,
- 'instance_title': instance_title,
- 'instance_url': view_url,
- }, mobile=True)
-
def execute_error_message(self, error):
return "Batch execution failed: {}".format(simple_error(error))
@@ -1576,7 +1428,6 @@ class BatchMasterView(MasterView):
permission_prefix = cls.get_permission_prefix()
model_title = cls.get_model_title()
model_title_plural = cls.get_model_title_plural()
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# TODO: currently must do this here (in addition to `_defaults()` or
# else the perm group label will not display correctly...
@@ -1635,18 +1486,6 @@ class BatchMasterView(MasterView):
config.add_view(cls, attr='toggle_complete', route_name='{}.toggle_complete'.format(route_prefix),
permission='{}.edit'.format(permission_prefix))
- # mobile mark complete
- if legacy_mobile:
- config.add_route('mobile.{}.mark_complete'.format(route_prefix), '/mobile{}/{{{}}}/mark-complete'.format(url_prefix, model_key))
- config.add_view(cls, attr='mobile_mark_complete', route_name='mobile.{}.mark_complete'.format(route_prefix),
- permission='{}.edit'.format(permission_prefix))
-
- # mobile mark pending
- if legacy_mobile:
- config.add_route('mobile.{}.mark_pending'.format(route_prefix), '/mobile{}/{{{}}}/mark-pending'.format(url_prefix, model_key))
- config.add_view(cls, attr='mobile_mark_pending', route_name='mobile.{}.mark_pending'.format(route_prefix),
- permission='{}.edit'.format(permission_prefix))
-
# refresh multiple batches (results)
if cls.results_refreshable:
config.add_route('{}.refresh_results'.format(route_prefix), '{}/refresh-results'.format(url_prefix),
@@ -1709,33 +1548,3 @@ class UploadWorksheet(colander.Schema):
class ToggleComplete(colander.MappingSchema):
complete = colander.SchemaNode(colander.Boolean())
-
-
-class MobileBatchStatusFilter(grids.filters.MobileFilter):
-
- value_choices = ['pending', 'complete', 'executed', 'all']
-
- def __init__(self, model_class, key, **kwargs):
- self.model_class = model_class
- super(MobileBatchStatusFilter, self).__init__(key, **kwargs)
-
- def filter_equal(self, query, value):
-
- if value == 'pending':
- return query.filter(self.model_class.executed == None)\
- .filter(sa.or_(
- self.model_class.complete == None,
- self.model_class.complete == False))
-
- if value == 'complete':
- return query.filter(self.model_class.executed == None)\
- .filter(self.model_class.complete == True)
-
- if value == 'executed':
- return query.filter(self.model_class.executed != None)
-
- return query
-
- def iter_choices(self):
- for value in self.value_choices:
- yield value, prettify(value)
diff --git a/tailbone/views/batch/inventory.py b/tailbone/views/batch/inventory.py
index 26123707..adf91561 100644
--- a/tailbone/views/batch/inventory.py
+++ b/tailbone/views/batch/inventory.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -35,7 +35,6 @@ import six
from rattail import pod
from rattail.db import model
from rattail.db.util import make_full_description
-from rattail.time import localtime
from rattail.gpc import GPC
from rattail.util import pretty_quantity, OrderedDict
@@ -63,8 +62,6 @@ class InventoryBatchView(BatchMasterView):
index_title = "Inventory"
rows_creatable = True
bulk_deletable = True
- mobile_creatable = True
- mobile_rows_creatable = True
# set to True for the UI to "prefer" case amounts, as opposed to unit
prefer_cases = False
@@ -101,15 +98,6 @@ class InventoryBatchView(BatchMasterView):
'executed_by',
]
- mobile_form_fields = [
- 'mode',
- 'reason_code',
- 'rowcount',
- 'complete',
- 'executed',
- 'executed_by',
- ]
-
model_row_class = model.InventoryBatchRow
rows_editable = True
@@ -160,13 +148,6 @@ class InventoryBatchView(BatchMasterView):
# total_cost
g.set_type('total_cost', 'currency')
- def render_mobile_listitem(self, batch, i):
- return "({}) {} rows - {}, {}".format(
- batch.id_str,
- "?" if batch.rowcount is None else batch.rowcount,
- batch.created_by,
- localtime(self.request.rattail_config, batch.created, from_utc=True).strftime('%Y-%m-%d'))
-
def mutable_batch(self, batch):
return not batch.executed and not batch.complete and batch.mode != self.enum.INVENTORY_MODE_ZERO_ALL
@@ -397,56 +378,6 @@ class InventoryBatchView(BatchMasterView):
data['image_url'] = pod.get_image_url(self.rattail_config, product.upc)
return data
- def configure_mobile_form(self, f):
- super(InventoryBatchView, self).configure_mobile_form(f)
- batch = f.model_instance
-
- # mode
- modes = self.get_available_modes()
- f.set_enum('mode', modes)
- mode_values = [(k, v) for k, v in sorted(modes.items())]
- f.set_widget('mode', forms.widgets.PlainSelectWidget(values=mode_values))
-
- # complete
- if self.creating or batch.executed or not batch.complete:
- f.remove_field('complete')
-
- # rowcount
- if self.viewing and not batch.executed and not batch.complete:
- f.remove_field('rowcount')
-
- # TODO: this view can create new rows, with only a GET query. that should
- # probably be changed to require POST; for now we just require the "create
- # batch row" perm and call it good..
- def mobile_row_from_upc(self):
- """
- Locate and/or create a row within the batch, according to the given
- product UPC, then redirect to the row view page.
- """
- batch = self.get_instance()
- row = None
- raw_entry = self.request.GET.get('upc', '')
- entry = raw_entry.strip()
- entry = re.sub(r'\D', '', entry)
- if entry:
-
- if len(entry) <= 14:
- row = self.add_row_for_upc(batch, entry, warn_if_present=True)
- if not row:
- self.request.session.flash("Product not found: {}".format(entry), 'error')
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- else:
- self.request.session.flash("UPC has too many digits ({}): {}".format(len(entry), entry), 'error')
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- else:
- self.request.session.flash("Product not found: {}".format(raw_entry), 'error')
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- self.Session.flush()
- return self.redirect(self.mobile_row_route_url('view', uuid=row.batch_uuid, row_uuid=row.uuid))
-
def add_row_for_upc(self, batch, entry, warn_if_present=False):
"""
Add a row to the batch for the given UPC, if applicable.
@@ -467,76 +398,13 @@ class InventoryBatchView(BatchMasterView):
kwargs['product_image_url'] = pod.get_image_url(self.rattail_config, row.upc)
return kwargs
- def get_batch_kwargs(self, batch, mobile=False):
- kwargs = super(InventoryBatchView, self).get_batch_kwargs(batch, mobile=False)
+ def get_batch_kwargs(self, batch, **kwargs):
+ kwargs = super(InventoryBatchView, self).get_batch_kwargs(batch, **kwargs)
kwargs['mode'] = batch.mode
kwargs['complete'] = False
kwargs['reason_code'] = batch.reason_code
return kwargs
- def get_mobile_row_data(self, batch):
- # we want newest on top, for inventory batch rows
- return self.get_row_data(batch)\
- .order_by(self.model_row_class.sequence.desc())
-
- # TODO: ugh, the hackiness. needs a refactor fo sho
- def mobile_view_row(self):
- """
- Mobile view for inventory batch rows. Note that this also handles
- updating a row...ugh.
- """
- self.viewing = True
- row = self.get_row_instance()
- batch = self.get_parent(row)
- form = self.make_mobile_row_form(row)
-
- allow_cases = self.allow_cases(batch)
- unit_uom = 'LB' if row.product and row.product.weighed else 'EA'
- if row.cases and allow_cases:
- uom = 'CS'
- elif row.units:
- uom = unit_uom
- elif row.case_quantity and allow_cases and self.prefer_cases:
- uom = 'CS'
- else:
- uom = unit_uom
-
- context = {
- 'row': row,
- 'batch': batch,
- 'instance': row,
- 'instance_title': self.get_row_instance_title(row),
- 'parent_model_title': self.get_model_title(),
- 'parent_title': self.get_instance_title(batch),
- 'parent_url': self.get_action_url('view', batch, mobile=True),
- 'product_image_url': pod.get_image_url(self.rattail_config, row.upc),
- 'form': form,
- 'allow_cases': allow_cases,
- 'unit_uom': unit_uom,
- 'uom': uom,
- }
-
- if self.request.has_perm('{}.edit_row'.format(self.get_permission_prefix())):
- schema = InventoryForm().bind(session=self.Session())
- update_form = forms.Form(schema=schema, request=self.request)
- if update_form.validate(newstyle=True):
- row = self.Session.query(model.InventoryBatchRow).get(update_form.validated['row'])
- cases = update_form.validated['cases']
- units = update_form.validated['units']
- if cases is not colander.null:
- row.cases = cases
- row.units = None
- elif units is not colander.null:
- row.cases = None
- row.units = units
- else:
- raise NotImplementedError
- self.handler.refresh_row(row)
- route_prefix = self.get_route_prefix()
- return self.redirect(self.request.route_url('mobile.{}.view'.format(route_prefix), uuid=batch.uuid))
-
- return self.render_to_response('view_row', context, mobile=True)
-
def get_row_instance_title(self, row):
if row.upc:
return row.upc.pretty()
@@ -569,12 +437,6 @@ class InventoryBatchView(BatchMasterView):
if row.status_code == row.STATUS_PRODUCT_NOT_FOUND:
return 'warning'
- def render_mobile_row_listitem(self, row, i):
- description = row.product.full_description if row.product else row.description
- unit_uom = 'LB' if row.product and row.product.weighed else 'EA'
- qty = "{} {}".format(pretty_quantity(row.cases or row.units), 'CS' if row.cases else unit_uom)
- return "({}) {} - {}".format(row.upc.pretty(), description, qty)
-
def configure_row_form(self, f):
super(InventoryBatchView, self).configure_row_form(f)
row = f.model_instance
@@ -633,7 +495,6 @@ class InventoryBatchView(BatchMasterView):
route_prefix = cls.get_route_prefix()
url_prefix = cls.get_url_prefix()
permission_prefix = cls.get_permission_prefix()
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# we need batch handler to determine available permissions
factory = cls.get_handler_factory(rattail_config)
@@ -654,38 +515,6 @@ class InventoryBatchView(BatchMasterView):
config.add_view(cls, attr='desktop_lookup', route_name='{}.desktop_lookup'.format(route_prefix),
renderer='json', permission='{}.create_row'.format(permission_prefix))
- # mobile - make new row from UPC
- if legacy_mobile:
- config.add_route('mobile.{}.row_from_upc'.format(route_prefix), '/mobile{}/{{{}}}/row-from-upc'.format(url_prefix, model_key))
- config.add_view(cls, attr='mobile_row_from_upc', route_name='mobile.{}.row_from_upc'.format(route_prefix),
- permission='{}.create_row'.format(permission_prefix))
-
-
-# TODO: this is a stopgap measure to fix an obvious bug, which exists when the
-# session is not provided by the view at runtime (i.e. when it was instead
-# being provided by the type instance, which was created upon app startup).
-@colander.deferred
-def valid_inventory_batch_row(node, kw):
- session = kw['session']
- def validate(node, value):
- row = session.query(model.InventoryBatchRow).get(value)
- if not row:
- raise colander.Invalid(node, "Batch row not found")
- if row.batch.executed:
- raise colander.Invalid(node, "Batch has already been executed")
- return row.uuid
- return validate
-
-
-class InventoryForm(colander.MappingSchema):
-
- row = colander.SchemaNode(colander.String(),
- validator=valid_inventory_batch_row)
-
- cases = colander.SchemaNode(colander.Decimal(), missing=colander.null)
-
- units = colander.SchemaNode(colander.Decimal(), missing=colander.null)
-
# TODO: this is a stopgap measure to fix an obvious bug, which exists when the
# session is not provided by the view at runtime (i.e. when it was instead
diff --git a/tailbone/views/batch/pricing.py b/tailbone/views/batch/pricing.py
index 88063d00..6a8c56f2 100644
--- a/tailbone/views/batch/pricing.py
+++ b/tailbone/views/batch/pricing.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -171,8 +171,8 @@ class PricingBatchView(BatchMasterView):
if self.request.POST.get('auto_generate_from_srp_breach') == 'true':
f.set_required('input_filename', False)
- def get_batch_kwargs(self, batch, mobile=False):
- kwargs = super(PricingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
+ def get_batch_kwargs(self, batch, **kwargs):
+ kwargs = super(PricingBatchView, self).get_batch_kwargs(batch, **kwargs)
kwargs['min_diff_threshold'] = batch.min_diff_threshold
kwargs['min_diff_percent'] = batch.min_diff_percent
kwargs['calculate_for_manual'] = batch.calculate_for_manual
diff --git a/tailbone/views/common.py b/tailbone/views/common.py
index 05ac8e5a..37b2c4a4 100644
--- a/tailbone/views/common.py
+++ b/tailbone/views/common.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2020 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -55,11 +55,11 @@ class CommonView(View):
project_version = tailbone.__version__
robots_txt_path = resource_path('tailbone.static:robots.txt')
- def home(self, mobile=False):
+ def home(self, **kwargs):
"""
Home page view.
"""
- if not mobile and not self.request.user:
+ if not self.request.user:
if self.rattail_config.getbool('tailbone', 'login_is_home', default=True):
raise self.redirect(self.request.route_url('login'))
@@ -96,12 +96,6 @@ class CommonView(View):
response.content_type = b'text/plain'
return response
- def mobile_home(self):
- """
- Home page view for mobile.
- """
- return self.home(mobile=True)
-
def exception(self):
"""
Generic exception view
@@ -179,12 +173,6 @@ class CommonView(View):
return {'ok': True}
return {'error': "Form did not validate!"}
- def mobile_feedback(self):
- """
- Generic view to handle the user feedback form on mobile.
- """
- return self.feedback()
-
def consume_batch_id(self):
"""
Consume next batch ID from the PG sequence, and display via flash message.
@@ -207,7 +195,6 @@ class CommonView(View):
@classmethod
def _defaults(cls, config):
rattail_config = config.registry.settings.get('rattail_config')
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# auto-correct URLs which require trailing slash
config.add_notfound_view(cls, attr='notfound', append_slash=True)
@@ -222,9 +209,6 @@ class CommonView(View):
# home
config.add_route('home', '/')
config.add_view(cls, attr='home', route_name='home', renderer='/home.mako')
- if legacy_mobile:
- config.add_route('mobile.home', '/mobile/')
- config.add_view(cls, attr='mobile_home', route_name='mobile.home', renderer='/mobile/home.mako')
# robots.txt
config.add_route('robots.txt', '/robots.txt')
@@ -233,9 +217,6 @@ class CommonView(View):
# about
config.add_route('about', '/about')
config.add_view(cls, attr='about', route_name='about', renderer='/about.mako')
- if legacy_mobile:
- config.add_route('mobile.about', '/mobile/about')
- config.add_view(cls, attr='about', route_name='mobile.about', renderer='/mobile/about.mako')
# change db engine
config.add_tailbone_permission('common', 'common.change_db_engine',
@@ -255,10 +236,6 @@ class CommonView(View):
config.add_route('feedback', '/feedback', request_method='POST')
config.add_view(cls, attr='feedback', route_name='feedback',
renderer='json', permission='common.feedback')
- if legacy_mobile:
- config.add_route('mobile.feedback', '/mobile/feedback', request_method='POST')
- config.add_view(cls, attr='mobile_feedback', route_name='mobile.feedback',
- renderer='json', permission='common.feedback')
# consume batch ID
config.add_tailbone_permission('common', 'common.consume_batch_id',
diff --git a/tailbone/views/core.py b/tailbone/views/core.py
index 69f9974c..bcb5b01b 100644
--- a/tailbone/views/core.py
+++ b/tailbone/views/core.py
@@ -42,7 +42,7 @@ from tailbone.db import Session
from tailbone.auth import logout_user
from tailbone.progress import SessionProgress
from tailbone.util import should_use_buefy
-from tailbone.config import legacy_mobile_enabled, protected_usernames
+from tailbone.config import protected_usernames
class View(object):
@@ -101,14 +101,6 @@ class View(object):
"""
return should_use_buefy(self.request)
- @classmethod
- def legacy_mobile_enabled(cls, rattail_config):
- """
- Returns the boolean setting indicating whether the old / "legacy"
- (jQuery) mobile app/site should be exposed.
- """
- return legacy_mobile_enabled(rattail_config)
-
def late_login_user(self):
"""
Returns the :class:`rattail:rattail.db.model.User` instance
diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py
index 3d8e78d1..723beb5a 100644
--- a/tailbone/views/customers.py
+++ b/tailbone/views/customers.py
@@ -50,7 +50,6 @@ class CustomerView(MasterView):
model_class = model.Customer
is_contact = True
has_versions = True
- supports_mobile = True
people_detachable = True
touchable = True
@@ -95,20 +94,6 @@ class CustomerView(MasterView):
'members',
]
- mobile_form_fields = [
- 'id',
- 'name',
- 'default_phone',
- 'default_email',
- 'default_address',
- 'email_preference',
- 'wholesale',
- 'active_in_pos',
- 'active_in_pos_sticky',
- 'people',
- 'groups',
- ]
-
def configure_grid(self, g):
super(CustomerView, self).configure_grid(g)
@@ -154,10 +139,6 @@ class CustomerView(MasterView):
g.set_link('person')
g.set_link('email')
- def get_mobile_data(self, session=None):
- # TODO: hacky!
- return self.get_data(session=session).order_by(model.Customer.name)
-
def get_instance(self):
try:
instance = super(CustomerView, self).get_instance()
@@ -303,8 +284,7 @@ class CustomerView(MasterView):
items = []
for person in people:
text = six.text_type(person)
- route = '{}people.view'.format('mobile.' if self.mobile else '')
- url = self.request.route_url(route, uuid=person.uuid)
+ url = self.request.route_url('people.view', uuid=person.uuid)
link = tags.link_to(text, url)
items.append(HTML.tag('li', c=[link]))
return HTML.tag('ul', c=items)
diff --git a/tailbone/views/datasync.py b/tailbone/views/datasync.py
index d3b9b3b8..f4434447 100644
--- a/tailbone/views/datasync.py
+++ b/tailbone/views/datasync.py
@@ -89,27 +89,17 @@ class DataSyncChangeView(MasterView):
self.request.session.flash("DataSync daemon could not be restarted; result was: {}".format(result), 'error')
return self.redirect(self.request.get_referrer(default=self.request.route_url('datasyncchanges')))
- def mobile_index(self):
- return {}
-
@classmethod
def defaults(cls, config):
rattail_config = config.registry.settings.get('rattail_config')
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# fix permission group title
config.add_tailbone_permission_group('datasync', label="DataSync")
# restart datasync
config.add_tailbone_permission('datasync', 'datasync.restart', label="Restart DataSync Daemon")
- # desktop
config.add_route('datasync.restart', '/datasync/restart')
config.add_view(cls, attr='restart', route_name='datasync.restart', permission='datasync.restart')
- # mobile
- if legacy_mobile:
- config.add_route('datasync.mobile', '/mobile/datasync/')
- config.add_view(cls, attr='mobile_index', route_name='datasync.mobile',
- permission='datasync.restart', renderer='/mobile/datasync.mako')
cls._defaults(config)
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index be67eaa5..68130a2f 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -115,14 +115,6 @@ class MasterView(View):
# set to True to declare model as "contact"
is_contact = False
- supports_mobile = False
- mobile_creatable = False
- mobile_editable = False
- mobile_pageable = True
- mobile_filterable = False
- mobile_executable = False
-
- mobile = False
listing = False
creating = False
creates_multiple = False
@@ -170,14 +162,6 @@ class MasterView(View):
rows_downloadable_csv = False
rows_downloadable_xlsx = False
- mobile_rows_creatable = False
- mobile_rows_creatable_via_browse = False
- mobile_rows_quickable = False
- mobile_rows_filterable = False
- mobile_rows_viewable = False
- mobile_rows_editable = False
- mobile_rows_deletable = False
-
row_labels = {}
@property
@@ -236,24 +220,6 @@ class MasterView(View):
"""
return getattr(cls, 'version_grid_factory', grids.Grid)
- @classmethod
- def get_mobile_grid_factory(cls):
- """
- Must return a callable to be used when creating new mobile grid
- instances. Instead of overriding this, you can set
- :attr:`mobile_grid_factory`. Default factory is :class:`MobileGrid`.
- """
- return getattr(cls, 'mobile_grid_factory', grids.MobileGrid)
-
- @classmethod
- def get_mobile_row_grid_factory(cls):
- """
- Must return a callable to be used when creating new mobile row grid
- instances. Instead of overriding this, you can set
- :attr:`mobile_row_grid_factory`. Default factory is :class:`MobileGrid`.
- """
- return getattr(cls, 'mobile_row_grid_factory', grids.MobileGrid)
-
def set_labels(self, obj):
labels = self.collect_labels()
for key, label in six.iteritems(labels):
@@ -624,163 +590,6 @@ class MasterView(View):
def render_version_comment(self, transaction, column):
return transaction.meta.get('comment', "")
- def mobile_index(self):
- """
- Mobile "home" page for the data model
- """
- self.mobile = True
- self.listing = True
- grid = self.make_mobile_grid()
- return self.render_to_response('index', {'grid': grid}, mobile=True)
-
- @classmethod
- def get_mobile_grid_key(cls):
- """
- Must return a unique "config key" for the mobile grid, for sort/filter
- purposes etc. (It need only be unique among *mobile* grids.) Instead
- of overriding this, you can set :attr:`mobile_grid_key`. Default is
- the value returned by :meth:`get_route_prefix()`.
- """
- if hasattr(cls, 'mobile_grid_key'):
- return cls.mobile_grid_key
- return 'mobile.{}'.format(cls.get_route_prefix())
-
- def make_mobile_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
- """
- Creates a new mobile grid instance
- """
- if factory is None:
- factory = self.get_mobile_grid_factory()
- if key is None:
- key = self.get_mobile_grid_key()
- if data is None:
- data = self.get_mobile_data(session=kwargs.get('session'))
- if columns is None:
- columns = self.get_mobile_grid_columns()
-
- kwargs.setdefault('request', self.request)
- kwargs.setdefault('mobile', True)
- kwargs = self.make_mobile_grid_kwargs(**kwargs)
- grid = factory(key, data, columns, **kwargs)
- self.configure_mobile_grid(grid)
- grid.load_settings()
- return grid
-
- def get_mobile_grid_columns(self):
- if hasattr(self, 'mobile_grid_columns'):
- return self.mobile_grid_columns
- # TODO
- return ['listitem']
-
- def get_mobile_data(self, session=None):
- """
- Must return the "raw" / full data set for the mobile grid. This data
- should *not* yet be sorted or filtered in any way; that happens later.
- Default is the value returned by :meth:`get_data()`, in which case all
- records visible in the traditional view, are visible in mobile too.
- """
- return self.get_data(session=session)
-
- def make_mobile_grid_kwargs(self, **kwargs):
- """
- Must return a dictionary of kwargs to be passed to the factory when
- creating new mobile grid instances.
- """
- defaults = {
- 'model_class': getattr(self, 'model_class', None),
- 'pageable': self.mobile_pageable,
- 'sortable': False,
- 'filterable': self.mobile_filterable,
- 'renderers': self.make_mobile_grid_renderers(),
- 'url': lambda obj: self.get_action_url('view', obj, mobile=True),
- }
- # TODO: this seems wrong..
- if self.mobile_filterable:
- defaults['filters'] = self.make_mobile_filters()
- defaults.update(kwargs)
- return defaults
-
- def make_mobile_grid_renderers(self):
- return {
- 'listitem': self.render_mobile_listitem,
- }
-
- def render_mobile_listitem(self, obj, i):
- return obj
-
- def configure_mobile_grid(self, grid):
- pass
-
- def make_mobile_row_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
- """
- Make a new (configured) rows grid instance for mobile.
- """
- instance = kwargs.pop('instance', self.get_instance())
-
- if factory is None:
- factory = self.get_mobile_row_grid_factory()
- if key is None:
- key = 'mobile.{}.{}'.format(self.get_grid_key(), self.request.matchdict[self.get_model_key()])
- if data is None:
- data = self.get_mobile_row_data(instance)
- if columns is None:
- columns = self.get_mobile_row_grid_columns()
-
- kwargs.setdefault('request', self.request)
- kwargs.setdefault('mobile', True)
- kwargs = self.make_mobile_row_grid_kwargs(**kwargs)
- grid = factory(key, data, columns, **kwargs)
- self.configure_mobile_row_grid(grid)
- grid.load_settings()
- return grid
-
- def get_mobile_row_grid_columns(self):
- if hasattr(self, 'mobile_row_grid_columns'):
- return self.mobile_row_grid_columns
- # TODO
- return ['listitem']
-
- def make_mobile_row_grid_kwargs(self, **kwargs):
- """
- Must return a dictionary of kwargs to be passed to the factory when
- creating new mobile *row* grid instances.
- """
- defaults = {
- 'model_class': self.model_row_class,
- # TODO
- 'pageable': self.pageable,
- 'sortable': False,
- 'filterable': self.mobile_rows_filterable,
- 'renderers': self.make_mobile_row_grid_renderers(),
- 'url': lambda obj: self.get_row_action_url('view', obj, mobile=True),
- }
- # TODO: this seems wrong..
- if self.mobile_rows_filterable:
- defaults['filters'] = self.make_mobile_row_filters()
- defaults.update(kwargs)
- return defaults
-
- def make_mobile_row_grid_renderers(self):
- return {
- 'listitem': self.render_mobile_row_listitem,
- }
-
- def configure_mobile_row_grid(self, grid):
- pass
-
- def make_mobile_filters(self):
- """
- Returns a set of filters for the mobile grid, if applicable.
- """
-
- def make_mobile_row_filters(self):
- """
- Returns a set of filters for the mobile row grid, if applicable.
- """
-
- def render_mobile_row_listitem(self, obj, i):
- return obj
-
def create(self, form=None, template='create'):
"""
View for creating a new model record.
@@ -800,22 +609,6 @@ class MasterView(View):
context['dform'] = form.make_deform_form()
return self.render_to_response(template, context)
- def mobile_create(self):
- """
- Mobile view for creating a new primary object
- """
- self.mobile = True
- self.creating = True
- form = self.make_mobile_form(self.get_model_class())
- if self.request.method == 'POST':
- if self.validate_mobile_form(form):
- # let save_create_form() return alternate object if necessary
- obj = self.save_mobile_create_form(form)
- self.after_create(obj)
- self.flash_after_create(obj)
- return self.redirect_after_create(obj, mobile=True)
- return self.render_to_response('create', {'form': form}, mobile=True)
-
def save_create_form(self, form):
uploads = self.normalize_uploads(form)
self.before_create(form)
@@ -1044,19 +837,10 @@ class MasterView(View):
self.request.session.flash("{} has been created: {}".format(
self.get_model_title(), self.get_instance_title(obj)))
- def save_mobile_create_form(self, form):
- self.before_create(form)
- with self.Session.no_autoflush:
- obj = self.objectify(form, self.form_deserialized)
- self.before_create_flush(obj, form)
- self.Session.add(obj)
- self.Session.flush()
- return obj
-
- def redirect_after_create(self, instance, mobile=False):
+ def redirect_after_create(self, instance, **kwargs):
if self.populatable and self.should_populate(instance):
- return self.redirect(self.get_action_url('populate', instance, mobile=mobile))
- return self.redirect(self.get_action_url('view', instance, mobile=mobile))
+ return self.redirect(self.get_action_url('populate', instance))
+ return self.redirect(self.get_action_url('view', instance))
def should_populate(self, obj):
return True
@@ -1249,8 +1033,8 @@ class MasterView(View):
self.Session.flush()
return cloned
- def redirect_after_clone(self, instance, mobile=False):
- return self.redirect(self.get_action_url('view', instance, mobile=mobile))
+ def redirect_after_clone(self, instance, **kwargs):
+ return self.redirect(self.get_action_url('view', instance))
def touch(self):
"""
@@ -1414,75 +1198,6 @@ class MasterView(View):
versions.extend(query.all())
return versions
- def mobile_view(self):
- """
- Mobile view for displaying a single object's details
- """
- self.mobile = True
- self.viewing = True
- instance = self.get_instance()
- form = self.make_mobile_form(instance)
-
- context = {
- 'instance': instance,
- 'instance_title': self.get_instance_title(instance),
- 'instance_editable': self.editable_instance(instance),
- # 'instance_deletable': self.deletable_instance(instance),
- 'form': form,
- }
- if self.has_rows:
- context['model_row_class'] = self.model_row_class
- context['grid'] = self.make_mobile_row_grid(instance=instance)
- return self.render_to_response('view', context, mobile=True)
-
- def make_mobile_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
- """
- Creates a new mobile form for the given model class/instance.
- """
- if factory is None:
- factory = self.get_mobile_form_factory()
- if fields is None:
- fields = self.get_mobile_form_fields()
- if schema is None:
- schema = self.make_mobile_form_schema()
-
- if not self.creating:
- kwargs['model_instance'] = instance
- kwargs = self.make_mobile_form_kwargs(**kwargs)
- form = factory(fields, schema, **kwargs)
- self.configure_mobile_form(form)
- return form
-
- def get_mobile_form_fields(self):
- if hasattr(self, 'mobile_form_fields'):
- return self.mobile_form_fields
- # TODO
- # raise NotImplementedError
-
- def make_mobile_form_schema(self):
- if not self.model_class:
- # TODO
- raise NotImplementedError
-
- def make_mobile_form_kwargs(self, **kwargs):
- """
- Return a dictionary of kwargs to be passed to the factory when creating
- new mobile forms.
- """
- defaults = {
- 'request': self.request,
- 'readonly': self.viewing,
- 'model_class': getattr(self, 'model_class', None),
- 'action_url': self.request.current_route_url(_query=None),
- }
- if self.creating:
- defaults['cancel_url'] = self.get_index_url(mobile=True)
- else:
- instance = kwargs['model_instance']
- defaults['cancel_url'] = self.get_action_url('view', instance, mobile=True)
- defaults.update(kwargs)
- return defaults
-
def configure_common_form(self, form):
"""
Configure the form in whatever way is deemed "common" - i.e. where
@@ -1491,6 +1206,8 @@ class MasterView(View):
By default this removes the 'uuid' field (if present), sets any primary
key fields to be readonly (if we have a :attr:`model_class` and are in
edit mode), and sets labels as defined by the master class hierarchy.
+
+ TODO: this logic should be moved back into configure_form()
"""
form.remove_field('uuid')
@@ -1516,62 +1233,29 @@ class MasterView(View):
# is the safer option and would help prevent unwanted mistakes
form.set_default('local_only', True)
- def configure_mobile_form(self, form):
- """
- Configure the main "mobile" form for the view's data model.
- """
- self.configure_common_form(form)
-
- def validate_mobile_form(self, form):
- if form.validate(newstyle=True):
- # TODO: deprecate / remove self.form_deserialized
- self.form_deserialized = form.validated
- return True
- else:
- return False
-
- def make_mobile_row_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
- """
- Creates a new mobile form for the given model class/instance.
- """
- if factory is None:
- factory = self.get_mobile_row_form_factory()
- if fields is None:
- fields = self.get_mobile_row_form_fields()
- if schema is None:
- schema = self.make_mobile_row_form_schema()
-
- if not self.creating:
- kwargs['model_instance'] = instance
- kwargs = self.make_mobile_row_form_kwargs(**kwargs)
- form = factory(fields, schema, **kwargs)
- self.configure_mobile_row_form(form)
- return form
-
- def make_quick_row_form(self, instance=None, factory=None, fields=None, schema=None, mobile=False, **kwargs):
+ def make_quick_row_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
"""
Creates a "quick" form for adding a new row to the given instance.
"""
if factory is None:
- factory = self.get_quick_row_form_factory(mobile=mobile)
+ factory = self.get_quick_row_form_factory()
if fields is None:
- fields = self.get_quick_row_form_fields(mobile=mobile)
+ fields = self.get_quick_row_form_fields()
if schema is None:
- schema = self.make_quick_row_form_schema(mobile=mobile)
+ schema = self.make_quick_row_form_schema()
- kwargs['mobile'] = mobile
kwargs = self.make_quick_row_form_kwargs(**kwargs)
form = factory(fields, schema, **kwargs)
- self.configure_quick_row_form(form, mobile=mobile)
+ self.configure_quick_row_form(form)
return form
- def get_quick_row_form_factory(self, mobile=False):
+ def get_quick_row_form_factory(self, **kwargs):
return forms.Form
- def get_quick_row_form_fields(self, mobile=False):
+ def get_quick_row_form_fields(self, **kwargs):
pass
- def make_quick_row_form_schema(self, mobile=False):
+ def make_quick_row_form_schema(self, **kwargs):
schema = colander.MappingSchema()
schema.add(colander.SchemaNode(colander.String(), name='quick_entry'))
return schema
@@ -1585,102 +1269,12 @@ class MasterView(View):
defaults.update(kwargs)
return defaults
- def configure_quick_row_form(self, form, mobile=False):
+ def configure_quick_row_form(self, form, **kwargs):
pass
- def get_mobile_row_form_fields(self):
- if hasattr(self, 'mobile_row_form_fields'):
- return self.mobile_row_form_fields
- # TODO
- # raise NotImplementedError
-
- def make_mobile_row_form_schema(self):
- if not self.model_row_class:
- # TODO
- raise NotImplementedError
-
- def make_mobile_row_form_kwargs(self, **kwargs):
- """
- Return a dictionary of kwargs to be passed to the factory when creating
- new mobile row forms.
- """
- defaults = {
- 'request': self.request,
- 'mobile': True,
- 'readonly': self.viewing,
- 'model_class': getattr(self, 'model_row_class', None),
- 'action_url': self.request.current_route_url(_query=None),
- }
- if self.creating:
- defaults['cancel_url'] = self.request.get_referrer()
- else:
- instance = kwargs['model_instance']
- defaults['cancel_url'] = self.get_row_action_url('view', instance, mobile=True)
- defaults.update(kwargs)
- return defaults
-
- def configure_mobile_row_form(self, form):
- """
- Configure the mobile row form.
- """
- # TODO: is any of this stuff from configure_form() needed?
- # if self.editing:
- # model_class = self.get_model_class(error=False)
- # if model_class:
- # mapper = orm.class_mapper(model_class)
- # for key in mapper.primary_key:
- # for field in form.fields:
- # if field == key.name:
- # form.set_readonly(field)
- # break
- # form.remove_field('uuid')
-
- self.set_row_labels(form)
-
- def validate_mobile_row_form(self, form):
- controls = self.request.POST.items()
- try:
- self.form_deserialized = form.validate(controls)
- except deform.ValidationFailure:
- return False
- return True
-
def validate_quick_row_form(self, form):
return form.validate(newstyle=True)
- def get_mobile_row_data(self, parent):
- query = self.get_row_data(parent)
- return self.sort_mobile_row_data(query)
-
- def sort_mobile_row_data(self, query):
- return query
-
- def mobile_row_route_url(self, route_name, **kwargs):
- route_name = 'mobile.{}.{}_row'.format(self.get_route_prefix(), route_name)
- return self.request.route_url(route_name, **kwargs)
-
- def mobile_view_row(self):
- """
- Mobile view for row items
- """
- self.mobile = True
- self.viewing = True
- row = self.get_row_instance()
- parent = self.get_parent(row)
- form = self.make_mobile_row_form(row)
- context = {
- 'row': row,
- 'parent_instance': parent,
- 'parent_title': self.get_instance_title(parent),
- 'parent_url': self.get_action_url('view', parent, mobile=True),
- 'instance': row,
- 'instance_title': self.get_row_instance_title(row),
- 'instance_editable': self.row_editable(row),
- 'parent_model_title': self.get_model_title(),
- 'form': form,
- }
- return self.render_to_response('view_row', context, mobile=True)
-
def make_default_row_grid_tools(self, obj):
if self.rows_creatable:
link = tags.link_to("Create a new {}".format(self.get_row_model_title()),
@@ -1851,61 +1445,16 @@ class MasterView(View):
context['dform'] = form.make_deform_form()
return self.render_to_response('edit', context)
- def mobile_edit(self):
- """
- Mobile view for editing an existing model record.
- """
- self.mobile = True
- self.editing = True
- obj = self.get_instance()
-
- if not self.editable_instance(obj):
- msg = "Edit is not permitted for {}: {}".format(
- self.get_model_title(),
- self.get_instance_title(obj))
- self.request.session.flash(msg, 'error')
- return self.redirect(self.get_action_url('view', obj))
-
- form = self.make_mobile_form(obj)
-
- if self.request.method == 'POST':
- if self.validate_mobile_form(form):
-
- # note that save_form() may return alternate object
- obj = self.save_mobile_edit_form(form)
-
- msg = "{} has been updated: {}".format(
- self.get_model_title(),
- self.get_instance_title(obj))
- self.request.session.flash(msg)
- return self.redirect_after_edit(obj, mobile=True)
-
- context = {
- 'instance': obj,
- 'instance_title': self.get_instance_title(obj),
- 'instance_deletable': self.deletable_instance(obj),
- 'instance_url': self.get_action_url('view', obj, mobile=True),
- 'form': form,
- }
- if hasattr(form, 'make_deform_form'):
- context['dform'] = form.make_deform_form()
- return self.render_to_response('edit', context, mobile=True)
-
def save_edit_form(self, form):
- if not self.mobile:
- uploads = self.normalize_uploads(form)
+ uploads = self.normalize_uploads(form)
obj = self.objectify(form)
- if not self.mobile:
- self.process_uploads(obj, form, uploads)
+ self.process_uploads(obj, form, uploads)
self.after_edit(obj)
self.Session.flush()
return obj
- def save_mobile_edit_form(self, form):
- return self.save_edit_form(form)
-
- def redirect_after_edit(self, instance, mobile=False):
- return self.redirect(self.get_action_url('view', instance, mobile=mobile))
+ def redirect_after_edit(self, instance, **kwargs):
+ return self.redirect(self.get_action_url('view', instance))
def delete(self):
"""
@@ -2350,13 +1899,11 @@ class MasterView(View):
"""
return getattr(cls, 'permission_prefix', cls.get_route_prefix())
- def get_index_url(self, mobile=False, **kwargs):
+ def get_index_url(self, **kwargs):
"""
Returns the master view's index URL.
"""
route = self.get_route_prefix()
- if mobile:
- route = 'mobile.{}'.format(route)
return self.request.route_url(route, **kwargs)
@classmethod
@@ -2366,15 +1913,13 @@ class MasterView(View):
"""
return getattr(cls, 'index_title', cls.get_model_title_plural())
- def get_action_url(self, action, instance, mobile=False, **kwargs):
+ def get_action_url(self, action, instance, **kwargs):
"""
Generate a URL for the given action on the given instance
"""
kw = self.get_action_route_kwargs(instance)
kw.update(kwargs)
route_prefix = self.get_route_prefix()
- if mobile:
- route_prefix = 'mobile.{}'.format(route_prefix)
return self.request.route_url('{}.{}'.format(route_prefix, action), **kw)
def get_help_url(self):
@@ -2394,7 +1939,7 @@ class MasterView(View):
return global_help_url(self.rattail_config)
- def render_to_response(self, template, data, mobile=False):
+ def render_to_response(self, template, data, **kwargs):
"""
Return a response with the given template rendered with the given data.
Note that ``template`` must only be a "key" (e.g. 'index' or 'view').
@@ -2405,13 +1950,12 @@ class MasterView(View):
context = {
'master': self,
'use_buefy': self.get_use_buefy(),
- 'mobile': mobile,
'model_title': self.get_model_title(),
'model_title_plural': self.get_model_title_plural(),
'route_prefix': self.get_route_prefix(),
'permission_prefix': self.get_permission_prefix(),
'index_title': self.get_index_title(),
- 'index_url': self.get_index_url(mobile=mobile),
+ 'index_url': self.get_index_url(),
'action_url': self.get_action_url,
'grid_index': self.grid_index,
'help_url': self.get_help_url(),
@@ -2430,34 +1974,20 @@ class MasterView(View):
context['row_model_title_plural'] = self.get_row_model_title_plural()
context['row_action_url'] = self.get_row_action_url
- if mobile and self.viewing and self.mobile_rows_quickable:
-
- # quick row does *not* mimic keyboard wedge by default, but can
- context['quick_row_keyboard_wedge'] = False
-
- # quick row does *not* use autocomplete by default, but can
- context['quick_row_autocomplete'] = False
- context['quick_row_autocomplete_url'] = '#'
-
context.update(data)
context.update(self.template_kwargs(**context))
if hasattr(self, 'template_kwargs_{}'.format(template)):
context.update(getattr(self, 'template_kwargs_{}'.format(template))(**context))
- if mobile and hasattr(self, 'mobile_template_kwargs_{}'.format(template)):
- context.update(getattr(self, 'mobile_template_kwargs_{}'.format(template))(**context))
# First try the template path most specific to the view.
- if mobile:
- mako_path = '/mobile{}/{}.mako'.format(self.get_template_prefix(), template)
- else:
- mako_path = '{}/{}.mako'.format(self.get_template_prefix(), template)
+ mako_path = '{}/{}.mako'.format(self.get_template_prefix(), template)
try:
return render_to_response(mako_path, context, request=self.request)
except IOError:
# Failing that, try one or more fallback templates.
- for fallback in self.get_fallback_templates(template, mobile=mobile):
+ for fallback in self.get_fallback_templates(template):
try:
return render_to_response(fallback, context, request=self.request)
except IOError:
@@ -2504,9 +2034,7 @@ class MasterView(View):
return render('{}/{}.mako'.format(self.get_template_prefix(), template),
context, request=self.request)
- def get_fallback_templates(self, template, mobile=False):
- if mobile:
- return ['/mobile/master/{}.mako'.format(template)]
+ def get_fallback_templates(self, template, **kwargs):
return ['/master/{}.mako'.format(template)]
def get_default_engine_dbkey(self):
@@ -3736,14 +3264,6 @@ class MasterView(View):
"""
return getattr(cls, 'form_factory', forms.Form)
- @classmethod
- def get_mobile_form_factory(cls):
- """
- Returns the factory or class which is to be used when creating new
- mobile forms.
- """
- return getattr(cls, 'mobile_form_factory', forms.Form)
-
@classmethod
def get_row_form_factory(cls):
"""
@@ -3752,14 +3272,6 @@ class MasterView(View):
"""
return getattr(cls, 'row_form_factory', forms.Form)
- @classmethod
- def get_mobile_row_form_factory(cls):
- """
- Returns the factory or class which is to be used when creating new
- mobile row forms.
- """
- return getattr(cls, 'mobile_row_form_factory', forms.Form)
-
def download_path(self, obj, filename):
"""
Should return absolute path on disk, for the given object and filename.
@@ -4055,49 +3567,8 @@ class MasterView(View):
def after_create_row(self, row_object):
pass
- def redirect_after_create_row(self, row, mobile=False):
- return self.redirect(self.get_row_action_url('view', row, mobile=mobile))
-
- def mobile_create_row(self):
- """
- Mobile view for creating a new row object
- """
- self.mobile = True
- self.creating = True
- parent = self.get_instance()
- instance_url = self.get_action_url('view', parent, mobile=True)
- form = self.make_mobile_row_form(self.model_row_class, cancel_url=instance_url)
- if self.request.method == 'POST':
- if self.validate_mobile_row_form(form):
- self.before_create_row(form)
- # let save() return alternate object if necessary
- obj = self.save_create_row_form(form)
- self.after_create_row(obj)
- return self.redirect_after_create_row(obj, mobile=True)
- return self.render_to_response('create_row', {
- 'instance_title': self.get_instance_title(parent),
- 'instance_url': instance_url,
- 'parent_object': parent,
- 'form': form,
- }, mobile=True)
-
- def mobile_quick_row(self):
- """
- Mobile view for "quick" location or creation of a row object
- """
- parent = self.get_instance()
- parent_url = self.get_action_url('view', parent, mobile=True)
- form = self.make_quick_row_form(self.model_row_class, mobile=True, cancel_url=parent_url)
- if self.request.method == 'POST':
- if self.validate_quick_row_form(form):
- row = self.save_quick_row_form(form)
- if not row:
- self.request.session.flash("Could not locate/create row for entry: "
- "{}".format(form.validated['quick_entry']),
- 'error')
- return self.redirect(parent_url)
- return self.redirect_after_quick_row(row, mobile=True)
- return self.redirect(parent_url)
+ def redirect_after_create_row(self, row, **kwargs):
+ return self.redirect(self.get_row_action_url('view', row))
def save_quick_row_form(self, form):
raise NotImplementedError("You must define `{}:{}.save_quick_row_form()` "
@@ -4105,8 +3576,8 @@ class MasterView(View):
self.__class__.__module__,
self.__class__.__name__))
- def redirect_after_quick_row(self, row, mobile=False):
- return self.redirect(self.get_row_action_url('edit', row, mobile=mobile))
+ def redirect_after_quick_row(self, row, **kwargs):
+ return self.redirect(self.get_row_action_url('edit', row))
def view_row(self):
"""
@@ -4182,34 +3653,6 @@ class MasterView(View):
'dform': form.make_deform_form(),
})
- def mobile_edit_row(self):
- """
- Mobile view for editing a row object
- """
- self.mobile = True
- self.editing = True
- row = self.get_row_instance()
- instance_url = self.get_row_action_url('view', row, mobile=True)
- form = self.make_mobile_row_form(row)
-
- if self.request.method == 'POST':
- if self.validate_mobile_row_form(form):
- self.save_edit_row_form(form)
- return self.redirect_after_edit_row(row, mobile=True)
-
- parent = self.get_parent(row)
- return self.render_to_response('edit_row', {
- 'row': row,
- 'instance': row,
- 'parent_instance': parent,
- 'instance_title': self.get_row_instance_title(row),
- 'instance_url': instance_url,
- 'instance_deletable': self.row_deletable(row),
- 'parent_title': self.get_instance_title(parent),
- 'parent_url': self.get_action_url('view', parent, mobile=True),
- 'form': form},
- mobile=True)
-
def save_edit_row_form(self, form):
obj = self.objectify(form, self.form_deserialized)
self.after_edit_row(obj)
@@ -4224,8 +3667,8 @@ class MasterView(View):
Event hook, called just after an existing row object is saved.
"""
- def redirect_after_edit_row(self, row, mobile=False):
- return self.redirect(self.get_row_action_url('view', row, mobile=mobile))
+ def redirect_after_edit_row(self, row, **kwargs):
+ return self.redirect(self.get_row_action_url('view', row))
def row_deletable(self, row):
"""
@@ -4252,22 +3695,6 @@ class MasterView(View):
self.delete_row_object(row)
return self.redirect(self.get_action_url('view', self.get_parent(row)))
- def mobile_delete_row(self):
- """
- Mobile view which can "delete" a sub-row from the parent.
- """
- if self.request.method == 'POST':
- parent = self.get_instance()
- row = self.get_row_instance()
- if self.get_parent(row) is not parent:
- raise RuntimeError("Can only delete rows which belong to current object")
-
- self.delete_row_object(row)
- return self.redirect(self.get_action_url('view', parent, mobile=True))
-
- self.session.flash("Must POST to delete a row", 'error')
- return self.redirect(self.request.get_referrer(mobile=True))
-
def get_parent(self, row):
raise NotImplementedError
@@ -4357,13 +3784,11 @@ class MasterView(View):
return True
return False
- def get_row_action_url(self, action, row, mobile=False):
+ def get_row_action_url(self, action, row, **kwargs):
"""
Generate a URL for the given action on the given row.
"""
route_name = '{}.{}_row'.format(self.get_route_prefix(), action)
- if mobile:
- route_name = 'mobile.{}'.format(route_name)
return self.request.route_url(route_name, **self.get_row_action_route_kwargs(row))
def get_row_action_route_kwargs(self, row):
@@ -4426,7 +3851,6 @@ class MasterView(View):
model_title_plural = cls.get_model_title_plural()
if cls.has_rows:
row_model_title = cls.get_row_model_title()
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
config.add_tailbone_permission_group(permission_prefix, model_title_plural, overwrite=False)
@@ -4437,10 +3861,6 @@ class MasterView(View):
config.add_route(route_prefix, '{}/'.format(url_prefix))
config.add_view(cls, attr='index', route_name=route_prefix,
permission='{}.list'.format(permission_prefix))
- if legacy_mobile and cls.supports_mobile:
- config.add_route('mobile.{}'.format(route_prefix), '/mobile{}/'.format(url_prefix))
- config.add_view(cls, attr='mobile_index', route_name='mobile.{}'.format(route_prefix),
- permission='{}.list'.format(permission_prefix))
# download results
# this is the "new" more flexible approach, but we only want to
@@ -4495,17 +3915,12 @@ class MasterView(View):
permission='{}.quickie'.format(permission_prefix))
# create
- if cls.creatable or (legacy_mobile and cls.mobile_creatable):
+ if cls.creatable:
config.add_tailbone_permission(permission_prefix, '{}.create'.format(permission_prefix),
"Create new {}".format(model_title))
- if cls.creatable:
config.add_route('{}.create'.format(route_prefix), '{}/new'.format(url_prefix))
config.add_view(cls, attr='create', route_name='{}.create'.format(route_prefix),
permission='{}.create'.format(permission_prefix))
- if legacy_mobile and cls.mobile_creatable:
- config.add_route('mobile.{}.create'.format(route_prefix), '/mobile{}/new'.format(url_prefix))
- config.add_view(cls, attr='mobile_create', route_name='mobile.{}.create'.format(route_prefix),
- permission='{}.create'.format(permission_prefix))
# populate new object
if cls.populatable:
@@ -4572,10 +3987,6 @@ class MasterView(View):
config.add_route('{}.view'.format(route_prefix), instance_url_prefix)
config.add_view(cls, attr='view', route_name='{}.view'.format(route_prefix),
permission='{}.view'.format(permission_prefix))
- if legacy_mobile and cls.supports_mobile:
- config.add_route('mobile.{}.view'.format(route_prefix), '/mobile{}'.format(instance_url_prefix))
- config.add_view(cls, attr='mobile_view', route_name='mobile.{}.view'.format(route_prefix),
- permission='{}.view'.format(permission_prefix))
# version history
if cls.has_versions and rattail_config and rattail_config.versioning_enabled():
@@ -4625,30 +4036,20 @@ class MasterView(View):
"Download associated data for {}".format(model_title))
# edit
- if cls.editable or (legacy_mobile and cls.mobile_editable):
+ if cls.editable:
config.add_tailbone_permission(permission_prefix, '{}.edit'.format(permission_prefix),
"Edit {}".format(model_title))
- if cls.editable:
config.add_route('{}.edit'.format(route_prefix), '{}/edit'.format(instance_url_prefix))
config.add_view(cls, attr='edit', route_name='{}.edit'.format(route_prefix),
permission='{}.edit'.format(permission_prefix))
- if legacy_mobile and cls.mobile_editable:
- config.add_route('mobile.{}.edit'.format(route_prefix), '/mobile{}/edit'.format(instance_url_prefix))
- config.add_view(cls, attr='mobile_edit', route_name='mobile.{}.edit'.format(route_prefix),
- permission='{}.edit'.format(permission_prefix))
# execute
- if cls.executable or (legacy_mobile and cls.mobile_executable):
+ if cls.executable:
config.add_tailbone_permission(permission_prefix, '{}.execute'.format(permission_prefix),
"Execute {}".format(model_title))
- if cls.executable:
config.add_route('{}.execute'.format(route_prefix), '{}/execute'.format(instance_url_prefix))
config.add_view(cls, attr='execute', route_name='{}.execute'.format(route_prefix),
permission='{}.execute'.format(permission_prefix))
- if legacy_mobile and cls.mobile_executable:
- config.add_route('mobile.{}.execute'.format(route_prefix), '/mobile{}/execute'.format(instance_url_prefix))
- config.add_view(cls, attr='mobile_execute', route_name='mobile.{}.execute'.format(route_prefix),
- permission='{}.execute'.format(permission_prefix))
# delete
if cls.deletable:
@@ -4683,21 +4084,12 @@ class MasterView(View):
# create row
if cls.has_rows:
- if cls.rows_creatable or (legacy_mobile and cls.mobile_rows_creatable):
+ if cls.rows_creatable:
config.add_tailbone_permission(permission_prefix, '{}.create_row'.format(permission_prefix),
"Create new {} rows".format(model_title))
- if cls.rows_creatable:
config.add_route('{}.create_row'.format(route_prefix), '{}/new-row'.format(instance_url_prefix))
config.add_view(cls, attr='create_row', route_name='{}.create_row'.format(route_prefix),
permission='{}.create_row'.format(permission_prefix))
- if legacy_mobile and cls.mobile_rows_creatable:
- config.add_route('mobile.{}.create_row'.format(route_prefix), '/mobile{}/new-row'.format(instance_url_prefix))
- config.add_view(cls, attr='mobile_create_row', route_name='mobile.{}.create_row'.format(route_prefix),
- permission='{}.create_row'.format(permission_prefix))
- if cls.mobile_rows_quickable:
- config.add_route('mobile.{}.quick_row'.format(route_prefix), '/mobile{}/quick-row'.format(instance_url_prefix))
- config.add_view(cls, attr='mobile_quick_row', route_name='mobile.{}.quick_row'.format(route_prefix),
- permission='{}.create_row'.format(permission_prefix))
# view row
if cls.has_rows:
@@ -4705,35 +4097,21 @@ class MasterView(View):
config.add_route('{}.view_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}'.format(url_prefix))
config.add_view(cls, attr='view_row', route_name='{}.view_row'.format(route_prefix),
permission='{}.view'.format(permission_prefix))
- if legacy_mobile and cls.mobile_rows_viewable:
- config.add_route('mobile.{}.view_row'.format(route_prefix), '/mobile{}/{{uuid}}/rows/{{row_uuid}}'.format(url_prefix))
- config.add_view(cls, attr='mobile_view_row', route_name='mobile.{}.view_row'.format(route_prefix),
- permission='{}.view'.format(permission_prefix))
# edit row
if cls.has_rows:
- if cls.rows_editable or (legacy_mobile and cls.mobile_rows_editable):
+ if cls.rows_editable:
config.add_tailbone_permission(permission_prefix, '{}.edit_row'.format(permission_prefix),
"Edit individual {} rows".format(model_title))
- if cls.rows_editable:
config.add_route('{}.edit_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/edit'.format(url_prefix))
config.add_view(cls, attr='edit_row', route_name='{}.edit_row'.format(route_prefix),
permission='{}.edit_row'.format(permission_prefix))
- if legacy_mobile and cls.mobile_rows_editable:
- config.add_route('mobile.{}.edit_row'.format(route_prefix), '/mobile{}/{{uuid}}/rows/{{row_uuid}}/edit'.format(url_prefix))
- config.add_view(cls, attr='mobile_edit_row', route_name='mobile.{}.edit_row'.format(route_prefix),
- permission='{}.edit_row'.format(permission_prefix))
# delete row
if cls.has_rows:
- if cls.rows_deletable or (legacy_mobile and cls.mobile_rows_deletable):
+ if cls.rows_deletable:
config.add_tailbone_permission(permission_prefix, '{}.delete_row'.format(permission_prefix),
"Delete individual {} rows".format(model_title))
- if cls.rows_deletable:
config.add_route('{}.delete_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/delete'.format(url_prefix))
config.add_view(cls, attr='delete_row', route_name='{}.delete_row'.format(route_prefix),
permission='{}.delete_row'.format(permission_prefix))
- if legacy_mobile and cls.mobile_rows_deletable:
- config.add_route('mobile.{}.delete_row'.format(route_prefix), '/mobile{}/{{uuid}}/rows/{{row_uuid}}/delete'.format(url_prefix))
- config.add_view(cls, attr='mobile_delete_row', route_name='mobile.{}.delete_row'.format(route_prefix),
- permission='{}.delete_row'.format(permission_prefix))
diff --git a/tailbone/views/people.py b/tailbone/views/people.py
index 54c84d82..de970119 100644
--- a/tailbone/views/people.py
+++ b/tailbone/views/people.py
@@ -53,7 +53,6 @@ class PersonView(MasterView):
route_prefix = 'people'
touchable = True
has_versions = True
- supports_mobile = True
bulk_deletable = True
is_contact = True
manage_notes_from_profile_view = False
@@ -85,19 +84,6 @@ class PersonView(MasterView):
'users',
]
- mobile_form_fields = [
- 'first_name',
- 'middle_name',
- 'last_name',
- 'display_name',
- 'phone',
- 'email',
- 'address',
- 'employee',
- 'customers',
- 'users',
- ]
-
mergeable = True
merge_additive_fields = [
'usernames',
@@ -331,8 +317,7 @@ class PersonView(MasterView):
text = "(#{}) {}".format(customer.number, text)
elif customer.id:
text = "({}) {}".format(customer.id, text)
- route = '{}customers.view'.format('mobile.' if self.mobile else '')
- url = self.request.route_url(route, uuid=customer.uuid)
+ url = self.request.route_url('customers.view', uuid=customer.uuid)
items.append(HTML.tag('li', c=[tags.link_to(text, url)]))
return HTML.tag('ul', c=items)
diff --git a/tailbone/views/principal.py b/tailbone/views/principal.py
index e22e2554..b3d032ab 100644
--- a/tailbone/views/principal.py
+++ b/tailbone/views/principal.py
@@ -2,7 +2,7 @@
################################################################################
#
# Rattail -- Retail Software Framework
-# Copyright © 2010-2019 Lance Edgar
+# Copyright © 2010-2021 Lance Edgar
#
# This file is part of Rattail.
#
@@ -44,10 +44,10 @@ class PrincipalMasterView(MasterView):
Master view base class for security principal models, i.e. User and Role.
"""
- def get_fallback_templates(self, template, mobile=False):
+ def get_fallback_templates(self, template, **kwargs):
return [
'/principal/{}.mako'.format(template),
- ] + super(PrincipalMasterView, self).get_fallback_templates(template, mobile=mobile)
+ ] + super(PrincipalMasterView, self).get_fallback_templates(template, **kwargs)
def perm_sortkey(self, item):
key, value = item
diff --git a/tailbone/views/products.py b/tailbone/views/products.py
index 526a9160..e7afa49a 100644
--- a/tailbone/views/products.py
+++ b/tailbone/views/products.py
@@ -82,7 +82,6 @@ class ProductView(MasterView):
Master view for the Product class.
"""
model_class = model.Product
- supports_mobile = True
has_versions = True
results_downloadable_xlsx = True
@@ -157,8 +156,6 @@ class ProductView(MasterView):
'inventory_on_order',
]
- mobile_form_fields = form_fields
-
# These aliases enable the grid queries to filter products which may be
# purchased from *any* vendor, and yet sort by only the "preferred" vendor
# (since that's what shows up in the grid column).
@@ -936,7 +933,7 @@ class ProductView(MasterView):
else:
code = pack.item_id
text = "({}) {}".format(code, pack.full_description)
- url = self.get_action_url('view', pack, mobile=self.mobile)
+ url = self.get_action_url('view', pack)
links.append(tags.link_to(text, url))
items = [HTML.tag('li', c=[link]) for link in links]
@@ -955,7 +952,7 @@ class ProductView(MasterView):
code = unit.item_id
text = "({}) {}".format(code, unit.full_description)
- url = self.get_action_url('view', unit, mobile=self.mobile)
+ url = self.get_action_url('view', unit)
return tags.link_to(text, url)
def render_current_price_ends(self, product, field):
@@ -1494,37 +1491,6 @@ class ProductView(MasterView):
'instance_title': self.get_instance_title(instance),
'form': form})
- def mobile_index(self):
- """
- Mobile "home" page for products
- """
- self.mobile = True
- context = {
- 'quick_lookup': False,
- 'placeholder': "Enter {}".format(self.rattail_config.product_key_title()),
- 'quick_lookup_keyboard_wedge': True,
- }
- if self.rattail_config.getbool('rattail', 'products.mobile.quick_lookup', default=False):
- context['quick_lookup'] = True
- else:
- self.listing = True
- grid = self.make_mobile_grid()
- context['grid'] = grid
- return self.render_to_response('index', context, mobile=True)
-
- def mobile_quick_lookup(self):
- entry = self.request.POST['quick_entry'].strip()
- provided = GPC(entry, calc_check_digit=False)
- product = api.get_product_by_upc(self.Session(), provided)
- if not product:
- checked = GPC(entry, calc_check_digit='upc')
- product = api.get_product_by_upc(self.Session(), checked)
- if not product:
- product = api.get_product_by_code(self.Session(), entry)
- if not product:
- raise self.notfound()
- return self.redirect(self.get_action_url('view', product, mobile=True))
-
def get_version_child_classes(self):
return [
(model.ProductCode, 'product_uuid'),
@@ -1746,7 +1712,6 @@ class ProductView(MasterView):
template_prefix = cls.get_template_prefix()
permission_prefix = cls.get_permission_prefix()
model_title = cls.get_model_title()
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# print labels
config.add_tailbone_permission('products', 'products.print_labels',
@@ -1787,11 +1752,6 @@ class ProductView(MasterView):
renderer='json',
permission='{}.versions'.format(permission_prefix))
- # mobile quick lookup
- if legacy_mobile:
- config.add_route('mobile.products.quick_lookup', '/mobile/products/quick-lookup')
- config.add_view(cls, attr='mobile_quick_lookup', route_name='mobile.products.quick_lookup')
-
# TODO: deprecate / remove this
ProductsView = ProductView
diff --git a/tailbone/views/purchasing/batch.py b/tailbone/views/purchasing/batch.py
index ebfcf8ce..1ca8c21c 100644
--- a/tailbone/views/purchasing/batch.py
+++ b/tailbone/views/purchasing/batch.py
@@ -150,34 +150,6 @@ class PurchasingBatchView(BatchMasterView):
'credits',
]
- mobile_row_form_fields = [
- 'upc',
- 'item_id',
- 'product',
- 'brand_name',
- 'description',
- 'size',
- 'case_quantity',
- 'cases_ordered',
- 'units_ordered',
- 'cases_received',
- 'units_received',
- 'cases_damaged',
- 'units_damaged',
- 'cases_expired',
- 'units_expired',
- 'cases_mispick',
- 'units_mispick',
- # 'po_line_number',
- 'po_unit_cost',
- 'po_total',
- # 'invoice_line_number',
- 'invoice_unit_cost',
- 'invoice_total',
- 'status_code',
- # 'credits',
- ]
-
@property
def batch_mode(self):
raise NotImplementedError("Please define `batch_mode` for your purchasing batch view")
@@ -518,8 +490,8 @@ class PurchasingBatchView(BatchMasterView):
total = purchase.invoice_total
return '{} for ${:0,.2f} ({})'.format(date, total or 0, purchase.department or purchase.buyer)
- def get_batch_kwargs(self, batch, mobile=False):
- kwargs = super(PurchasingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
+ def get_batch_kwargs(self, batch, **kwargs):
+ kwargs = super(PurchasingBatchView, self).get_batch_kwargs(batch, **kwargs)
kwargs['mode'] = self.batch_mode
kwargs['truck_dump'] = batch.truck_dump
kwargs['invoice_parser_key'] = batch.invoice_parser_key
@@ -596,9 +568,6 @@ class PurchasingBatchView(BatchMasterView):
# query = super(PurchasingBatchView, self).get_row_data(batch)
# return query.options(orm.joinedload(model.PurchaseBatchRow.credits))
- def sort_mobile_row_data(self, query):
- return query.order_by(model.PurchaseBatchRow.modified.desc())
-
def configure_row_grid(self, g):
super(PurchasingBatchView, self).configure_row_grid(g)
@@ -760,104 +729,6 @@ class PurchasingBatchView(BatchMasterView):
g.set_type('credit_total', 'currency')
return HTML.literal(g.render_grid())
- def configure_mobile_row_form(self, f):
- super(PurchasingBatchView, self).configure_mobile_row_form(f)
- # row = f.model_instance
- # if self.creating:
- # batch = self.get_instance()
- # else:
- # batch = self.get_parent(row)
-
- # # readonly fields
- # f.set_readonly('case_quantity')
- # f.set_readonly('credits')
-
- # quantity fields
- f.set_type('case_quantity', 'quantity')
- f.set_type('cases_ordered', 'quantity')
- f.set_type('units_ordered', 'quantity')
- f.set_type('cases_received', 'quantity')
- f.set_type('units_received', 'quantity')
- f.set_type('cases_damaged', 'quantity')
- f.set_type('units_damaged', 'quantity')
- f.set_type('cases_expired', 'quantity')
- f.set_type('units_expired', 'quantity')
- f.set_type('cases_mispick', 'quantity')
- f.set_type('units_mispick', 'quantity')
-
- # currency fields
- f.set_type('po_unit_cost', 'currency')
- f.set_type('po_total', 'currency')
- f.set_type('po_total_calculated', 'currency')
- f.set_type('invoice_unit_cost', 'currency')
- f.set_type('invoice_total', 'currency')
- f.set_type('invoice_total_calculated', 'currency')
-
- # if self.creating:
- # f.remove_fields(
- # 'upc',
- # 'product',
- # 'po_total',
- # 'invoice_total',
- # )
- # if self.batch_mode == self.enum.PURCHASE_BATCH_MODE_ORDERING:
- # f.remove_fields('cases_received',
- # 'units_received')
- # elif self.batch_mode == self.enum.PURCHASE_BATCH_MODE_RECEIVING:
- # f.remove_fields('cases_ordered',
- # 'units_ordered')
-
- # elif self.editing:
- # f.set_readonly('upc')
- # f.set_readonly('product')
- # f.remove_fields('po_total',
- # 'invoice_total',
- # 'status_code')
-
- # elif self.viewing:
- # if row.product:
- # f.remove_fields('brand_name',
- # 'description',
- # 'size')
- # else:
- # f.remove_field('product')
-
- def mobile_new_product(self):
- """
- View which allows user to create a new Product and add a row for it to
- the Purchasing Batch.
- """
- batch = self.get_instance()
- batch_url = self.get_action_url('view', batch, mobile=True)
- form = forms.Form(schema=self.make_new_product_schema(),
- request=self.request,
- mobile=True,
- cancel_url=batch_url)
-
- if form.validate(newstyle=True):
- product = model.Product()
- product.item_id = form.validated['item_id']
- product.description = form.validated['description']
- row = self.model_row_class()
- row.product = product
- self.handler.add_row(batch, row)
- self.Session.flush()
- return self.redirect(self.get_row_action_url('edit', row, mobile=True))
-
- return self.render_to_response('new_product', {
- 'form': form,
- 'dform': form.make_deform_form(),
- 'instance_title': self.get_instance_title(batch),
- 'instance_url': batch_url,
- }, mobile=True)
-
- def make_new_product_schema(self):
- """
- Must return a ``colander.Schema`` instance for use with the form in the
- :meth:`mobile_new_product()` view.
- """
- return NewProduct()
-
# def item_lookup(self, value, field=None):
# """
# Try to locate a single product using ``value`` as a lookup code.
@@ -956,9 +827,9 @@ class PurchasingBatchView(BatchMasterView):
# return self.redirect(self.request.current_route_url())
# TODO: seems like this should be master behavior, controlled by setting?
- def redirect_after_edit_row(self, row, mobile=False):
+ def redirect_after_edit_row(self, row, **kwargs):
parent = self.get_parent(row)
- return self.redirect(self.get_action_url('view', parent, mobile=mobile))
+ return self.redirect(self.get_action_url('view', parent))
# def get_execute_success_url(self, batch, result, **kwargs):
# # if batch execution yielded a Purchase, redirect to it
@@ -977,21 +848,12 @@ class PurchasingBatchView(BatchMasterView):
permission_prefix = cls.get_permission_prefix()
model_key = cls.get_model_key()
model_title = cls.get_model_title()
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# eligible purchases (AJAX)
config.add_route('{}.eligible_purchases'.format(route_prefix), '{}/eligible-purchases'.format(url_prefix))
config.add_view(cls, attr='eligible_purchases', route_name='{}.eligible_purchases'.format(route_prefix),
renderer='json', permission='{}.view'.format(permission_prefix))
- # add new product
- if legacy_mobile and cls.supports_new_product:
- config.add_tailbone_permission(permission_prefix, '{}.new_product'.format(permission_prefix),
- "Create new Product when adding row to {}".format(model_title))
- config.add_route('mobile.{}.new_product'.format(route_prefix), '{}/{{{}}}/new-product'.format(url_prefix, model_key))
- config.add_view(cls, attr='mobile_new_product', route_name='mobile.{}.new_product'.format(route_prefix),
- permission='{}.new_product'.format(permission_prefix))
-
@classmethod
def defaults(cls, config):
diff --git a/tailbone/views/purchasing/ordering.py b/tailbone/views/purchasing/ordering.py
index 2a7a9b11..43955263 100644
--- a/tailbone/views/purchasing/ordering.py
+++ b/tailbone/views/purchasing/ordering.py
@@ -51,12 +51,7 @@ class OrderingBatchView(PurchasingBatchView):
model_title = "Ordering Batch"
model_title_plural = "Ordering Batches"
index_title = "Ordering"
- mobile_creatable = True
rows_editable = True
- mobile_rows_creatable = True
- mobile_rows_quickable = True
- mobile_rows_editable = True
- mobile_rows_deletable = True
has_worksheet = True
labels = {
@@ -86,21 +81,6 @@ class OrderingBatchView(PurchasingBatchView):
'executed_by',
]
- mobile_form_fields = [
- 'vendor',
- 'department',
- 'date_ordered',
- 'po_number',
- 'po_total',
- 'created',
- 'created_by',
- 'notes',
- 'status_code',
- 'complete',
- 'executed',
- 'executed_by',
- ]
-
row_labels = {
'po_total_calculated': "PO Total",
}
@@ -161,8 +141,8 @@ class OrderingBatchView(PurchasingBatchView):
if self.creating or not batch.executed or not batch.purchase:
f.remove_field('purchase')
- def get_batch_kwargs(self, batch, mobile=False):
- kwargs = super(OrderingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
+ def get_batch_kwargs(self, batch, **kwargs):
+ kwargs = super(OrderingBatchView, self).get_batch_kwargs(batch, **kwargs)
kwargs['ship_method'] = batch.ship_method
kwargs['notes_to_vendor'] = batch.notes_to_vendor
return kwargs
@@ -387,60 +367,6 @@ class OrderingBatchView(PurchasingBatchView):
'batch_po_total_display': '${:0,.2f}'.format(batch.po_total_calculated or batch.po_total or 0),
}
- def render_mobile_listitem(self, batch, i):
- return "({}) {} on {} for ${:0,.2f}".format(batch.id_str, batch.vendor,
- batch.date_ordered, batch.po_total or 0)
-
- def mobile_create(self):
- """
- Mobile view for creating a new ordering batch
- """
- mode = self.batch_mode
- data = {'mode': mode}
-
- vendor = None
- if self.request.method == 'POST' and self.request.POST.get('vendor'):
- vendor = self.Session.query(model.Vendor).get(self.request.POST['vendor'])
- if vendor:
-
- # fetch first to avoid flush below
- store = self.rattail_config.get_store(self.Session())
-
- batch = self.model_class()
- batch.mode = mode
- batch.vendor = vendor
- batch.store = store
- batch.buyer = self.request.user.employee
- batch.created_by = self.request.user
- batch.po_total = 0
- kwargs = self.get_batch_kwargs(batch, mobile=True)
- batch = self.handler.make_batch(self.Session(), **kwargs)
- if self.handler.should_populate(batch):
- self.handler.populate(batch)
- return self.redirect(self.request.route_url('mobile.ordering.view', uuid=batch.uuid))
-
- data['index_title'] = self.get_index_title()
- data['index_url'] = self.get_index_url(mobile=True)
- data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize()
-
- data['vendor_use_autocomplete'] = self.rattail_config.getbool(
- 'rattail', 'vendor.use_autocomplete', default=True)
- if not data['vendor_use_autocomplete']:
- vendors = self.Session.query(model.Vendor)\
- .order_by(model.Vendor.name)
- options = [(tags.Option(vendor.name, vendor.uuid))
- for vendor in vendors]
- options.insert(0, tags.Option("(please choose)", ''))
- data['vendor_options'] = options
-
- return self.render_to_response('create', data, mobile=True)
-
- def configure_mobile_row_form(self, f):
- super(OrderingBatchView, self).configure_mobile_row_form(f)
- if self.editing:
- # TODO: probably should take `allow_cases` into account here...
- f.focus_spec = '[name="units_ordered"]'
-
def download_excel(self):
"""
Download ordering batch as Excel spreadsheet.
diff --git a/tailbone/views/purchasing/receiving.py b/tailbone/views/purchasing/receiving.py
index 17d2eddf..ff82595e 100644
--- a/tailbone/views/purchasing/receiving.py
+++ b/tailbone/views/purchasing/receiving.py
@@ -48,78 +48,11 @@ from webhelpers2.html import tags, HTML
from tailbone import forms, grids
from tailbone.views.purchasing import PurchasingBatchView
-from tailbone.forms.receiving import ReceiveRow as MobileReceivingForm
log = logging.getLogger(__name__)
-class MobileItemStatusFilter(grids.filters.MobileFilter):
-
- value_choices = ['incomplete', 'unexpected', 'damaged', 'expired', 'all']
-
- def filter_equal(self, query, value):
-
- # NOTE: this is only relevant for truck dump or "from scratch"
- if value == 'received':
- return query.filter(sa.or_(
- model.PurchaseBatchRow.cases_received != 0,
- model.PurchaseBatchRow.units_received != 0))
-
- if value == 'incomplete':
- # looking for any rows with "ordered" quantity, but where the
- # status does *not* signify a "settled" row so to speak
- # TODO: would be nice if we had a simple flag to leverage?
- return query.filter(sa.or_(model.PurchaseBatchRow.cases_ordered != 0,
- model.PurchaseBatchRow.units_ordered != 0))\
- .filter(~model.PurchaseBatchRow.status_code.in_((
- model.PurchaseBatchRow.STATUS_OK,
- model.PurchaseBatchRow.STATUS_PRODUCT_NOT_FOUND,
- model.PurchaseBatchRow.STATUS_CASE_QUANTITY_DIFFERS)))
-
- if value == 'invalid':
- return query.filter(model.PurchaseBatchRow.status_code.in_((
- model.PurchaseBatchRow.STATUS_PRODUCT_NOT_FOUND,
- model.PurchaseBatchRow.STATUS_COST_NOT_FOUND,
- model.PurchaseBatchRow.STATUS_CASE_QUANTITY_UNKNOWN,
- model.PurchaseBatchRow.STATUS_CASE_QUANTITY_DIFFERS,
- )))
-
- if value == 'unexpected':
- # looking for any rows which have "received" quantity but which
- # do *not* have any "ordered" quantity
- return query.filter(sa.and_(
- sa.or_(
- model.PurchaseBatchRow.cases_ordered == None,
- model.PurchaseBatchRow.cases_ordered == 0),
- sa.or_(
- model.PurchaseBatchRow.units_ordered == None,
- model.PurchaseBatchRow.units_ordered == 0),
- sa.or_(
- model.PurchaseBatchRow.cases_received != 0,
- model.PurchaseBatchRow.units_received != 0,
- model.PurchaseBatchRow.cases_damaged != 0,
- model.PurchaseBatchRow.units_damaged != 0,
- model.PurchaseBatchRow.cases_expired != 0,
- model.PurchaseBatchRow.units_expired != 0)))
-
- if value == 'damaged':
- return query.filter(sa.or_(
- model.PurchaseBatchRow.cases_damaged != 0,
- model.PurchaseBatchRow.units_damaged != 0))
-
- if value == 'expired':
- return query.filter(sa.or_(
- model.PurchaseBatchRow.cases_expired != 0,
- model.PurchaseBatchRow.units_expired != 0))
-
- return query
-
- def iter_choices(self):
- for value in self.value_choices:
- yield value, prettify(value)
-
-
class ReceivingBatchView(PurchasingBatchView):
"""
Master view for receiving batches
@@ -132,11 +65,6 @@ class ReceivingBatchView(PurchasingBatchView):
downloadable = True
bulk_deletable = True
rows_editable = True
- mobile_creatable = True
- mobile_rows_filterable = True
- mobile_rows_creatable = True
- mobile_rows_quickable = True
- mobile_rows_deletable = True
allow_from_po = False
allow_from_scratch = True
@@ -207,11 +135,6 @@ class ReceivingBatchView(PurchasingBatchView):
'executed_by',
]
- mobile_form_fields = [
- 'vendor',
- 'department',
- ]
-
row_grid_columns = [
'sequence',
'upc',
@@ -295,20 +218,9 @@ class ReceivingBatchView(PurchasingBatchView):
if batch.executed or batch.complete:
return False
- # can "always" delete rows from truck dump parent...
+ # can always delete rows from truck dump parent
if batch.is_truck_dump_parent():
-
- # ...but only on desktop!
- if not self.mobile:
- return True
-
- # ...for mobile we only allow deletion of rows which did *not* come
- # from a child batch, i.e. can delete ad-hoc rows only
- # TODO: should have a better way to detect this; for now we rely on
- # the fact that only rows from an invoice or similar would have
- # order quantities
- if not (row.cases_ordered or row.units_ordered):
- return True
+ return True
# can always delete rows from truck dump child
elif batch.is_truck_dump_child():
@@ -466,33 +378,32 @@ class ReceivingBatchView(PurchasingBatchView):
kwargs['batch_vendor_map'] = vmap
return kwargs
- def get_batch_kwargs(self, batch, mobile=False):
- kwargs = super(ReceivingBatchView, self).get_batch_kwargs(batch, mobile=mobile)
- if not mobile:
- batch_type = self.request.POST['batch_type']
- if batch_type == 'from_scratch':
- kwargs.pop('truck_dump_batch', None)
- kwargs.pop('truck_dump_batch_uuid', None)
- elif batch_type == 'truck_dump_children_first':
- kwargs['truck_dump'] = True
- kwargs['truck_dump_children_first'] = True
- kwargs['order_quantities_known'] = True
- # TODO: this makes sense in some cases, but all?
- # (should just omit that field when not relevant)
- kwargs['date_ordered'] = None
- elif batch_type == 'truck_dump_children_last':
- kwargs['truck_dump'] = True
- kwargs['truck_dump_ready'] = True
- # TODO: this makes sense in some cases, but all?
- # (should just omit that field when not relevant)
- kwargs['date_ordered'] = None
- elif batch_type.startswith('truck_dump_child'):
- truck_dump = self.get_instance()
- kwargs['store'] = truck_dump.store
- kwargs['vendor'] = truck_dump.vendor
- kwargs['truck_dump_batch'] = truck_dump
- else:
- raise NotImplementedError
+ def get_batch_kwargs(self, batch, **kwargs):
+ kwargs = super(ReceivingBatchView, self).get_batch_kwargs(batch, **kwargs)
+ batch_type = self.request.POST['batch_type']
+ if batch_type == 'from_scratch':
+ kwargs.pop('truck_dump_batch', None)
+ kwargs.pop('truck_dump_batch_uuid', None)
+ elif batch_type == 'truck_dump_children_first':
+ kwargs['truck_dump'] = True
+ kwargs['truck_dump_children_first'] = True
+ kwargs['order_quantities_known'] = True
+ # TODO: this makes sense in some cases, but all?
+ # (should just omit that field when not relevant)
+ kwargs['date_ordered'] = None
+ elif batch_type == 'truck_dump_children_last':
+ kwargs['truck_dump'] = True
+ kwargs['truck_dump_ready'] = True
+ # TODO: this makes sense in some cases, but all?
+ # (should just omit that field when not relevant)
+ kwargs['date_ordered'] = None
+ elif batch_type.startswith('truck_dump_child'):
+ truck_dump = self.get_instance()
+ kwargs['store'] = truck_dump.store
+ kwargs['vendor'] = truck_dump.vendor
+ kwargs['truck_dump_batch'] = truck_dump
+ else:
+ raise NotImplementedError
return kwargs
def department_for_purchase(self, purchase):
@@ -608,140 +519,6 @@ class ReceivingBatchView(PurchasingBatchView):
url = self.request.route_url('receiving.view', uuid=truck_dump.uuid)
return tags.link_to(text, url)
- def render_mobile_listitem(self, batch, i):
- title = "({}) {} for ${:0,.2f} - {}, {}".format(
- batch.id_str,
- batch.vendor,
- batch.invoice_total or batch.po_total or 0,
- batch.department,
- batch.created_by)
- return title
-
- def make_mobile_row_filters(self):
- """
- Returns a set of filters for the mobile row grid.
- """
- batch = self.get_instance()
- filters = grids.filters.GridFilterSet()
-
- # visible filter options will depend on whether batch came from purchase
- if batch.order_quantities_known:
- value_choices = ['incomplete', 'unexpected', 'damaged', 'expired', 'invalid', 'all']
- default_status = 'incomplete'
- else:
- value_choices = ['received', 'damaged', 'expired', 'invalid', 'all']
- default_status = 'all'
-
- # remove 'expired' filter option if not relevant
- if 'expired' in value_choices and not self.handler.allow_expired_credits():
- value_choices.remove('expired')
-
- filters['status'] = MobileItemStatusFilter('status',
- value_choices=value_choices,
- default_value=default_status)
- return filters
-
- def mobile_create(self):
- """
- Mobile view for creating a new receiving batch
- """
- mode = self.batch_mode
- data = {'mode': mode}
- phase = 1
-
- schema = MobileNewReceivingBatch().bind(session=self.Session())
- form = forms.Form(schema=schema, request=self.request)
- if form.validate(newstyle=True):
- phase = form.validated['phase']
-
- if form.validated['workflow'] == 'from_scratch':
- if not self.allow_from_scratch:
- raise NotImplementedError("Requested workflow not supported: from_scratch")
- batch = self.model_class()
- batch.store = self.rattail_config.get_store(self.Session())
- batch.mode = mode
- batch.vendor = self.Session.query(model.Vendor).get(form.validated['vendor'])
- batch.created_by = self.request.user
- batch.date_received = localtime(self.rattail_config).date()
- kwargs = self.get_batch_kwargs(batch, mobile=True)
- batch = self.handler.make_batch(self.Session(), **kwargs)
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- elif form.validated['workflow'] == 'truck_dump':
- if not self.allow_truck_dump:
- raise NotImplementedError("Requested workflow not supported: truck_dump")
- batch = self.model_class()
- batch.store = self.rattail_config.get_store(self.Session())
- batch.mode = mode
- batch.truck_dump = True
- batch.vendor = self.Session.query(model.Vendor).get(form.validated['vendor'])
- batch.created_by = self.request.user
- batch.date_received = localtime(self.rattail_config).date()
- kwargs = self.get_batch_kwargs(batch, mobile=True)
- batch = self.handler.make_batch(self.Session(), **kwargs)
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- elif form.validated['workflow'] == 'from_po':
- if not self.allow_from_po:
- raise NotImplementedError("Requested workflow not supported: from_po")
-
- vendor = self.Session.query(model.Vendor).get(form.validated['vendor'])
- data['vendor'] = vendor
-
- schema = self.make_mobile_receiving_from_po_schema()
- po_form = forms.Form(schema=schema, request=self.request)
- if phase == 2:
- if po_form.validate(newstyle=True):
- batch = self.model_class()
- batch.store = self.rattail_config.get_store(self.Session())
- batch.mode = mode
- batch.vendor = vendor
- batch.buyer = self.request.user.employee
- batch.created_by = self.request.user
- batch.date_received = localtime(self.rattail_config).date()
- self.assign_purchase_order(batch, po_form)
- kwargs = self.get_batch_kwargs(batch, mobile=True)
- batch = self.handler.make_batch(self.Session(), **kwargs)
- if self.handler.should_populate(batch):
- self.handler.populate(batch)
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- else:
- phase = 2
-
- else:
- raise NotImplementedError("Requested workflow not supported: {}".format(form.validated['workflow']))
-
- data['form'] = form
- data['dform'] = form.make_deform_form()
- data['mode_title'] = self.enum.PURCHASE_BATCH_MODE[mode].capitalize()
- data['phase'] = phase
-
- if phase == 1:
- data['vendor_use_autocomplete'] = self.rattail_config.getbool(
- 'rattail', 'vendor.use_autocomplete', default=True)
- if not data['vendor_use_autocomplete']:
- vendors = self.Session.query(model.Vendor)\
- .order_by(model.Vendor.name)
- options = [(tags.Option(vendor.name, vendor.uuid))
- for vendor in vendors]
- options.insert(0, tags.Option("(please choose)", ''))
- data['vendor_options'] = options
-
- elif phase == 2:
- purchases = self.eligible_purchases(vendor.uuid, mode=mode)
- data['purchases'] = [(p['key'], p['display']) for p in purchases['purchases']]
- data['purchase_order_fieldname'] = self.purchase_order_fieldname
-
- return self.render_to_response('create', data, mobile=True)
-
- def make_mobile_receiving_from_po_schema(self):
- schema = colander.MappingSchema()
- schema.add(colander.SchemaNode(colander.String(),
- name=self.purchase_order_fieldname,
- validator=self.validate_purchase))
- return schema.bind(session=self.Session())
-
@staticmethod
@colander.deferred
def validate_purchase(node, kw):
@@ -766,20 +543,6 @@ class ReceivingBatchView(PurchasingBatchView):
if department:
batch.department_uuid = department.uuid
- def configure_mobile_form(self, f):
- super(ReceivingBatchView, self).configure_mobile_form(f)
- batch = f.model_instance
-
- # truck_dump
- if not self.creating:
- if not batch.is_truck_dump_parent():
- f.remove_field('truck_dump')
-
- # department
- if not self.creating:
- if batch.is_truck_dump_parent():
- f.remove_field('department')
-
def configure_row_grid(self, g):
super(ReceivingBatchView, self).configure_row_grid(g)
g.set_label('department_name', "Department")
@@ -858,7 +621,7 @@ class ReceivingBatchView(PurchasingBatchView):
if row.product and row.product.is_pack_item():
return self.get_row_action_url('transform_unit', row)
- def receive_row(self, mobile=False):
+ def receive_row(self, **kwargs):
"""
Primary desktop view for row-level receiving.
"""
@@ -866,7 +629,6 @@ class ReceivingBatchView(PurchasingBatchView):
# tries to pave the way for shared logic, i.e. where the latter would
# simply invoke this method and return the result. however we're not
# there yet...for now it's only tested for desktop
- self.mobile = mobile
self.viewing = True
row = self.get_row_instance()
batch = row.batch
@@ -890,23 +652,14 @@ class ReceivingBatchView(PurchasingBatchView):
'quick_receive_all': False,
}
- if mobile:
- context['quick_receive'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive',
- default=True)
- if batch.order_quantities_known:
- context['quick_receive_all'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive_all',
- default=False)
-
schema = ReceiveRowForm().bind(session=self.Session())
form = forms.Form(schema=schema, request=self.request)
- form.cancel_url = self.get_row_action_url('view', row, mobile=mobile)
+ form.cancel_url = self.get_row_action_url('view', row)
form.set_widget('mode', forms.widgets.JQuerySelectWidget(values=[(m, m) for m in possible_modes]))
form.set_widget('quantity', forms.widgets.CasesUnitsWidget(amount_required=True,
one_amount_only=True))
form.set_type('expiration_date', 'date_jquery')
-
- if not mobile:
- form.remove_field('quick_receive')
+ form.remove_field('quick_receive')
if form.validate(newstyle=True):
@@ -921,20 +674,17 @@ class ReceivingBatchView(PurchasingBatchView):
# whether or not it was 'CS' since the unit_uom can vary
# TODO: should this be done for desktop too somehow?
sticky_case = None
- if mobile and not form.validated['quick_receive']:
- cases = form.validated['cases']
- units = form.validated['units']
- if cases and not units:
- sticky_case = True
- elif units and not cases:
- sticky_case = False
+ # if mobile and not form.validated['quick_receive']:
+ # cases = form.validated['cases']
+ # units = form.validated['units']
+ # if cases and not units:
+ # sticky_case = True
+ # elif units and not cases:
+ # sticky_case = False
if sticky_case is not None:
self.request.session['tailbone.mobile.receiving.sticky_uom_is_case'] = sticky_case
- if mobile:
- return self.redirect(self.get_action_url('view', batch, mobile=True))
- else:
- return self.redirect(self.get_row_action_url('view', row))
+ return self.redirect(self.get_row_action_url('view', row))
# unit_uom can vary by product
context['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
@@ -968,9 +718,9 @@ class ReceivingBatchView(PurchasingBatchView):
# effective uom can vary in a few ways...the basic default is 'CS' if
# self.default_uom_is_case is true, otherwise whatever unit_uom is.
sticky_case = None
- if mobile:
- # TODO: should do this for desktop also, but rename the session variable
- sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
+ # if mobile:
+ # # TODO: should do this for desktop also, but rename the session variable
+ # sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
if sticky_case is None:
context['uom'] = 'CS' if self.default_uom_is_case else context['unit_uom']
elif sticky_case:
@@ -980,37 +730,37 @@ class ReceivingBatchView(PurchasingBatchView):
if context['uom'] == 'CS' and row.units_ordered and not row.cases_ordered:
context['uom'] = context['unit_uom']
- # TODO: should do this for desktop in addition to mobile?
- if mobile and batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
- warn = True
- if batch.is_truck_dump_parent() and row.product:
- uuids = [child.uuid for child in batch.truck_dump_children]
- if uuids:
- count = self.Session.query(model.PurchaseBatchRow)\
- .filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
- .filter(model.PurchaseBatchRow.product == row.product)\
- .count()
- if count:
- warn = False
- if warn:
- self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
+ # # TODO: should do this for desktop in addition to mobile?
+ # if mobile and batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
+ # warn = True
+ # if batch.is_truck_dump_parent() and row.product:
+ # uuids = [child.uuid for child in batch.truck_dump_children]
+ # if uuids:
+ # count = self.Session.query(model.PurchaseBatchRow)\
+ # .filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
+ # .filter(model.PurchaseBatchRow.product == row.product)\
+ # .count()
+ # if count:
+ # warn = False
+ # if warn:
+ # self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
- # TODO: should do this for desktop in addition to mobile?
- if mobile:
- # maybe alert user if they've already received some of this product
- alert_received = self.rattail_config.getbool('tailbone', 'receiving.alert_already_received',
- default=False)
- if alert_received:
- if self.handler.get_units_confirmed(row):
- msg = "You have already received some of this product; last update was {}.".format(
- humanize.naturaltime(make_utc() - row.modified))
- self.request.session.flash(msg, 'receiving-warning')
+ # # TODO: should do this for desktop in addition to mobile?
+ # if mobile:
+ # # maybe alert user if they've already received some of this product
+ # alert_received = self.rattail_config.getbool('tailbone', 'receiving.alert_already_received',
+ # default=False)
+ # if alert_received:
+ # if self.handler.get_units_confirmed(row):
+ # msg = "You have already received some of this product; last update was {}.".format(
+ # humanize.naturaltime(make_utc() - row.modified))
+ # self.request.session.flash(msg, 'receiving-warning')
context['form'] = form
context['dform'] = form.make_deform_form()
- context['parent_url'] = self.get_action_url('view', batch, mobile=mobile)
+ context['parent_url'] = self.get_action_url('view', batch)
context['parent_title'] = self.get_instance_title(batch)
- return self.render_to_response('receive_row', context, mobile=mobile)
+ return self.render_to_response('receive_row', context)
def declare_credit(self):
"""
@@ -1418,8 +1168,8 @@ class ReceivingBatchView(PurchasingBatchView):
self.Session.flush()
return row
- def redirect_after_edit_row(self, row, mobile=False):
- return self.redirect(self.get_row_action_url('view', row, mobile=mobile))
+ def redirect_after_edit_row(self, row, **kwargs):
+ return self.redirect(self.get_row_action_url('view', row))
def update_row_cost(self):
"""
@@ -1463,287 +1213,16 @@ class ReceivingBatchView(PurchasingBatchView):
},
}
- def render_mobile_row_listitem(self, row, i):
- key = self.render_product_key_value(row)
- description = row.product.full_description if row.product else row.description
- return "({}) {}".format(key, description)
-
- def make_mobile_row_grid_kwargs(self, **kwargs):
- kwargs = super(ReceivingBatchView, self).make_mobile_row_grid_kwargs(**kwargs)
-
- # use custom `receive_row` instead of `view_row`
- # TODO: should still use `view_row` in some cases? e.g. executed batch
- kwargs['url'] = lambda obj: self.get_row_action_url('receive', obj, mobile=True)
-
- return kwargs
-
def save_quick_row_form(self, form):
batch = self.get_instance()
entry = form.validated['quick_entry']
row = self.handler.quick_entry(self.Session(), batch, entry)
return row
- def redirect_after_quick_row(self, row, mobile=False):
- if mobile:
- return self.redirect(self.get_row_action_url('receive', row, mobile=mobile))
- return super(ReceivingBatchView, self).redirect_after_quick_row(row, mobile=mobile)
-
def get_row_image_url(self, row):
if self.rattail_config.getbool('rattail.batch', 'purchase.mobile_images', default=True):
return pod.get_image_url(self.rattail_config, row.upc)
- def get_mobile_data(self, session=None):
- query = super(ReceivingBatchView, self).get_mobile_data(session=session)
-
- # do not expose truck dump child batches on mobile
- # TODO: is there any case where we *would* want to?
- query = query.filter(model.PurchaseBatch.truck_dump_batch == None)
-
- return query
-
- def mobile_view_row(self):
- """
- Mobile view for receiving batch row items. Note that this also handles
- updating a row.
- """
- self.mobile = True
- self.viewing = True
- row = self.get_row_instance()
- batch = row.batch
- permission_prefix = self.get_permission_prefix()
- form = self.make_mobile_row_form(row)
- context = {
- 'row': row,
- 'batch': batch,
- 'parent_instance': batch,
- 'instance': row,
- 'instance_title': self.get_row_instance_title(row),
- 'parent_model_title': self.get_model_title(),
- 'product_image_url': self.get_row_image_url(row),
- 'form': form,
- 'allow_expired': self.handler.allow_expired_credits(),
- 'allow_cases': self.handler.allow_cases(),
- 'quick_receive': False,
- 'quick_receive_all': False,
- }
-
- context['quick_receive'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive',
- default=True)
- if batch.order_quantities_known:
- context['quick_receive_all'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive_all',
- default=False)
-
- if self.request.has_perm('{}.create_row'.format(permission_prefix)):
- schema = MobileReceivingForm().bind(session=self.Session())
- update_form = forms.Form(schema=schema, request=self.request)
- # TODO: this seems hacky, but avoids "complex" date value parsing
- update_form.set_widget('expiration_date', dfwidget.TextInputWidget())
- if update_form.validate(newstyle=True):
- row = self.Session.query(model.PurchaseBatchRow).get(update_form.validated['row'])
- mode = update_form.validated['mode']
- cases = update_form.validated['cases']
- units = update_form.validated['units']
-
- # handler takes care of the row receiving logic for us
- kwargs = dict(update_form.validated)
- del kwargs['row']
- self.handler.receive_row(row, **kwargs)
-
- # keep track of last-used uom, although we just track
- # whether or not it was 'CS' since the unit_uom can vary
- sticky_case = None
- if not update_form.validated['quick_receive']:
- if cases and not units:
- sticky_case = True
- elif units and not cases:
- sticky_case = False
- if sticky_case is not None:
- self.request.session['tailbone.mobile.receiving.sticky_uom_is_case'] = sticky_case
-
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- # unit_uom can vary by product
- context['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
-
- if context['quick_receive'] and context['quick_receive_all']:
- if context['allow_cases']:
- context['quick_receive_uom'] = 'CS'
- raise NotImplementedError("TODO: add CS support for quick_receive_all")
- else:
- context['quick_receive_uom'] = context['unit_uom']
- accounted_for = self.handler.get_units_accounted_for(row)
- remainder = self.handler.get_units_ordered(row) - accounted_for
-
- if accounted_for:
- # some product accounted for; button should receive "remainder" only
- if remainder:
- remainder = pretty_quantity(remainder)
- context['quick_receive_quantity'] = remainder
- context['quick_receive_text'] = "Receive Remainder ({} {})".format(remainder, context['unit_uom'])
- else:
- # unless there is no remainder, in which case disable it
- context['quick_receive'] = False
-
- else: # nothing yet accounted for, button should receive "all"
- if not remainder:
- raise ValueError("why is remainder empty?")
- remainder = pretty_quantity(remainder)
- context['quick_receive_quantity'] = remainder
- context['quick_receive_text'] = "Receive ALL ({} {})".format(remainder, context['unit_uom'])
-
- # effective uom can vary in a few ways...the basic default is 'CS' if
- # self.default_uom_is_case is true, otherwise whatever unit_uom is.
- sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
- if sticky_case is None:
- context['uom'] = 'CS' if self.default_uom_is_case else context['unit_uom']
- elif sticky_case:
- context['uom'] = 'CS'
- else:
- context['uom'] = context['unit_uom']
- if context['uom'] == 'CS' and row.units_ordered and not row.cases_ordered:
- context['uom'] = context['unit_uom']
-
- if batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
- warn = True
- if batch.is_truck_dump_parent() and row.product:
- uuids = [child.uuid for child in batch.truck_dump_children]
- if uuids:
- count = self.Session.query(model.PurchaseBatchRow)\
- .filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
- .filter(model.PurchaseBatchRow.product == row.product)\
- .count()
- if count:
- warn = False
- if warn:
- self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
- return self.render_to_response('view_row', context, mobile=True)
-
- def mobile_receive_row(self):
- """
- Mobile view for row-level receiving.
- """
- self.mobile = True
- self.viewing = True
- row = self.get_row_instance()
- batch = row.batch
- permission_prefix = self.get_permission_prefix()
- form = self.make_mobile_row_form(row)
- context = {
- 'row': row,
- 'batch': batch,
- 'parent_instance': batch,
- 'instance': row,
- 'instance_title': self.get_row_instance_title(row),
- 'parent_model_title': self.get_model_title(),
- 'product_image_url': self.get_row_image_url(row),
- 'form': form,
- 'allow_expired': self.handler.allow_expired_credits(),
- 'allow_cases': self.handler.allow_cases(),
- 'quick_receive': False,
- 'quick_receive_all': False,
- }
-
- context['quick_receive'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive',
- default=True)
- if batch.order_quantities_known:
- context['quick_receive_all'] = self.rattail_config.getbool('rattail.batch', 'purchase.mobile_quick_receive_all',
- default=False)
-
- if self.request.has_perm('{}.create_row'.format(permission_prefix)):
- schema = MobileReceivingForm().bind(session=self.Session())
- update_form = forms.Form(schema=schema, request=self.request)
- # TODO: this seems hacky, but avoids "complex" date value parsing
- update_form.set_widget('expiration_date', dfwidget.TextInputWidget())
- if update_form.validate(newstyle=True):
- row = self.Session.query(model.PurchaseBatchRow).get(update_form.validated['row'])
- mode = update_form.validated['mode']
- cases = update_form.validated['cases']
- units = update_form.validated['units']
-
- # handler takes care of the row receiving logic for us
- kwargs = dict(update_form.validated)
- del kwargs['row']
- self.handler.receive_row(row, **kwargs)
-
- # keep track of last-used uom, although we just track
- # whether or not it was 'CS' since the unit_uom can vary
- sticky_case = None
- if not update_form.validated['quick_receive']:
- if cases and not units:
- sticky_case = True
- elif units and not cases:
- sticky_case = False
- if sticky_case is not None:
- self.request.session['tailbone.mobile.receiving.sticky_uom_is_case'] = sticky_case
-
- return self.redirect(self.get_action_url('view', batch, mobile=True))
-
- # unit_uom can vary by product
- context['unit_uom'] = 'LB' if row.product and row.product.weighed else 'EA'
-
- if context['quick_receive'] and context['quick_receive_all']:
- if context['allow_cases']:
- context['quick_receive_uom'] = 'CS'
- raise NotImplementedError("TODO: add CS support for quick_receive_all")
- else:
- context['quick_receive_uom'] = context['unit_uom']
- accounted_for = self.handler.get_units_accounted_for(row)
- remainder = self.handler.get_units_ordered(row) - accounted_for
-
- if accounted_for:
- # some product accounted for; button should receive "remainder" only
- if remainder:
- remainder = pretty_quantity(remainder)
- context['quick_receive_quantity'] = remainder
- context['quick_receive_text'] = "Receive Remainder ({} {})".format(remainder, context['unit_uom'])
- else:
- # unless there is no remainder, in which case disable it
- context['quick_receive'] = False
-
- else: # nothing yet accounted for, button should receive "all"
- if not remainder:
- raise ValueError("why is remainder empty?")
- remainder = pretty_quantity(remainder)
- context['quick_receive_quantity'] = remainder
- context['quick_receive_text'] = "Receive ALL ({} {})".format(remainder, context['unit_uom'])
-
- # effective uom can vary in a few ways...the basic default is 'CS' if
- # self.default_uom_is_case is true, otherwise whatever unit_uom is.
- sticky_case = self.request.session.get('tailbone.mobile.receiving.sticky_uom_is_case')
- if sticky_case is None:
- context['uom'] = 'CS' if self.default_uom_is_case else context['unit_uom']
- elif sticky_case:
- context['uom'] = 'CS'
- else:
- context['uom'] = context['unit_uom']
- if context['uom'] == 'CS' and row.units_ordered and not row.cases_ordered:
- context['uom'] = context['unit_uom']
-
- if batch.order_quantities_known and not row.cases_ordered and not row.units_ordered:
- warn = True
- if batch.is_truck_dump_parent() and row.product:
- uuids = [child.uuid for child in batch.truck_dump_children]
- if uuids:
- count = self.Session.query(model.PurchaseBatchRow)\
- .filter(model.PurchaseBatchRow.batch_uuid.in_(uuids))\
- .filter(model.PurchaseBatchRow.product == row.product)\
- .count()
- if count:
- warn = False
- if warn:
- self.request.session.flash("This item was NOT on the original purchase order.", 'receiving-warning')
-
- # maybe alert user if they've already received some of this product
- alert_received = self.rattail_config.getbool('tailbone', 'receiving.alert_already_received',
- default=False)
- if alert_received:
- if self.handler.get_units_confirmed(row):
- msg = "You have already received some of this product; last update was {}.".format(
- humanize.naturaltime(make_utc() - row.modified))
- self.request.session.flash(msg, 'receiving-warning')
-
- return self.render_to_response('receive_row', context, mobile=True)
-
def auto_receive(self):
"""
View which can "auto-receive" all items in the batch. Meant only as a
@@ -1804,16 +1283,11 @@ class ReceivingBatchView(PurchasingBatchView):
instance_url_prefix = cls.get_instance_url_prefix()
model_key = cls.get_model_key()
permission_prefix = cls.get_permission_prefix()
- legacy_mobile = cls.legacy_mobile_enabled(rattail_config)
# row-level receiving
config.add_route('{}.receive_row'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/receive'.format(url_prefix))
config.add_view(cls, attr='receive_row', route_name='{}.receive_row'.format(route_prefix),
permission='{}.edit_row'.format(permission_prefix))
- if legacy_mobile:
- config.add_route('mobile.{}.receive_row'.format(route_prefix), '/mobile{}/{{uuid}}/rows/{{row_uuid}}/receive'.format(url_prefix))
- config.add_view(cls, attr='mobile_receive_row', route_name='mobile.{}.receive_row'.format(route_prefix),
- permission='{}.edit_row'.format(permission_prefix))
# declare credit for row
config.add_route('{}.declare_credit'.format(route_prefix), '{}/{{uuid}}/rows/{{row_uuid}}/declare-credit'.format(url_prefix))
@@ -1854,40 +1328,6 @@ class ReceivingBatchView(PurchasingBatchView):
cls._defaults(config)
-# TODO: this is a stopgap measure to fix an obvious bug, which exists when the
-# session is not provided by the view at runtime (i.e. when it was instead
-# being provided by the type instance, which was created upon app startup).
-@colander.deferred
-def valid_vendor(node, kw):
- session = kw['session']
- def validate(node, value):
- vendor = session.query(model.Vendor).get(value)
- if not vendor:
- raise colander.Invalid(node, "Vendor not found")
- return vendor.uuid
- return validate
-
-
-class MobileNewReceivingBatch(colander.MappingSchema):
-
- vendor = colander.SchemaNode(colander.String(),
- validator=valid_vendor)
-
- workflow = colander.SchemaNode(colander.String(),
- validator=colander.OneOf([
- 'from_po',
- 'from_scratch',
- 'truck_dump',
- ]))
-
- phase = colander.SchemaNode(colander.Int())
-
-
-class MobileNewReceivingFromPO(colander.MappingSchema):
-
- purchase = colander.SchemaNode(colander.String())
-
-
class ReceiveRowForm(colander.MappingSchema):
mode = colander.SchemaNode(colander.String(),