diff --git a/tailbone/views/__init__.py b/tailbone/views/__init__.py
index f2b4fcdc..8d5c680b 100644
--- a/tailbone/views/__init__.py
+++ b/tailbone/views/__init__.py
@@ -28,9 +28,6 @@ from __future__ import unicode_literals, absolute_import
from .core import View
from .master import MasterView
-from .master2 import MasterView2
-from .master3 import MasterView3
-from .master4 import MasterView4
# TODO: deprecate / remove some of this
from .autocomplete import AutocompleteView
diff --git a/tailbone/views/batch/__init__.py b/tailbone/views/batch/__init__.py
index 9a053ca2..e4b61802 100644
--- a/tailbone/views/batch/__init__.py
+++ b/tailbone/views/batch/__init__.py
@@ -27,6 +27,3 @@ Views for batches
from __future__ import unicode_literals, absolute_import
from .core import BatchMasterView, FileBatchMasterView
-from .core2 import BatchMasterView2, FileBatchMasterView2
-from .core3 import BatchMasterView3, FileBatchMasterView3
-from .core4 import BatchMasterView4, FileBatchMasterView4
diff --git a/tailbone/views/batch/core.py b/tailbone/views/batch/core.py
index ab28c429..2d066ae8 100644
--- a/tailbone/views/batch/core.py
+++ b/tailbone/views/batch/core.py
@@ -73,6 +73,33 @@ class BatchMasterView(MasterView):
mobile_rows_viewable = True
has_worksheet = False
+ grid_columns = [
+ 'id',
+ 'description',
+ 'created',
+ 'created_by',
+ 'rowcount',
+ # 'status_code',
+ # 'complete',
+ 'executed',
+ 'executed_by',
+ ]
+
+ form_fields = [
+ 'id',
+ 'created',
+ 'created_by',
+ 'rowcount',
+ 'status_code',
+ 'executed',
+ 'executed_by',
+ 'purge',
+ ]
+
+ row_labels = {
+ 'status_code': "Status",
+ }
+
def __init__(self, request):
super(BatchMasterView, self).__init__(request)
self.handler = self.get_handler()
@@ -114,6 +141,44 @@ class BatchMasterView(MasterView):
def allow_worksheet(self, batch):
return not batch.executed and not batch.complete
+ def configure_grid(self, g):
+ super(BatchMasterView, self).configure_grid(g)
+
+ g.joiners['created_by'] = lambda q: q.join(model.User, model.User.uuid == self.model_class.created_by_uuid)
+ g.joiners['executed_by'] = lambda q: q.outerjoin(model.User, model.User.uuid == self.model_class.executed_by_uuid)
+
+ g.filters['executed'].default_active = True
+ g.filters['executed'].default_verb = 'is_null'
+
+ # TODO: not sure this todo is still relevant?
+ # TODO: in some cases grid has no sorters yet..e.g. when building query for bulk-delete
+ # if hasattr(g, 'sorters'):
+ g.sorters['created_by'] = g.make_sorter(model.User.username)
+ g.sorters['executed_by'] = g.make_sorter(model.User.username)
+
+ g.set_sort_defaults('id', 'desc')
+
+ g.set_enum('status_code', self.model_class.STATUS)
+
+ g.set_type('created', 'datetime')
+ g.set_type('executed', 'datetime')
+
+ g.set_renderer('id', self.render_batch_id)
+
+ g.set_link('id')
+ g.set_link('description')
+ g.set_link('created')
+ g.set_link('executed')
+
+ g.set_label('id', "Batch ID")
+ g.set_label('created_by', "Created by")
+ g.set_label('rowcount', "Rows")
+ g.set_label('status_code', "Status")
+ g.set_label('executed_by', "Executed by")
+
+ def render_batch_id(self, batch, column):
+ return batch.id_str
+
def template_kwargs_index(self, **kwargs):
kwargs['execute_enabled'] = self.instance_executable(None)
if kwargs['execute_enabled'] and self.has_execution_options():
@@ -151,6 +216,82 @@ class BatchMasterView(MasterView):
filters['status'] = MobileBatchStatusFilter(self.model_class, 'status', default_value='pending')
return filters
+ def configure_form(self, f):
+ super(BatchMasterView, self).configure_form(f)
+
+ # id
+ f.set_readonly('id')
+ f.set_renderer('id', self.render_batch_id)
+ f.set_label('id', "Batch ID")
+
+ # created
+ f.set_readonly('created')
+ f.set_readonly('created_by')
+ f.set_renderer('created_by', self.render_user)
+ f.set_label('created_by', "Created by")
+
+ # cognized
+ f.set_renderer('cognized_by', self.render_user)
+ f.set_label('cognized_by', "Cognized by")
+
+ # row count
+ f.set_readonly('rowcount')
+ f.set_label('rowcount', "Row Count")
+
+ # status_code
+ f.set_readonly('status_code')
+ f.set_renderer('status_code', self.make_status_renderer(self.model_class.STATUS))
+ f.set_label('status_code', "Status")
+
+ # executed
+ f.set_readonly('executed')
+ f.set_readonly('executed_by')
+ f.set_renderer('executed_by', self.render_user)
+ f.set_label('executed_by', "Executed by")
+
+ # notes
+ f.set_type('notes', 'text')
+
+ # if self.creating and self.request.user:
+ # batch = fs.model
+ # batch.created_by_uuid = self.request.user.uuid
+
+ if self.creating:
+ f.remove_fields('id',
+ 'rowcount',
+ 'created',
+ 'created_by',
+ 'cognized',
+ 'cognized_by',
+ 'executed',
+ 'executed_by',
+ 'purge')
+
+ else: # not creating
+ batch = self.get_instance()
+ if not batch.executed:
+ f.remove_fields('executed',
+ 'executed_by')
+
+ def make_status_renderer(self, enum):
+ def render_status(batch, field):
+ value = batch.status_code
+ if value is None:
+ return ""
+ status_code_text = enum.get(value, six.text_type(value))
+ if batch.status_text:
+ return HTML.tag('span', title=batch.status_text, c=status_code_text)
+ return status_code_text
+ return render_status
+
+ def render_user(self, batch, field):
+ user = getattr(batch, field)
+ if not user:
+ return ""
+ title = six.text_type(user)
+ url = self.request.route_url('users.view', uuid=user.uuid)
+ return tags.link_to(title, url)
+
def _preconfigure_fieldset(self, fs):
"""
Apply some commonly-useful pre-configuration to the main batch
@@ -240,67 +381,93 @@ class BatchMasterView(MasterView):
download_url=download_url)
fs.append(fa.Field(name, **kwargs))
+ 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):
self.before_create(form)
- with Session.no_autoflush:
+ session = self.Session()
+ with session.no_autoflush:
# transfer form data to batch instance
- form.fieldset.sync()
- batch = form.fieldset.model
+ batch = self.objectify(form, self.form_deserialized)
# current user is batch creator
batch.created_by = self.request.user or self.late_login_user()
- # destroy initial batch and re-make using handler
+ # obtain kwargs for making batch via handler, below
kwargs = self.get_batch_kwargs(batch)
- Session.expunge(batch)
- batch = self.handler.make_batch(Session(), **kwargs)
- Session.flush()
+ # TODO: this needs work yet surely...
+ if 'filename' in form.schema:
+ filedict = kwargs.pop('filename', None)
+ filepath = None
+ if filedict:
+ kwargs['filename'] = '' # null not allowed
+ tempdir = tempfile.mkdtemp()
+ filepath = os.path.join(tempdir, filedict['filename'])
+ tmpinfo = form.deform_form['filename'].widget.tmpstore.get(filedict['uid'])
+ tmpdata = tmpinfo['fp'].read()
+ with open(filepath, 'wb') as f:
+ f.write(tmpdata)
+
+ # TODO: is this still necessary with colander?
+ # destroy initial batch and re-make using handler
+ # if batch in self.Session:
+ # self.Session.expunge(batch)
+ batch = self.handler.make_batch(session, **kwargs)
+
+ self.Session.flush()
# TODO: this needs work yet surely...
# if batch has input data file, let handler properly establish that
- filename = getattr(batch, 'filename', None)
- if filename:
- path = os.path.join(self.upload_dir, filename)
- if os.path.exists(path):
- self.handler.set_input_file(batch, path)
- os.remove(path)
+ if 'filename' in form.schema:
+ if filedict:
+ self.handler.set_input_file(batch, filepath)
+ os.remove(filepath)
+ os.rmdir(tempdir)
- # return this object to replace the original
return batch
- # TODO: this is a totaly copy of save_create_form()
def save_mobile_create_form(self, form):
self.before_create(form)
-
- with Session.no_autoflush:
+ session = self.Session()
+ with session.no_autoflush:
# transfer form data to batch instance
- form.fieldset.sync()
- batch = form.fieldset.model
+ batch = self.objectify(form, self.form_deserialized)
# current user is batch creator
- batch.created_by = self.request.user or self.late_login_user()
+ 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)
- Session.expunge(batch)
- batch = self.handler.make_batch(Session(), **kwargs)
+ if batch in session:
+ session.expunge(batch)
+ batch = self.handler.make_batch(session, **kwargs)
- Session.flush()
-
- # TODO: this needs work yet surely...
- # if batch has input data file, let handler properly establish that
- filename = getattr(batch, 'filename', None)
- if filename:
- path = os.path.join(self.upload_dir, filename)
- if os.path.exists(path):
- self.handler.set_input_file(batch, path)
- os.remove(path)
-
- # return this object to replace the original
+ session.flush()
return batch
def get_batch_kwargs(self, batch, mobile=False):
@@ -367,6 +534,35 @@ class BatchMasterView(MasterView):
"""
return not batch.executed
+ def configure_row_grid(self, g):
+ super(BatchMasterView, self).configure_row_grid(g)
+
+ if 'status_code' in g.filters:
+ g.filters['status_code'].set_value_renderer(grids.filters.EnumValueRenderer(self.model_row_class.STATUS))
+
+ g.set_sort_defaults('sequence')
+
+ if self.model_row_class:
+ g.set_enum('status_code', self.model_row_class.STATUS)
+
+ g.set_renderer('status_code', self.render_row_status)
+
+ g.set_label('sequence', "Seq.")
+ g.set_label('status_code', "Status")
+ g.set_label('item_id', "Item ID")
+
+ def get_row_status_enum(self):
+ return self.model_row_class.STATUS
+
+ def render_row_status(self, row, column):
+ code = row.status_code
+ if code is None:
+ return ""
+ text = self.get_row_status_enum().get(code, six.text_type(code))
+ if row.status_text:
+ return HTML.tag('span', title=row.status_text, c=text)
+ return text
+
def create_row(self):
"""
Only allow creating a new row if the batch hasn't yet been executed.
@@ -387,14 +583,42 @@ class BatchMasterView(MasterView):
return self.redirect(self.get_action_url('view', batch, mobile=True))
return super(BatchMasterView, self).mobile_create_row()
- def before_create_row(self, form):
+ def save_create_row_form(self, form):
batch = self.get_instance()
- row = form.fieldset.model
+ row = self.objectify(form, self.form_deserialized)
self.handler.add_row(batch, row)
+ self.Session.flush()
+ return row
def after_create_row(self, row):
self.handler.refresh_row(row)
+ def configure_row_form(self, f):
+ super(BatchMasterView, self).configure_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 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:
permission_prefix = self.get_permission_prefix()
@@ -1007,6 +1231,26 @@ class FileBatchMasterView(BatchMasterView):
os.makedirs(uploads)
return uploads
+ def configure_form(self, f):
+ super(FileBatchMasterView, self).configure_form(f)
+
+ # filename
+ f.set_renderer('filename', self.render_filename)
+ f.set_label('filename', "Data File")
+ if self.editing:
+ f.set_readonly('filename')
+
+ if self.creating:
+ if 'filename' not in f.fields:
+ f.fields.insert(0, 'filename')
+ tmpstore = SessionFileUploadTempStore(self.request)
+ f.set_node('filename', colander.SchemaNode(deform.FileData(), widget=dfwidget.FileUploadWidget(tmpstore)))
+
+ def render_filename(self, batch, field):
+ path = batch.filepath(self.rattail_config, filename=batch.filename)
+ url = self.get_action_url('download', batch)
+ return self.render_file_field(path, url)
+
def _preconfigure_fieldset(self, fs):
super(FileBatchMasterView, self)._preconfigure_fieldset(fs)
fs.filename.set(label="Data File", renderer=FileFieldRenderer.new(self))
diff --git a/tailbone/views/batch/core2.py b/tailbone/views/batch/core2.py
deleted file mode 100644
index 3a70366b..00000000
--- a/tailbone/views/batch/core2.py
+++ /dev/null
@@ -1,129 +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 .
-#
-################################################################################
-"""
-Base views for maintaining batches
-"""
-
-from __future__ import unicode_literals, absolute_import
-
-import six
-
-from rattail.db import model
-
-from webhelpers2.html import HTML
-
-from tailbone import grids
-from tailbone.views import MasterView2
-from tailbone.views.batch import BatchMasterView, FileBatchMasterView
-from tailbone.views.batch.core import MobileBatchStatusFilter
-
-
-class BatchMasterView2(MasterView2, BatchMasterView):
- """
- Base class for all "batch master" views
- """
-
- grid_columns = [
- 'id',
- 'description',
- 'created',
- 'created_by',
- 'rowcount',
- # 'status_code',
- # 'complete',
- 'executed',
- 'executed_by',
- ]
-
- def configure_grid(self, g):
- super(BatchMasterView2, self).configure_grid(g)
-
- g.joiners['created_by'] = lambda q: q.join(model.User, model.User.uuid == self.model_class.created_by_uuid)
- g.joiners['executed_by'] = lambda q: q.outerjoin(model.User, model.User.uuid == self.model_class.executed_by_uuid)
-
- g.filters['executed'].default_active = True
- g.filters['executed'].default_verb = 'is_null'
-
- # TODO: not sure this todo is still relevant?
- # TODO: in some cases grid has no sorters yet..e.g. when building query for bulk-delete
- # if hasattr(g, 'sorters'):
- g.sorters['created_by'] = g.make_sorter(model.User.username)
- g.sorters['executed_by'] = g.make_sorter(model.User.username)
-
- g.set_sort_defaults('id', 'desc')
-
- g.set_enum('status_code', self.model_class.STATUS)
-
- g.set_type('created', 'datetime')
- g.set_type('executed', 'datetime')
-
- g.set_renderer('id', self.render_batch_id)
-
- g.set_link('id')
- g.set_link('description')
- g.set_link('created')
- g.set_link('executed')
-
- g.set_label('id', "Batch ID")
- g.set_label('created_by', "Created by")
- g.set_label('rowcount', "Rows")
- g.set_label('status_code', "Status")
- g.set_label('executed_by', "Executed by")
-
- def render_batch_id(self, batch, column):
- return batch.id_str
-
- def configure_row_grid(self, g):
- super(BatchMasterView2, self).configure_row_grid(g)
-
- if 'status_code' in g.filters:
- g.filters['status_code'].set_value_renderer(grids.filters.EnumValueRenderer(self.model_row_class.STATUS))
-
- g.set_sort_defaults('sequence')
-
- if self.model_row_class:
- g.set_enum('status_code', self.model_row_class.STATUS)
-
- g.set_renderer('status_code', self.render_row_status)
-
- g.set_label('sequence', "Seq.")
- g.set_label('status_code', "Status")
- g.set_label('item_id', "Item ID")
-
- def get_row_status_enum(self):
- return self.model_row_class.STATUS
-
- def render_row_status(self, row, column):
- code = row.status_code
- if code is None:
- return ""
- text = self.get_row_status_enum().get(code, six.text_type(code))
- if row.status_text:
- return HTML.tag('span', title=row.status_text, c=text)
- return text
-
-
-class FileBatchMasterView2(BatchMasterView2, FileBatchMasterView):
- """
- Base class for all file-based "batch master" views
- """
diff --git a/tailbone/views/batch/core3.py b/tailbone/views/batch/core3.py
deleted file mode 100644
index 92dedf26..00000000
--- a/tailbone/views/batch/core3.py
+++ /dev/null
@@ -1,205 +0,0 @@
-# -*- coding: utf-8; -*-
-################################################################################
-#
-# Rattail -- Retail Software Framework
-# Copyright © 2010-2018 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 .
-#
-################################################################################
-"""
-Base views for maintaining batches
-"""
-
-from __future__ import unicode_literals, absolute_import
-
-import os
-import tempfile
-
-import six
-import colander
-import deform
-from deform import widget as dfwidget
-from pyramid_deform import SessionFileUploadTempStore
-from webhelpers2.html import tags
-
-from tailbone.views import MasterView3
-from tailbone.views.batch import BatchMasterView2, FileBatchMasterView2
-
-
-class BatchMasterView3(MasterView3, BatchMasterView2):
- """
- Base class for all "batch master" views
- """
-
- form_fields = [
- 'id',
- 'created',
- 'created_by',
- 'rowcount',
- 'status_code',
- 'executed',
- 'executed_by',
- 'purge',
- ]
-
- def configure_form(self, f):
- super(BatchMasterView3, self).configure_form(f)
-
- # id
- f.set_readonly('id')
- f.set_renderer('id', self.render_batch_id)
- f.set_label('id', "Batch ID")
-
- # created
- f.set_readonly('created')
- f.set_readonly('created_by')
- f.set_renderer('created_by', self.render_user)
- f.set_label('created_by', "Created by")
-
- # cognized
- f.set_renderer('cognized_by', self.render_user)
- f.set_label('cognized_by', "Cognized by")
-
- # row count
- f.set_readonly('rowcount')
- f.set_label('rowcount', "Row Count")
-
- # status_code
- f.set_readonly('status_code')
- f.set_renderer('status_code', self.make_status_renderer(self.model_class.STATUS))
- f.set_label('status_code', "Status")
-
- # executed
- f.set_readonly('executed')
- f.set_readonly('executed_by')
- f.set_renderer('executed_by', self.render_user)
- f.set_label('executed_by', "Executed by")
-
- # notes
- f.set_type('notes', 'text')
-
- # if self.creating and self.request.user:
- # batch = fs.model
- # batch.created_by_uuid = self.request.user.uuid
-
- if self.creating:
- f.remove_fields('id',
- 'rowcount',
- 'created',
- 'created_by',
- 'cognized',
- 'cognized_by',
- 'executed',
- 'executed_by',
- 'purge')
-
- else: # not creating
- batch = self.get_instance()
- if not batch.executed:
- f.remove_fields('executed',
- 'executed_by')
-
- def save_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 or self.late_login_user()
-
- # obtain kwargs for making batch via handler, below
- kwargs = self.get_batch_kwargs(batch)
-
- # TODO: this needs work yet surely...
- if 'filename' in form.schema:
- filedict = kwargs.pop('filename', None)
- filepath = None
- if filedict:
- kwargs['filename'] = '' # null not allowed
- tempdir = tempfile.mkdtemp()
- filepath = os.path.join(tempdir, filedict['filename'])
- tmpinfo = form.deform_form['filename'].widget.tmpstore.get(filedict['uid'])
- tmpdata = tmpinfo['fp'].read()
- with open(filepath, 'wb') as f:
- f.write(tmpdata)
-
- # TODO: is this still necessary with colander?
- # destroy initial batch and re-make using handler
- # if batch in self.Session:
- # self.Session.expunge(batch)
- batch = self.handler.make_batch(session, **kwargs)
-
- self.Session.flush()
-
- # TODO: this needs work yet surely...
- # if batch has input data file, let handler properly establish that
- if 'filename' in form.schema:
- if filedict:
- self.handler.set_input_file(batch, filepath)
- os.remove(filepath)
- os.rmdir(tempdir)
-
- return batch
-
- def make_status_renderer(self, enum):
- def render_status(batch, field):
- value = batch.status_code
- if value is None:
- return ""
- status_code_text = enum.get(value, six.text_type(value))
- if batch.status_text:
- return HTML.tag('span', title=batch.status_text, c=status_code_text)
- return status_code_text
- return render_status
-
- def render_user(self, batch, field):
- user = getattr(batch, field)
- if not user:
- return ""
- title = six.text_type(user)
- url = self.request.route_url('users.view', uuid=user.uuid)
- return tags.link_to(title, url)
-
-
-class FileBatchMasterView3(BatchMasterView3, FileBatchMasterView2):
- """
- Base class for all file-based "batch master" views
- """
-
- def configure_form(self, f):
- super(FileBatchMasterView3, self).configure_form(f)
-
- # filename
- f.set_renderer('filename', self.render_filename)
- f.set_label('filename', "Data File")
- if self.editing:
- f.set_readonly('filename')
-
- if self.creating:
- if 'filename' not in f.fields:
- f.fields.insert(0, 'filename')
- tmpstore = SessionFileUploadTempStore(self.request)
- f.set_node('filename', colander.SchemaNode(deform.FileData(), widget=dfwidget.FileUploadWidget(tmpstore)))
-
- def render_filename(self, batch, field):
- path = batch.filepath(self.rattail_config, filename=batch.filename)
- url = self.get_action_url('download', batch)
- return self.render_file_field(path, url)
diff --git a/tailbone/views/batch/core4.py b/tailbone/views/batch/core4.py
deleted file mode 100644
index 6d9ac792..00000000
--- a/tailbone/views/batch/core4.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# -*- coding: utf-8; -*-
-################################################################################
-#
-# Rattail -- Retail Software Framework
-# Copyright © 2010-2018 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 .
-#
-################################################################################
-"""
-Base views for maintaining batches
-"""
-
-from __future__ import unicode_literals, absolute_import
-
-from tailbone.views import MasterView4
-from tailbone.views.batch import BatchMasterView3, FileBatchMasterView3
-
-
-class BatchMasterView4(MasterView4, BatchMasterView3):
- """
- Base class for all "batch master" views
- """
-
- row_labels = {
- 'status_code': "Status",
- }
-
- def configure_mobile_form(self, f):
- super(BatchMasterView4, 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_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 configure_row_form(self, f):
- super(BatchMasterView4, self).configure_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 configure_mobile_row_form(self, f):
- super(BatchMasterView4, 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")
-
- # NOTE: must override default logic here, by doing nothing
- def before_create_row(self, form):
- pass
-
- def save_create_row_form(self, form):
- batch = self.get_instance()
- row = self.objectify(form, self.form_deserialized)
- self.handler.add_row(batch, row)
- self.Session.flush()
- return row
-
-
-class FileBatchMasterView4(BatchMasterView4, FileBatchMasterView3):
- """
- Base class for all file-based "batch master" views
- """
-
diff --git a/tailbone/views/batch/importer.py b/tailbone/views/batch/importer.py
index 9e966f66..22e4323a 100644
--- a/tailbone/views/batch/importer.py
+++ b/tailbone/views/batch/importer.py
@@ -30,7 +30,7 @@ import sqlalchemy as sa
from rattail.db import model
-from tailbone.views.batch import BatchMasterView4 as BatchMasterView
+from tailbone.views.batch import BatchMasterView
class ImporterBatchView(BatchMasterView):
diff --git a/tailbone/views/batch/pricing.py b/tailbone/views/batch/pricing.py
index a378614c..e871c3df 100644
--- a/tailbone/views/batch/pricing.py
+++ b/tailbone/views/batch/pricing.py
@@ -30,7 +30,7 @@ from rattail.db import model
from webhelpers2.html import tags
-from tailbone.views.batch import BatchMasterView4 as BatchMasterView
+from tailbone.views.batch import BatchMasterView
class PricingBatchView(BatchMasterView):
diff --git a/tailbone/views/bouncer.py b/tailbone/views/bouncer.py
index 861f6a86..680bdef8 100644
--- a/tailbone/views/bouncer.py
+++ b/tailbone/views/bouncer.py
@@ -37,7 +37,7 @@ from pyramid.response import FileResponse
from webhelpers2.html import HTML, tags
from tailbone import grids
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class EmailBouncesView(MasterView):
diff --git a/tailbone/views/brands.py b/tailbone/views/brands.py
index 3a0b177f..c3796af8 100644
--- a/tailbone/views/brands.py
+++ b/tailbone/views/brands.py
@@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
-from tailbone.views import MasterView4 as MasterView, AutocompleteView
+from tailbone.views import MasterView, AutocompleteView
class BrandsView(MasterView):
diff --git a/tailbone/views/categories.py b/tailbone/views/categories.py
index f5a7731e..057e6d6a 100644
--- a/tailbone/views/categories.py
+++ b/tailbone/views/categories.py
@@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class CategoriesView(MasterView):
diff --git a/tailbone/views/customergroups.py b/tailbone/views/customergroups.py
index 0ee806b2..5c6892d5 100644
--- a/tailbone/views/customergroups.py
+++ b/tailbone/views/customergroups.py
@@ -29,7 +29,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class CustomerGroupsView(MasterView):
diff --git a/tailbone/views/customers.py b/tailbone/views/customers.py
index a5305033..c6b64379 100644
--- a/tailbone/views/customers.py
+++ b/tailbone/views/customers.py
@@ -39,7 +39,7 @@ from webhelpers2.html import HTML, tags
from tailbone import grids
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView, AutocompleteView
+from tailbone.views import MasterView, AutocompleteView
from rattail.db import model
diff --git a/tailbone/views/custorders/items.py b/tailbone/views/custorders/items.py
index e075e1d8..f4c540f0 100644
--- a/tailbone/views/custorders/items.py
+++ b/tailbone/views/custorders/items.py
@@ -33,7 +33,7 @@ from sqlalchemy import orm
from rattail.db import model
from rattail.time import localtime
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
from tailbone.util import raw_datetime
diff --git a/tailbone/views/custorders/orders.py b/tailbone/views/custorders/orders.py
index 8dfcae28..dee21f15 100644
--- a/tailbone/views/custorders/orders.py
+++ b/tailbone/views/custorders/orders.py
@@ -34,7 +34,7 @@ from rattail.db import model
from webhelpers2.html import tags
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class CustomerOrdersView(MasterView):
diff --git a/tailbone/views/datasync.py b/tailbone/views/datasync.py
index 6d602666..def5ee5f 100644
--- a/tailbone/views/datasync.py
+++ b/tailbone/views/datasync.py
@@ -31,7 +31,7 @@ import logging
from rattail.db import model
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
log = logging.getLogger(__name__)
diff --git a/tailbone/views/departments.py b/tailbone/views/departments.py
index 29c6ea4e..b0eba5be 100644
--- a/tailbone/views/departments.py
+++ b/tailbone/views/departments.py
@@ -33,7 +33,7 @@ from rattail.db import model
from deform import widget as dfwidget
from tailbone import grids
-from tailbone.views import MasterView4 as MasterView, AutocompleteView
+from tailbone.views import MasterView, AutocompleteView
class DepartmentsView(MasterView):
diff --git a/tailbone/views/depositlinks.py b/tailbone/views/depositlinks.py
index 9be6260c..db28e3f6 100644
--- a/tailbone/views/depositlinks.py
+++ b/tailbone/views/depositlinks.py
@@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class DepositLinksView(MasterView):
diff --git a/tailbone/views/email.py b/tailbone/views/email.py
index 932840b4..de700260 100644
--- a/tailbone/views/email.py
+++ b/tailbone/views/email.py
@@ -37,7 +37,7 @@ from deform import widget as dfwidget
from webhelpers2.html import HTML
from tailbone.db import Session
-from tailbone.views import View, MasterView4 as MasterView
+from tailbone.views import View, MasterView
class ProfilesView(MasterView):
diff --git a/tailbone/views/employees.py b/tailbone/views/employees.py
index 42025eba..89d7e014 100644
--- a/tailbone/views/employees.py
+++ b/tailbone/views/employees.py
@@ -37,7 +37,7 @@ from webhelpers2.html import tags, HTML
from tailbone import grids
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView, AutocompleteView
+from tailbone.views import MasterView, AutocompleteView
class EmployeesView(MasterView):
diff --git a/tailbone/views/exports.py b/tailbone/views/exports.py
index 8dc7c1ae..9e842616 100644
--- a/tailbone/views/exports.py
+++ b/tailbone/views/exports.py
@@ -36,7 +36,7 @@ from pyramid.response import FileResponse
from webhelpers2.html import HTML, tags
from tailbone import forms2 as forms
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class ExportMasterView(MasterView):
diff --git a/tailbone/views/families.py b/tailbone/views/families.py
index 72feba3b..997255b3 100644
--- a/tailbone/views/families.py
+++ b/tailbone/views/families.py
@@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class FamiliesView(MasterView):
diff --git a/tailbone/views/handheld.py b/tailbone/views/handheld.py
index 015c7e41..13a74c54 100644
--- a/tailbone/views/handheld.py
+++ b/tailbone/views/handheld.py
@@ -35,7 +35,7 @@ import formencode as fe
from webhelpers2.html import tags
from tailbone.db import Session
-from tailbone.views.batch import FileBatchMasterView4 as FileBatchMasterView
+from tailbone.views.batch import FileBatchMasterView
ACTION_OPTIONS = OrderedDict([
diff --git a/tailbone/views/inventory.py b/tailbone/views/inventory.py
index e0e0976d..418515b0 100644
--- a/tailbone/views/inventory.py
+++ b/tailbone/views/inventory.py
@@ -43,8 +43,8 @@ from deform import widget as dfwidget
from webhelpers2.html import HTML, tags
from tailbone import forms, forms2, grids
-from tailbone.views import MasterView4 as MasterView
-from tailbone.views.batch import BatchMasterView4 as BatchMasterView
+from tailbone.views import MasterView
+from tailbone.views.batch import BatchMasterView
class InventoryAdjustmentReasonsView(MasterView):
diff --git a/tailbone/views/labels/batch.py b/tailbone/views/labels/batch.py
index 663d3416..acf33d9e 100644
--- a/tailbone/views/labels/batch.py
+++ b/tailbone/views/labels/batch.py
@@ -30,7 +30,7 @@ from rattail.db import model
from webhelpers2.html import HTML, tags
-from tailbone.views.batch import BatchMasterView4 as BatchMasterView
+from tailbone.views.batch import BatchMasterView
class LabelBatchView(BatchMasterView):
diff --git a/tailbone/views/labels/profiles.py b/tailbone/views/labels/profiles.py
index d2b821dc..25ecfa0e 100644
--- a/tailbone/views/labels/profiles.py
+++ b/tailbone/views/labels/profiles.py
@@ -31,7 +31,7 @@ from rattail.db import model
from pyramid.httpexceptions import HTTPFound
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class ProfilesView(MasterView):
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index 4a9bc50e..6c6080d1 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -38,19 +38,19 @@ import sqlalchemy_continuum as continuum
from rattail.db import model, Session as RattailSession
from rattail.db.continuum import model_transaction_query
from rattail.util import prettify
-from rattail.time import localtime #, make_utc
+from rattail.time import localtime
from rattail.threads import Thread
from rattail.csvutil import UnicodeDictWriter
from rattail.files import temp_path
from rattail.excel import ExcelWriter
-import formalchemy as fa
+import deform
from pyramid import httpexceptions
from pyramid.renderers import get_renderer, render_to_response, render
from pyramid.response import FileResponse
from webhelpers2.html import HTML, tags
-from tailbone import forms, grids, diffs
+from tailbone import forms2 as forms, grids, diffs
from tailbone.views import View
from tailbone.progress import SessionProgress
@@ -67,6 +67,7 @@ class MasterView(View):
checkboxes = False
listable = True
+ sortable = True
results_downloadable_csv = False
results_downloadable_xlsx = False
creatable = True
@@ -85,6 +86,7 @@ class MasterView(View):
supports_mobile = False
mobile_creatable = False
+ mobile_pageable = True
mobile_filterable = False
listing = False
@@ -103,12 +105,15 @@ class MasterView(View):
has_versions = False
+ labels = {'uuid': "UUID"}
+
# ROW-RELATED ATTRS FOLLOW:
has_rows = False
model_row_class = None
- rows_filterable = True
+ rows_pageable = True
rows_sortable = True
+ rows_filterable = True
rows_viewable = True
rows_creatable = False
rows_editable = False
@@ -123,6 +128,8 @@ class MasterView(View):
mobile_rows_viewable = False
mobile_rows_editable = False
+ row_labels = {}
+
@property
def Session(self):
"""
@@ -132,6 +139,56 @@ class MasterView(View):
from tailbone.db import Session
return Session
+ @classmethod
+ def get_grid_factory(cls):
+ """
+ Returns the grid factory or class which is to be used when creating new
+ grid instances.
+ """
+ return getattr(cls, 'grid_factory', grids.Grid)
+
+ @classmethod
+ def get_row_grid_factory(cls):
+ """
+ Returns the grid factory or class which is to be used when creating new
+ row grid instances.
+ """
+ return getattr(cls, 'row_grid_factory', grids.Grid)
+
+ @classmethod
+ def get_version_grid_factory(cls):
+ """
+ Returns the grid factory or class which is to be used when creating new
+ version grid instances.
+ """
+ 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):
+ for key, label in self.labels.items():
+ obj.set_label(key, label)
+
+ def set_row_labels(self, obj):
+ for key, label in self.row_labels.items():
+ obj.set_label(key, label)
+
##############################
# Available Views
##############################
@@ -164,6 +221,223 @@ class MasterView(View):
return self.render_to_response('index', {'grid': grid})
+ def make_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
+ """
+ Creates a new grid instance
+ """
+ if factory is None:
+ factory = self.get_grid_factory()
+ if key is None:
+ key = self.get_grid_key()
+ if data is None:
+ data = self.get_data(session=kwargs.get('session'))
+ if columns is None:
+ columns = self.get_grid_columns()
+
+ kwargs.setdefault('request', self.request)
+ kwargs = self.make_grid_kwargs(**kwargs)
+ grid = factory(key, data, columns, **kwargs)
+ self.configure_grid(grid)
+ grid.load_settings()
+ return grid
+
+ def get_effective_data(self, session=None, **kwargs):
+ """
+ Convenience method which returns the "effective" data for the master
+ grid, filtered and sorted to match what would show on the UI, but not
+ paged etc.
+ """
+ if session is None:
+ session = self.Session()
+ kwargs.setdefault('pageable', False)
+ grid = self.make_grid(session=session, **kwargs)
+ return grid.make_visible_data()
+
+ def get_grid_columns(self):
+ if hasattr(self, 'grid_columns'):
+ return self.grid_columns
+ # TODO
+ raise NotImplementedError
+
+ def make_grid_kwargs(self, **kwargs):
+ """
+ Return a dictionary of kwargs to be passed to the factory when creating
+ new grid instances.
+ """
+ defaults = {
+ 'model_class': getattr(self, 'model_class', None),
+ 'width': 'full',
+ 'filterable': self.filterable,
+ 'sortable': self.sortable,
+ 'pageable': self.pageable,
+ 'extra_row_class': self.grid_extra_class,
+ 'url': lambda obj: self.get_action_url('view', obj),
+ 'checkboxes': self.checkboxes or (
+ self.mergeable and self.request.has_perm('{}.merge'.format(self.get_permission_prefix()))),
+ 'checked': self.checked,
+ }
+ if 'main_actions' not in kwargs and 'more_actions' not in kwargs:
+ main, more = self.get_grid_actions()
+ defaults['main_actions'] = main
+ defaults['more_actions'] = more
+ defaults.update(kwargs)
+ return defaults
+
+ def configure_grid(self, grid):
+ self.set_labels(grid)
+
+ def grid_extra_class(self, obj, i):
+ """
+ Returns string of extra class(es) for the table row corresponding to
+ the given object, or ``None``.
+ """
+
+ def make_row_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
+ """
+ Make and return a new (configured) rows grid instance.
+ """
+ instance = kwargs.pop('instance', None)
+ if not instance:
+ instance = self.get_instance()
+
+ if factory is None:
+ factory = self.get_row_grid_factory()
+ if key is None:
+ key = self.get_row_grid_key()
+ if data is None:
+ data = self.get_row_data(instance)
+ if columns is None:
+ columns = self.get_row_grid_columns()
+
+ kwargs.setdefault('request', self.request)
+ kwargs = self.make_row_grid_kwargs(**kwargs)
+ grid = factory(key, data, columns, **kwargs)
+ self.configure_row_grid(grid)
+ grid.load_settings()
+ return grid
+
+ def get_row_grid_columns(self):
+ if hasattr(self, 'row_grid_columns'):
+ return self.row_grid_columns
+ # TODO
+ raise NotImplementedError
+
+ def make_row_grid_kwargs(self, **kwargs):
+ """
+ Return a dict of kwargs to be used when constructing a new rows grid.
+ """
+ permission_prefix = self.get_permission_prefix()
+
+ defaults = {
+ 'model_class': self.model_row_class,
+ 'width': 'full',
+ 'filterable': self.rows_filterable,
+ 'sortable': self.rows_sortable,
+ 'pageable': self.rows_pageable,
+ 'default_pagesize': self.rows_default_pagesize,
+ 'extra_row_class': self.row_grid_extra_class,
+ 'url': lambda obj: self.get_row_action_url('view', obj),
+ }
+
+ if self.has_rows and 'main_actions' not in defaults:
+ actions = []
+
+ # view action
+ if self.rows_viewable:
+ view = lambda r, i: self.get_row_action_url('view', r)
+ actions.append(grids.GridAction('view', icon='zoomin', url=view))
+
+ # edit action
+ if self.rows_editable:
+ actions.append(grids.GridAction('edit', icon='pencil', url=self.row_edit_action_url))
+
+ # delete action
+ if self.rows_deletable and self.request.has_perm('{}.delete_row'.format(permission_prefix)):
+ actions.append(grids.GridAction('delete', icon='trash', url=self.row_delete_action_url))
+ defaults['delete_speedbump'] = self.rows_deletable_speedbump
+
+ defaults['main_actions'] = actions
+
+ defaults.update(kwargs)
+ return defaults
+
+ def configure_row_grid(self, grid):
+ # super(MasterView, self).configure_row_grid(grid)
+ self.set_row_labels(grid)
+
+ def row_grid_extra_class(self, obj, i):
+ """
+ Returns string of extra class(es) for the table row corresponding to
+ the given row object, or ``None``.
+ """
+
+ def make_version_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
+ """
+ Creates a new version grid instance
+ """
+ instance = kwargs.pop('instance', None)
+ if not instance:
+ instance = self.get_instance()
+
+ if factory is None:
+ factory = self.get_version_grid_factory()
+ if key is None:
+ key = self.get_version_grid_key()
+ if data is None:
+ data = self.get_version_data(instance)
+ if columns is None:
+ columns = self.get_version_grid_columns()
+
+ kwargs.setdefault('request', self.request)
+ kwargs = self.make_version_grid_kwargs(**kwargs)
+ grid = factory(key, data, columns, **kwargs)
+ self.configure_version_grid(grid)
+ grid.load_settings()
+ return grid
+
+ def get_version_grid_columns(self):
+ if hasattr(self, 'version_grid_columns'):
+ return self.version_grid_columns
+ # TODO
+ return [
+ 'issued_at',
+ 'user',
+ 'remote_addr',
+ 'comment',
+ ]
+
+ def make_version_grid_kwargs(self, **kwargs):
+ """
+ Return a dictionary of kwargs to be passed to the factory when
+ constructing a new version grid.
+ """
+ defaults = {
+ 'model_class': continuum.transaction_class(self.get_model_class()),
+ 'width': 'full',
+ 'pageable': True,
+ }
+ if 'main_actions' not in kwargs:
+ route = '{}.version'.format(self.get_route_prefix())
+ instance = kwargs.get('instance') or self.get_instance()
+ url = lambda txn, i: self.request.route_url(route, uuid=instance.uuid, txnid=txn.id)
+ defaults['main_actions'] = [
+ self.make_action('view', icon='zoomin', url=url),
+ ]
+ defaults.update(kwargs)
+ return defaults
+
+ def configure_version_grid(self, g):
+ g.set_sort_defaults('issued_at', 'desc')
+ g.set_renderer('comment', self.render_version_comment)
+ g.set_label('issued_at', "Changed")
+ g.set_label('user', "Changed by")
+ g.set_label('remote_addr', "IP Address")
+ # TODO: why does this render '#' as url?
+ # g.set_link('issued_at')
+
+ def render_version_comment(self, transaction, column):
+ return transaction.meta.get('comment', "")
+
def mobile_index(self):
"""
Mobile "home" page for the data model
@@ -184,6 +458,33 @@ class MasterView(View):
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
@@ -193,6 +494,93 @@ class MasterView(View):
"""
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.
@@ -203,45 +591,8 @@ class MasterView(View):
Returns a set of filters for the mobile row grid, if applicable.
"""
- def mobile_listitem_field(self):
- """
- Must return a FormAlchemy field to be appended to grid, or ``None`` if
- none is desired.
- """
- return fa.Field('listitem', value=lambda obj: obj,
- renderer=self.mobile_listitem_renderer())
-
- def mobile_listitem_renderer(self):
- """
- Must return a FormAlchemy field renderer callable for the mobile grid's
- list item field.
- """
- master = self
-
- class ListItemRenderer(fa.FieldRenderer):
-
- def render_readonly(self, **kwargs):
- obj = self.raw_value
- if obj is None:
- return ''
- title = master.get_instance_title(obj)
- url = master.get_action_url('view', obj, mobile=True)
- return tags.link_to(title, url)
-
- return ListItemRenderer
-
- def mobile_row_listitem_renderer(self):
- """
- Must return a FormAlchemy field renderer callable for the mobile row
- grid's list item field.
- """
- master = self
-
- class ListItemRenderer(fa.FieldRenderer):
- def render_readonly(self, **kwargs):
- return master.render_mobile_row_listitem(self.raw_value, **kwargs)
-
- return ListItemRenderer
+ def render_mobile_row_listitem(self, obj, i):
+ return obj
def create(self):
"""
@@ -276,17 +627,30 @@ class MasterView(View):
return self.redirect_after_create(obj, mobile=True)
return self.render_to_response('create', {'form': form}, mobile=True)
+ def save_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 before_create_flush(self, obj, form):
+ pass
+
def flash_after_create(self, obj):
self.request.session.flash("{} has been created: {}".format(
self.get_model_title(), self.get_instance_title(obj)))
- def save_create_form(self, form):
- self.before_create(form)
- form.save()
-
def save_mobile_create_form(self, form):
self.before_create(form)
- form.save()
+ 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):
if self.populatable and self.should_populate(instance):
@@ -563,50 +927,160 @@ class MasterView(View):
context['grid'] = self.make_mobile_row_grid(instance=instance)
return self.render_to_response('view', context, mobile=True)
- def make_mobile_form(self, instance, **kwargs):
+ def make_mobile_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
"""
- Make a FormAlchemy-based form for use with mobile CRUD views
+ Creates a new mobile form for the given model class/instance.
"""
- fieldset = self.make_fieldset(instance)
- self.preconfigure_mobile_fieldset(fieldset)
- self.configure_mobile_fieldset(fieldset)
- kwargs.setdefault('creating', self.creating)
- kwargs.setdefault('editing', self.editing)
- kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
- if self.creating:
- kwargs.setdefault('cancel_url', self.get_index_url(mobile=True))
- else:
- kwargs.setdefault('cancel_url', self.get_action_url('view', instance, mobile=True))
- factory = kwargs.pop('factory', forms.AlchemyForm)
- kwargs.setdefault('session', self.Session())
- form = factory(self.request, fieldset, **kwargs)
- form.readonly = self.viewing
+ 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_mobile_form(self, form):
+ """
+ Configure the primary mobile 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_labels(form)
+
def preconfigure_mobile_row_fieldset(self, fieldset):
self._preconfigure_row_fieldset(fieldset)
def configure_mobile_row_fieldset(self, fieldset):
self.configure_row_fieldset(fieldset)
- def make_mobile_row_form(self, row, **kwargs):
+ def validate_mobile_form(self, form):
+ controls = self.request.POST.items()
+ try:
+ self.form_deserialized = form.validate(controls)
+ except deform.ValidationFailure:
+ return False
+ return True
+
+ def make_mobile_row_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
"""
- Make a form for use with mobile CRUD views, for the given row object.
+ Creates a new mobile form for the given model class/instance.
"""
- fieldset = self.make_fieldset(row)
- self.preconfigure_mobile_row_fieldset(fieldset)
- self.configure_mobile_row_fieldset(fieldset)
- kwargs.setdefault('session', self.Session())
- kwargs.setdefault('creating', self.creating)
- kwargs.setdefault('editing', self.editing)
- kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
- if 'cancel_url' not in kwargs:
- kwargs['cancel_url'] = self.get_action_url('view', self.get_parent(row), mobile=True)
- factory = kwargs.pop('factory', forms.AlchemyForm)
- form = factory(self.request, fieldset, **kwargs)
- form.readonly = self.viewing
+ 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 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,
+ '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 preconfigure_mobile_fieldset(self, fieldset):
self._preconfigure_fieldset(fieldset)
@@ -619,14 +1093,6 @@ class MasterView(View):
def get_mobile_row_data(self, parent):
return self.get_row_data(parent)
- def mobile_row_listitem_field(self):
- """
- Must return a FormAlchemy field to be appended to row grid, or ``None``
- if none is desired.
- """
- return fa.Field('listitem', value=lambda obj: obj,
- renderer=self.mobile_row_listitem_renderer())
-
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)
@@ -818,18 +1284,10 @@ class MasterView(View):
context['dform'] = form.make_deform_form()
return self.render_to_response('edit', context)
- def validate_form(self, form):
- return form.validate()
-
- def validate_mobile_form(self, form):
- return form.validate()
-
- def validate_row_form(self, form):
- return form.validate()
-
def save_edit_form(self, form):
- self.save_form(form)
- self.after_edit(form.fieldset.model)
+ obj = self.objectify(form, self.form_deserialized)
+ self.after_edit(obj)
+ self.Session.flush()
def redirect_after_edit(self, instance):
return self.redirect(self.get_action_url('view', instance))
@@ -1623,45 +2081,151 @@ class MasterView(View):
"""
return six.text_type(instance)
- def make_form(self, instance, **kwargs):
+ @classmethod
+ def get_form_factory(cls):
"""
- Make a FormAlchemy-based form for use with CRUD views.
+ Returns the grid factory or class which is to be used when creating new
+ grid instances.
"""
- # TODO: Some hacky stuff here, to accommodate old form cruft. Probably
- # should refactor forms soon too, but trying to avoid it for the moment.
+ return getattr(cls, 'form_factory', forms.Form)
- kwargs.setdefault('creating', self.creating)
- kwargs.setdefault('editing', self.editing)
+ @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)
- fieldset = self.make_fieldset(instance)
- self._preconfigure_fieldset(fieldset)
- self.configure_fieldset(fieldset)
- self._postconfigure_fieldset(fieldset)
+ @classmethod
+ def get_row_form_factory(cls):
+ """
+ Returns the factory or class which is to be used when creating new row
+ forms.
+ """
+ return getattr(cls, 'row_form_factory', forms.Form)
- kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
+ @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 render_file_field(self, path, url=None, filename=None):
+ """
+ Convenience for rendering a file with optional download link
+ """
+ if not filename:
+ filename = os.path.basename(path)
+ content = "{} ({})".format(filename, self.readable_size(path))
+ if url:
+ return tags.link_to(content, url)
+ return content
+
+ def readable_size(self, path):
+ # TODO: this was shamelessly copied from FormAlchemy ...
+ length = self.get_size(path)
+ if length == 0:
+ return '0 KB'
+ if length <= 1024:
+ return '1 KB'
+ if length > 1048576:
+ return '%0.02f MB' % (length / 1048576.0)
+ return '%0.02f KB' % (length / 1024.0)
+
+ def get_size(self, path):
+ try:
+ return os.path.getsize(path)
+ except os.error:
+ return 0
+
+ def make_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
+ """
+ Creates a new form for the given model class/instance
+ """
+ if factory is None:
+ factory = self.get_form_factory()
+ if fields is None:
+ fields = self.get_form_fields()
+ if schema is None:
+ schema = self.make_form_schema()
+
+ # TODO: SQLAlchemy class instance is assumed *unless* we get a dict
+ # (seems like we should be smarter about this somehow)
+ # if not self.creating and not isinstance(instance, dict):
+ if not self.creating:
+ kwargs['model_instance'] = instance
+ kwargs = self.make_form_kwargs(**kwargs)
+ form = factory(fields, schema, **kwargs)
+ self.configure_form(form)
+ return form
+
+ def get_form_fields(self):
+ if hasattr(self, 'form_fields'):
+ return self.form_fields
+ # TODO
+ # raise NotImplementedError
+
+ def make_form_schema(self):
+ if not self.model_class:
+ # TODO
+ raise NotImplementedError
+
+ def make_form_kwargs(self, **kwargs):
+ """
+ Return a dictionary of kwargs to be passed to the factory when creating
+ new form instances.
+ """
+ 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:
kwargs.setdefault('cancel_url', self.get_index_url())
else:
+ instance = kwargs['model_instance']
kwargs.setdefault('cancel_url', self.get_action_url('view', instance))
- factory = kwargs.pop('factory', forms.AlchemyForm)
- kwargs.setdefault('session', self.Session())
- form = factory(self.request, fieldset, **kwargs)
- form.readonly = self.viewing
- return form
+ defaults.update(kwargs)
+ return defaults
+
+ def configure_form(self, form):
+ """
+ Configure the primary form. By default this just sets any primary key
+ fields to be readonly (if we have a :attr:`model_class`).
+ """
+ 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_labels(form)
+
+ def validate_form(self, form):
+ controls = self.request.POST.items()
+ try:
+ self.form_deserialized = form.validate(controls)
+ except deform.ValidationFailure:
+ return False
+ return True
+
+ def objectify(self, form, data):
+ obj = form.schema.objectify(data, context=form.model_instance)
+ return obj
def save_form(self, form):
form.save()
- def make_fieldset(self, instance, **kwargs):
- """
- Make a FormAlchemy fieldset for the given model instance.
- """
- kwargs.setdefault('session', self.Session())
- kwargs.setdefault('request', self.request)
- fieldset = fa.FieldSet(instance, **kwargs)
- fieldset.prettify = prettify
- return fieldset
-
def _preconfigure_fieldset(self, fieldset):
pass
@@ -1756,8 +2320,19 @@ class MasterView(View):
self.get_instance_title(parent)),
'form': form})
+ # TODO: still need to verify this logic
def save_create_row_form(self, form):
- self.save_row_form(form)
+ # self.before_create(form)
+ # with self.Session().no_autoflush:
+ # obj = self.objectify(form, self.form_deserialized)
+ # self.before_create_flush(obj, form)
+ obj = self.objectify(form, self.form_deserialized)
+ self.Session.add(obj)
+ self.Session.flush()
+ return obj
+
+ # def save_create_row_form(self, form):
+ # self.save_row_form(form)
def before_create_row(self, form):
pass
@@ -1879,11 +2454,13 @@ class MasterView(View):
mobile=True)
def save_edit_row_form(self, form):
- self.save_row_form(form)
- self.after_edit_row(form.fieldset.model)
+ obj = self.objectify(form, self.form_deserialized)
+ self.after_edit_row(obj)
+ self.Session.flush()
+ return obj
- def save_row_form(self, form):
- form.save()
+ # def save_row_form(self, form):
+ # form.save()
def after_edit_row(self, row):
"""
@@ -1926,38 +2503,86 @@ class MasterView(View):
raise httpexceptions.HTTPNotFound()
return instance
- def make_row_form(self, instance, **kwargs):
+ def make_row_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
"""
- Make a FormAlchemy form for use with CRUD views for a data *row*.
+ Creates a new row form for the given model class/instance.
"""
- # TODO: Some hacky stuff here, to accommodate old form cruft. Probably
- # should refactor forms soon too, but trying to avoid it for the moment.
+ if factory is None:
+ factory = self.get_row_form_factory()
+ if fields is None:
+ fields = self.get_row_form_fields()
+ if schema is None:
+ schema = self.make_row_form_schema()
- kwargs.setdefault('creating', self.creating)
- kwargs.setdefault('editing', self.editing)
-
- fieldset = self.make_fieldset(instance)
- self._preconfigure_row_fieldset(fieldset)
- self.configure_row_fieldset(fieldset)
-
- kwargs.setdefault('action_url', self.request.current_route_url(_query=None))
- if 'cancel_url' not in kwargs:
- if self.creating:
- kwargs['cancel_url'] = self.get_action_url('view', self.get_parent(instance))
- else:
- kwargs['cancel_url'] = self.get_row_action_url('view', instance)
-
- kwargs.setdefault('session', self.Session())
- form = forms.AlchemyForm(self.request, fieldset, **kwargs)
- form.readonly = self.viewing
+ if not self.creating:
+ kwargs['model_instance'] = instance
+ kwargs = self.make_row_form_kwargs(**kwargs)
+ form = factory(fields, schema, **kwargs)
+ self.configure_row_form(form)
return form
+ def get_row_form_fields(self):
+ if hasattr(self, 'row_form_fields'):
+ return self.row_form_fields
+ # TODO
+ # raise NotImplementedError
+
+ def make_row_form_schema(self):
+ if not self.model_row_class:
+ # TODO
+ raise NotImplementedError
+
+ def make_row_form_kwargs(self, **kwargs):
+ """
+ Return a dictionary of kwargs to be passed to the factory when creating
+ new row forms.
+ """
+ defaults = {
+ 'request': self.request,
+ 'readonly': self.viewing,
+ 'model_class': getattr(self, 'model_row_class', None),
+ 'action_url': self.request.current_route_url(_query=None),
+ }
+ if self.creating:
+ kwargs.setdefault('cancel_url', self.request.get_referrer())
+ else:
+ instance = kwargs['model_instance']
+ kwargs.setdefault('cancel_url', self.get_row_action_url('view', instance))
+ defaults.update(kwargs)
+ return defaults
+
+ def configure_row_form(self, form):
+ """
+ Configure a 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 _preconfigure_row_fieldset(self, fs):
pass
def configure_row_fieldset(self, fs):
fs.configure()
+ def validate_row_form(self, form):
+ controls = self.request.POST.items()
+ try:
+ self.form_deserialized = form.validate(controls)
+ except deform.ValidationFailure:
+ return False
+ return True
+
def get_row_action_url(self, action, row, mobile=False):
"""
Generate a URL for the given action on the given row.
diff --git a/tailbone/views/master2.py b/tailbone/views/master2.py
deleted file mode 100644
index 233f5271..00000000
--- a/tailbone/views/master2.py
+++ /dev/null
@@ -1,422 +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 .
-#
-################################################################################
-"""
-Master View
-"""
-
-from __future__ import unicode_literals, absolute_import
-
-import sqlalchemy_continuum as continuum
-
-from tailbone import grids
-from tailbone.views import MasterView
-
-
-class MasterView2(MasterView):
- """
- Base "master" view class. All model master views should derive from this.
- """
- sortable = True
- rows_pageable = True
- mobile_pageable = True
- labels = {'uuid': "UUID"}
-
- @classmethod
- def get_grid_factory(cls):
- """
- Returns the grid factory or class which is to be used when creating new
- grid instances.
- """
- return getattr(cls, 'grid_factory', grids.Grid)
-
- @classmethod
- def get_row_grid_factory(cls):
- """
- Returns the grid factory or class which is to be used when creating new
- row grid instances.
- """
- return getattr(cls, 'row_grid_factory', grids.Grid)
-
- @classmethod
- def get_version_grid_factory(cls):
- """
- Returns the grid factory or class which is to be used when creating new
- version grid instances.
- """
- 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 get_effective_data(self, session=None, **kwargs):
- """
- Convenience method which returns the "effective" data for the master
- grid, filtered and sorted to match what would show on the UI, but not
- paged etc.
- """
- if session is None:
- session = self.Session()
- kwargs.setdefault('pageable', False)
- grid = self.make_grid(session=session, **kwargs)
- return grid.make_visible_data()
-
- def make_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
- """
- Creates a new grid instance
- """
- if factory is None:
- factory = self.get_grid_factory()
- if key is None:
- key = self.get_grid_key()
- if data is None:
- data = self.get_data(session=kwargs.get('session'))
- if columns is None:
- columns = self.get_grid_columns()
-
- kwargs.setdefault('request', self.request)
- kwargs = self.make_grid_kwargs(**kwargs)
- grid = factory(key, data, columns, **kwargs)
- self.configure_grid(grid)
- grid.load_settings()
- return grid
-
- def make_row_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
- """
- Make and return a new (configured) rows grid instance.
- """
- instance = kwargs.pop('instance', None)
- if not instance:
- instance = self.get_instance()
-
- if factory is None:
- factory = self.get_row_grid_factory()
- if key is None:
- key = self.get_row_grid_key()
- if data is None:
- data = self.get_row_data(instance)
- if columns is None:
- columns = self.get_row_grid_columns()
-
- kwargs.setdefault('request', self.request)
- kwargs = self.make_row_grid_kwargs(**kwargs)
-
- grid = factory(key, data, columns, **kwargs)
- self.configure_row_grid(grid)
- grid.load_settings()
- return grid
-
- def make_version_grid(self, factory=None, key=None, data=None, columns=None, **kwargs):
- """
- Creates a new version grid instance
- """
- instance = kwargs.pop('instance', None)
- if not instance:
- instance = self.get_instance()
-
- if factory is None:
- factory = self.get_version_grid_factory()
- if key is None:
- key = self.get_version_grid_key()
- if data is None:
- data = self.get_version_data(instance)
- if columns is None:
- columns = self.get_version_grid_columns()
-
- kwargs.setdefault('request', self.request)
- kwargs = self.make_version_grid_kwargs(**kwargs)
- grid = factory(key, data, columns, **kwargs)
- self.configure_version_grid(grid)
- grid.load_settings()
- return grid
-
- 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 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_grid_columns(self):
- if hasattr(self, 'grid_columns'):
- return self.grid_columns
- # TODO
- raise NotImplementedError
-
- def get_row_grid_columns(self):
- if hasattr(self, 'row_grid_columns'):
- return self.row_grid_columns
- # TODO
- raise NotImplementedError
-
- def get_version_grid_columns(self):
- if hasattr(self, 'version_grid_columns'):
- return self.version_grid_columns
- # TODO
- return [
- 'issued_at',
- 'user',
- 'remote_addr',
- 'comment',
- ]
-
- def get_mobile_grid_columns(self):
- if hasattr(self, 'mobile_grid_columns'):
- return self.mobile_grid_columns
- # TODO
- return ['listitem']
-
- 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_grid_kwargs(self, **kwargs):
- """
- Return a dictionary of kwargs to be passed to the factory when creating
- new grid instances.
- """
- defaults = {
- 'model_class': getattr(self, 'model_class', None),
- 'width': 'full',
- 'filterable': self.filterable,
- 'sortable': self.sortable,
- 'pageable': self.pageable,
- 'extra_row_class': self.grid_extra_class,
- 'url': lambda obj: self.get_action_url('view', obj),
- 'checkboxes': self.checkboxes or (
- self.mergeable and self.request.has_perm('{}.merge'.format(self.get_permission_prefix()))),
- 'checked': self.checked,
- }
- if 'main_actions' not in kwargs and 'more_actions' not in kwargs:
- main, more = self.get_grid_actions()
- defaults['main_actions'] = main
- defaults['more_actions'] = more
- defaults.update(kwargs)
- return defaults
-
- def make_row_grid_kwargs(self, **kwargs):
- """
- Return a dict of kwargs to be used when constructing a new rows grid.
- """
- permission_prefix = self.get_permission_prefix()
-
- defaults = {
- 'model_class': self.model_row_class,
- 'width': 'full',
- 'filterable': self.rows_filterable,
- 'sortable': self.rows_sortable,
- 'pageable': self.rows_pageable,
- 'default_pagesize': self.rows_default_pagesize,
- 'extra_row_class': self.row_grid_extra_class,
- 'url': lambda obj: self.get_row_action_url('view', obj),
- }
-
- if self.has_rows and 'main_actions' not in defaults:
- actions = []
-
- # view action
- if self.rows_viewable:
- view = lambda r, i: self.get_row_action_url('view', r)
- actions.append(grids.GridAction('view', icon='zoomin', url=view))
-
- # edit action
- if self.rows_editable:
- actions.append(grids.GridAction('edit', icon='pencil', url=self.row_edit_action_url))
-
- # delete action
- if self.rows_deletable and self.request.has_perm('{}.delete_row'.format(permission_prefix)):
- actions.append(grids.GridAction('delete', icon='trash', url=self.row_delete_action_url))
- defaults['delete_speedbump'] = self.rows_deletable_speedbump
-
- defaults['main_actions'] = actions
-
- defaults.update(kwargs)
- return defaults
-
- def make_version_grid_kwargs(self, **kwargs):
- """
- Return a dictionary of kwargs to be passed to the factory when
- constructing a new version grid.
- """
- defaults = {
- 'model_class': continuum.transaction_class(self.get_model_class()),
- 'width': 'full',
- 'pageable': True,
- }
- if 'main_actions' not in kwargs:
- route = '{}.version'.format(self.get_route_prefix())
- instance = kwargs.get('instance') or self.get_instance()
- url = lambda txn, i: self.request.route_url(route, uuid=instance.uuid, txnid=txn.id)
- defaults['main_actions'] = [
- self.make_action('view', icon='zoomin', url=url),
- ]
- defaults.update(kwargs)
- return defaults
-
- 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_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_grid_renderers(self):
- return {
- 'listitem': self.render_mobile_listitem,
- }
-
- def render_mobile_listitem(self, obj, i):
- return obj
-
- def make_mobile_row_grid_renderers(self):
- return {
- 'listitem': self.render_mobile_row_listitem,
- }
-
- def render_mobile_row_listitem(self, obj, i):
- return obj
-
- def grid_extra_class(self, obj, i):
- """
- Returns string of extra class(es) for the table row corresponding to
- the given object, or ``None``.
- """
-
- def row_grid_extra_class(self, obj, i):
- """
- Returns string of extra class(es) for the table row corresponding to
- the given row object, or ``None``.
- """
-
- def set_labels(self, obj):
- for key, label in self.labels.items():
- obj.set_label(key, label)
-
- def configure_grid(self, grid):
- self.set_labels(grid)
-
- def configure_row_grid(self, grid):
- pass
-
- def configure_version_grid(self, g):
- g.set_sort_defaults('issued_at', 'desc')
- g.set_renderer('comment', self.render_version_comment)
- g.set_label('issued_at', "Changed")
- g.set_label('user', "Changed by")
- g.set_label('remote_addr', "IP Address")
- # TODO: why does this render '#' as url?
- # g.set_link('issued_at')
-
- def render_version_comment(self, transaction, column):
- return transaction.meta.get('comment', "")
-
- def configure_mobile_grid(self, grid):
- pass
-
- def configure_mobile_row_grid(self, grid):
- pass
diff --git a/tailbone/views/master3.py b/tailbone/views/master3.py
deleted file mode 100644
index 08bbabb7..00000000
--- a/tailbone/views/master3.py
+++ /dev/null
@@ -1,178 +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 .
-#
-################################################################################
-"""
-Master View
-"""
-
-from __future__ import unicode_literals, absolute_import
-
-import os
-
-from sqlalchemy import orm
-
-import deform
-from webhelpers2.html import tags
-
-from tailbone import forms2 as forms
-from tailbone.views import MasterView2
-
-
-class MasterView3(MasterView2):
- """
- Base "master" view class. All model master views should derive from this.
- """
-
- @classmethod
- def get_form_factory(cls):
- """
- Returns the grid factory or class which is to be used when creating new
- grid instances.
- """
- return getattr(cls, 'form_factory', forms.Form)
-
- def make_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
- """
- Creates a new form for the given model class/instance
- """
- if factory is None:
- factory = self.get_form_factory()
- if fields is None:
- fields = self.get_form_fields()
- if schema is None:
- schema = self.make_form_schema()
-
- # TODO: SQLAlchemy class instance is assumed *unless* we get a dict
- # (seems like we should be smarter about this somehow)
- # if not self.creating and not isinstance(instance, dict):
- if not self.creating:
- kwargs['model_instance'] = instance
- kwargs = self.make_form_kwargs(**kwargs)
- form = factory(fields, schema, **kwargs)
- self.configure_form(form)
- return form
-
- def make_form_schema(self):
- if not self.model_class:
- # TODO
- raise NotImplementedError
-
- def get_form_fields(self):
- if hasattr(self, 'form_fields'):
- return self.form_fields
- # TODO
- # raise NotImplementedError
-
- def make_form_kwargs(self, **kwargs):
- """
- Return a dictionary of kwargs to be passed to the factory when creating
- new form instances.
- """
- 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:
- kwargs.setdefault('cancel_url', self.get_index_url())
- else:
- instance = kwargs['model_instance']
- kwargs.setdefault('cancel_url', self.get_action_url('view', instance))
- defaults.update(kwargs)
- return defaults
-
- def configure_form(self, form):
- """
- Configure the primary form. By default this just sets any primary key
- fields to be readonly (if we have a :attr:`model_class`).
- """
- 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_labels(form)
-
- def render_file_field(self, path, url=None, filename=None):
- """
- Convenience for rendering a file with optional download link
- """
- if not filename:
- filename = os.path.basename(path)
- content = "{} ({})".format(filename, self.readable_size(path))
- if url:
- return tags.link_to(content, url)
- return content
-
- def readable_size(self, path):
- # TODO: this was shamelessly copied from FormAlchemy ...
- length = self.get_size(path)
- if length == 0:
- return '0 KB'
- if length <= 1024:
- return '1 KB'
- if length > 1048576:
- return '%0.02f MB' % (length / 1048576.0)
- return '%0.02f KB' % (length / 1024.0)
-
- def get_size(self, path):
- try:
- return os.path.getsize(path)
- except os.error:
- return 0
-
- def validate_form(self, form):
- controls = self.request.POST.items()
- try:
- self.form_deserialized = form.validate(controls)
- except deform.ValidationFailure:
- return False
- return True
-
- def objectify(self, form, data):
- obj = form.schema.objectify(data, context=form.model_instance)
- return obj
-
- def save_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 before_create_flush(self, obj, form):
- pass
-
- def save_edit_form(self, form):
- obj = self.objectify(form, self.form_deserialized)
- self.after_edit(obj)
- self.Session.flush()
diff --git a/tailbone/views/master4.py b/tailbone/views/master4.py
deleted file mode 100644
index 07ae1247..00000000
--- a/tailbone/views/master4.py
+++ /dev/null
@@ -1,319 +0,0 @@
-# -*- coding: utf-8; -*-
-################################################################################
-#
-# Rattail -- Retail Software Framework
-# Copyright © 2010-2018 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 .
-#
-################################################################################
-"""
-Master View (v4)
-"""
-
-from __future__ import unicode_literals, absolute_import
-
-import deform
-
-from tailbone import forms2 as forms
-from tailbone.views import MasterView3
-
-
-class MasterView4(MasterView3):
- """
- Base "master" view class. All model master views should derive from this.
- """
- row_labels = {}
-
- 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 make_row_form(self, instance=None, factory=None, fields=None, schema=None, **kwargs):
- """
- Creates a new row form for the given model class/instance.
- """
- if factory is None:
- factory = self.get_row_form_factory()
- if fields is None:
- fields = self.get_row_form_fields()
- if schema is None:
- schema = self.make_row_form_schema()
-
- if not self.creating:
- kwargs['model_instance'] = instance
- kwargs = self.make_row_form_kwargs(**kwargs)
- form = factory(fields, schema, **kwargs)
- self.configure_row_form(form)
- return form
-
- 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
-
- @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):
- """
- Returns the factory or class which is to be used when creating new row
- forms.
- """
- 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 make_mobile_form_schema(self):
- if not self.model_class:
- # TODO
- raise NotImplementedError
-
- def make_row_form_schema(self):
- if not self.model_row_class:
- # TODO
- raise NotImplementedError
-
- def make_mobile_row_form_schema(self):
- if not self.model_row_class:
- # TODO
- raise NotImplementedError
-
- def get_mobile_form_fields(self):
- if hasattr(self, 'mobile_form_fields'):
- return self.mobile_form_fields
- # TODO
- # raise NotImplementedError
-
- def get_row_form_fields(self):
- if hasattr(self, 'row_form_fields'):
- return self.row_form_fields
- # TODO
- # raise NotImplementedError
-
- 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_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 make_row_form_kwargs(self, **kwargs):
- """
- Return a dictionary of kwargs to be passed to the factory when creating
- new row forms.
- """
- defaults = {
- 'request': self.request,
- 'readonly': self.viewing,
- 'model_class': getattr(self, 'model_row_class', None),
- 'action_url': self.request.current_route_url(_query=None),
- }
- if self.creating:
- kwargs.setdefault('cancel_url', self.request.get_referrer())
- else:
- instance = kwargs['model_instance']
- kwargs.setdefault('cancel_url', self.get_row_action_url('view', instance))
- defaults.update(kwargs)
- return defaults
-
- 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,
- '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_form(self, form):
- """
- Configure the primary mobile 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_labels(form)
-
- def configure_row_grid(self, grid):
- super(MasterView4, self).configure_row_grid(grid)
- self.set_row_labels(grid)
-
- def configure_row_form(self, form):
- """
- Configure a 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 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 set_row_labels(self, obj):
- for key, label in self.row_labels.items():
- obj.set_label(key, label)
-
- def validate_mobile_form(self, form):
- controls = self.request.POST.items()
- try:
- self.form_deserialized = form.validate(controls)
- except deform.ValidationFailure:
- return False
- return True
-
- def validate_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_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 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
-
- # TODO: still need to verify this logic
- def save_create_row_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)
- obj = self.objectify(form, self.form_deserialized)
- self.Session.add(obj)
- self.Session.flush()
- return obj
-
- def save_edit_row_form(self, form):
- obj = self.objectify(form, self.form_deserialized)
- self.after_edit_row(obj)
- self.Session.flush()
- return obj
diff --git a/tailbone/views/messages.py b/tailbone/views/messages.py
index d216e8ce..5d2060a5 100644
--- a/tailbone/views/messages.py
+++ b/tailbone/views/messages.py
@@ -37,7 +37,7 @@ from pyramid import httpexceptions
from webhelpers2.html import tags, HTML
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
from tailbone.util import raw_datetime
diff --git a/tailbone/views/people.py b/tailbone/views/people.py
index 3a3f1773..051d19af 100644
--- a/tailbone/views/people.py
+++ b/tailbone/views/people.py
@@ -34,7 +34,7 @@ from rattail.db import model, api
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from webhelpers2.html import HTML, tags
-from tailbone.views import MasterView4 as MasterView, AutocompleteView
+from tailbone.views import MasterView, AutocompleteView
class PeopleView(MasterView):
diff --git a/tailbone/views/principal.py b/tailbone/views/principal.py
index 494fda2d..dd82522b 100644
--- a/tailbone/views/principal.py
+++ b/tailbone/views/principal.py
@@ -35,7 +35,7 @@ import wtforms
from webhelpers2.html import HTML
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class PrincipalMasterView(MasterView):
diff --git a/tailbone/views/products.py b/tailbone/views/products.py
index f8e07a6d..48b445d7 100644
--- a/tailbone/views/products.py
+++ b/tailbone/views/products.py
@@ -47,7 +47,7 @@ from webhelpers2.html import tags, HTML
from tailbone import forms2 as forms, grids
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView, AutocompleteView
+from tailbone.views import MasterView, AutocompleteView
from tailbone.progress import SessionProgress
from tailbone.util import raw_datetime
diff --git a/tailbone/views/purchases/core.py b/tailbone/views/purchases/core.py
index c8d0ddc8..d214b53a 100644
--- a/tailbone/views/purchases/core.py
+++ b/tailbone/views/purchases/core.py
@@ -31,7 +31,7 @@ from rattail.db import model
from webhelpers2.html import HTML, tags
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class PurchaseView(MasterView):
diff --git a/tailbone/views/purchases/credits.py b/tailbone/views/purchases/credits.py
index 2188df3d..0d4b46ad 100644
--- a/tailbone/views/purchases/credits.py
+++ b/tailbone/views/purchases/credits.py
@@ -31,7 +31,7 @@ from rattail.db import model
from webhelpers2.html import tags
from tailbone import grids
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class PurchaseCreditView(MasterView):
diff --git a/tailbone/views/purchasing/batch.py b/tailbone/views/purchasing/batch.py
index 6a41cdca..d6699bd4 100644
--- a/tailbone/views/purchasing/batch.py
+++ b/tailbone/views/purchasing/batch.py
@@ -38,7 +38,7 @@ from webhelpers2.html import tags
# from tailbone import forms
from tailbone import forms2
-from tailbone.views.batch import BatchMasterView4 as BatchMasterView
+from tailbone.views.batch import BatchMasterView
class PurchasingBatchView(BatchMasterView):
diff --git a/tailbone/views/reportcodes.py b/tailbone/views/reportcodes.py
index db82a743..b50d138f 100644
--- a/tailbone/views/reportcodes.py
+++ b/tailbone/views/reportcodes.py
@@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class ReportCodesView(MasterView):
diff --git a/tailbone/views/settings.py b/tailbone/views/settings.py
index 5eff7d34..508fd9c6 100644
--- a/tailbone/views/settings.py
+++ b/tailbone/views/settings.py
@@ -32,7 +32,7 @@ from rattail.db import model
import colander
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class SettingsView(MasterView):
diff --git a/tailbone/views/shifts/core.py b/tailbone/views/shifts/core.py
index 0a15aa05..bb343143 100644
--- a/tailbone/views/shifts/core.py
+++ b/tailbone/views/shifts/core.py
@@ -33,7 +33,7 @@ import humanize
from rattail.db import model
from rattail.time import localtime
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
def render_shift_length(shift, field):
diff --git a/tailbone/views/stores.py b/tailbone/views/stores.py
index 7fd989b4..660e14e3 100644
--- a/tailbone/views/stores.py
+++ b/tailbone/views/stores.py
@@ -32,7 +32,7 @@ from rattail.db import model
import colander
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class StoresView(MasterView):
diff --git a/tailbone/views/subdepartments.py b/tailbone/views/subdepartments.py
index 31b6a43d..4550999f 100644
--- a/tailbone/views/subdepartments.py
+++ b/tailbone/views/subdepartments.py
@@ -29,7 +29,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class SubdepartmentsView(MasterView):
diff --git a/tailbone/views/tables.py b/tailbone/views/tables.py
index e8ddfc77..3c373de1 100644
--- a/tailbone/views/tables.py
+++ b/tailbone/views/tables.py
@@ -26,7 +26,7 @@ Views with info about the underlying Rattail tables
from __future__ import unicode_literals, absolute_import
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class TablesView(MasterView):
diff --git a/tailbone/views/taxes.py b/tailbone/views/taxes.py
index 4a26dd35..99177dea 100644
--- a/tailbone/views/taxes.py
+++ b/tailbone/views/taxes.py
@@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class TaxesView(MasterView):
diff --git a/tailbone/views/tempmon/core.py b/tailbone/views/tempmon/core.py
index 11a8cb55..6b85944e 100644
--- a/tailbone/views/tempmon/core.py
+++ b/tailbone/views/tempmon/core.py
@@ -32,7 +32,7 @@ from tailbone import views
from tailbone.db import TempmonSession
-class MasterView(views.MasterView4):
+class MasterView(views.MasterView):
"""
Base class for tempmon views.
"""
diff --git a/tailbone/views/trainwreck.py b/tailbone/views/trainwreck.py
index 6c851524..2c8c2c62 100644
--- a/tailbone/views/trainwreck.py
+++ b/tailbone/views/trainwreck.py
@@ -31,7 +31,7 @@ import six
from rattail.time import localtime
from tailbone.db import TrainwreckSession
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
class TransactionView(MasterView):
diff --git a/tailbone/views/upgrades.py b/tailbone/views/upgrades.py
index 13447ff7..733be540 100644
--- a/tailbone/views/upgrades.py
+++ b/tailbone/views/upgrades.py
@@ -43,7 +43,7 @@ from rattail.upgrades import get_upgrade_handler
from deform import widget as dfwidget
from webhelpers2.html import tags, HTML
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
from tailbone.progress import SessionProgress, get_progress_session
diff --git a/tailbone/views/users.py b/tailbone/views/users.py
index 48e05315..0e00e5f2 100644
--- a/tailbone/views/users.py
+++ b/tailbone/views/users.py
@@ -40,7 +40,7 @@ from webhelpers2.html import HTML, tags
from tailbone import forms2 as forms
from tailbone.db import Session
-from tailbone.views import MasterView4 as MasterView
+from tailbone.views import MasterView
from tailbone.views.principal import PrincipalMasterView, PermissionsRenderer
diff --git a/tailbone/views/vendors/catalogs.py b/tailbone/views/vendors/catalogs.py
index 16e8417a..a09110cf 100644
--- a/tailbone/views/vendors/catalogs.py
+++ b/tailbone/views/vendors/catalogs.py
@@ -39,7 +39,7 @@ from webhelpers2.html import tags
from tailbone import forms2 as forms
from tailbone.db import Session
-from tailbone.views.batch import FileBatchMasterView4 as FileBatchMasterView
+from tailbone.views.batch import FileBatchMasterView
log = logging.getLogger(__name__)
diff --git a/tailbone/views/vendors/core.py b/tailbone/views/vendors/core.py
index c36bcb25..60e43682 100644
--- a/tailbone/views/vendors/core.py
+++ b/tailbone/views/vendors/core.py
@@ -32,7 +32,7 @@ from rattail.db import model
from webhelpers2.html import tags
-from tailbone.views import MasterView4 as MasterView, AutocompleteView
+from tailbone.views import MasterView, AutocompleteView
class VendorsView(MasterView):
diff --git a/tailbone/views/vendors/invoices.py b/tailbone/views/vendors/invoices.py
index ad7dee11..dfc9f78b 100644
--- a/tailbone/views/vendors/invoices.py
+++ b/tailbone/views/vendors/invoices.py
@@ -33,7 +33,7 @@ from rattail.vendors.invoices import iter_invoice_parsers, require_invoice_parse
import colander
from deform import widget as dfwidget
-from tailbone.views.batch import FileBatchMasterView4 as FileBatchMasterView
+from tailbone.views.batch import FileBatchMasterView
class VendorInvoicesView(FileBatchMasterView):