Collapse all master4 views back to just 'master'

This commit is contained in:
Lance Edgar 2018-02-05 21:23:23 -06:00
parent 7c62b6f7a7
commit 2219315ccc
51 changed files with 1096 additions and 1613 deletions

View file

@ -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

View file

@ -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))

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
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
"""

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
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)

View file

@ -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 <http://www.gnu.org/licenses/>.
#
################################################################################
"""
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
"""

View file

@ -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):

View file

@ -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):