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

@ -28,9 +28,6 @@ from __future__ import unicode_literals, absolute_import
from .core import View from .core import View
from .master import MasterView from .master import MasterView
from .master2 import MasterView2
from .master3 import MasterView3
from .master4 import MasterView4
# TODO: deprecate / remove some of this # TODO: deprecate / remove some of this
from .autocomplete import AutocompleteView from .autocomplete import AutocompleteView

View file

@ -27,6 +27,3 @@ Views for batches
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
from .core import BatchMasterView, FileBatchMasterView 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 mobile_rows_viewable = True
has_worksheet = False 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): def __init__(self, request):
super(BatchMasterView, self).__init__(request) super(BatchMasterView, self).__init__(request)
self.handler = self.get_handler() self.handler = self.get_handler()
@ -114,6 +141,44 @@ class BatchMasterView(MasterView):
def allow_worksheet(self, batch): def allow_worksheet(self, batch):
return not batch.executed and not batch.complete 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): def template_kwargs_index(self, **kwargs):
kwargs['execute_enabled'] = self.instance_executable(None) kwargs['execute_enabled'] = self.instance_executable(None)
if kwargs['execute_enabled'] and self.has_execution_options(): 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') filters['status'] = MobileBatchStatusFilter(self.model_class, 'status', default_value='pending')
return filters 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): def _preconfigure_fieldset(self, fs):
""" """
Apply some commonly-useful pre-configuration to the main batch Apply some commonly-useful pre-configuration to the main batch
@ -240,67 +381,93 @@ class BatchMasterView(MasterView):
download_url=download_url) download_url=download_url)
fs.append(fa.Field(name, **kwargs)) 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): def save_create_form(self, form):
self.before_create(form) self.before_create(form)
with Session.no_autoflush: session = self.Session()
with session.no_autoflush:
# transfer form data to batch instance # transfer form data to batch instance
form.fieldset.sync() batch = self.objectify(form, self.form_deserialized)
batch = form.fieldset.model
# current user is batch creator # current user is batch creator
batch.created_by = self.request.user or self.late_login_user() 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) 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... # TODO: this needs work yet surely...
# if batch has input data file, let handler properly establish that # if batch has input data file, let handler properly establish that
filename = getattr(batch, 'filename', None) if 'filename' in form.schema:
if filename: if filedict:
path = os.path.join(self.upload_dir, filename) self.handler.set_input_file(batch, filepath)
if os.path.exists(path): os.remove(filepath)
self.handler.set_input_file(batch, path) os.rmdir(tempdir)
os.remove(path)
# return this object to replace the original
return batch return batch
# TODO: this is a totaly copy of save_create_form()
def save_mobile_create_form(self, form): def save_mobile_create_form(self, form):
self.before_create(form) self.before_create(form)
session = self.Session()
with Session.no_autoflush: with session.no_autoflush:
# transfer form data to batch instance # transfer form data to batch instance
form.fieldset.sync() batch = self.objectify(form, self.form_deserialized)
batch = form.fieldset.model
# current user is batch creator # 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 # destroy initial batch and re-make using handler
kwargs = self.get_batch_kwargs(batch) kwargs = self.get_batch_kwargs(batch)
Session.expunge(batch) if batch in session:
batch = self.handler.make_batch(Session(), **kwargs) session.expunge(batch)
batch = self.handler.make_batch(session, **kwargs)
Session.flush() 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
return batch return batch
def get_batch_kwargs(self, batch, mobile=False): def get_batch_kwargs(self, batch, mobile=False):
@ -367,6 +534,35 @@ class BatchMasterView(MasterView):
""" """
return not batch.executed 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): def create_row(self):
""" """
Only allow creating a new row if the batch hasn't yet been executed. 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 self.redirect(self.get_action_url('view', batch, mobile=True))
return super(BatchMasterView, self).mobile_create_row() return super(BatchMasterView, self).mobile_create_row()
def before_create_row(self, form): def save_create_row_form(self, form):
batch = self.get_instance() batch = self.get_instance()
row = form.fieldset.model row = self.objectify(form, self.form_deserialized)
self.handler.add_row(batch, row) self.handler.add_row(batch, row)
self.Session.flush()
return row
def after_create_row(self, row): def after_create_row(self, row):
self.handler.refresh_row(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): def make_default_row_grid_tools(self, batch):
if self.rows_creatable and not batch.executed: if self.rows_creatable and not batch.executed:
permission_prefix = self.get_permission_prefix() permission_prefix = self.get_permission_prefix()
@ -1007,6 +1231,26 @@ class FileBatchMasterView(BatchMasterView):
os.makedirs(uploads) os.makedirs(uploads)
return 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): def _preconfigure_fieldset(self, fs):
super(FileBatchMasterView, self)._preconfigure_fieldset(fs) super(FileBatchMasterView, self)._preconfigure_fieldset(fs)
fs.filename.set(label="Data File", renderer=FileFieldRenderer.new(self)) 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 rattail.db import model
from tailbone.views.batch import BatchMasterView4 as BatchMasterView from tailbone.views.batch import BatchMasterView
class ImporterBatchView(BatchMasterView): class ImporterBatchView(BatchMasterView):

View file

@ -30,7 +30,7 @@ from rattail.db import model
from webhelpers2.html import tags from webhelpers2.html import tags
from tailbone.views.batch import BatchMasterView4 as BatchMasterView from tailbone.views.batch import BatchMasterView
class PricingBatchView(BatchMasterView): class PricingBatchView(BatchMasterView):

View file

@ -37,7 +37,7 @@ from pyramid.response import FileResponse
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
from tailbone import grids from tailbone import grids
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class EmailBouncesView(MasterView): class EmailBouncesView(MasterView):

View file

@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.views import MasterView4 as MasterView, AutocompleteView from tailbone.views import MasterView, AutocompleteView
class BrandsView(MasterView): class BrandsView(MasterView):

View file

@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class CategoriesView(MasterView): class CategoriesView(MasterView):

View file

@ -29,7 +29,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class CustomerGroupsView(MasterView): class CustomerGroupsView(MasterView):

View file

@ -39,7 +39,7 @@ from webhelpers2.html import HTML, tags
from tailbone import grids from tailbone import grids
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView, AutocompleteView from tailbone.views import MasterView, AutocompleteView
from rattail.db import model from rattail.db import model

View file

@ -33,7 +33,7 @@ from sqlalchemy import orm
from rattail.db import model from rattail.db import model
from rattail.time import localtime from rattail.time import localtime
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
from tailbone.util import raw_datetime from tailbone.util import raw_datetime

View file

@ -34,7 +34,7 @@ from rattail.db import model
from webhelpers2.html import tags from webhelpers2.html import tags
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class CustomerOrdersView(MasterView): class CustomerOrdersView(MasterView):

View file

@ -31,7 +31,7 @@ import logging
from rattail.db import model from rattail.db import model
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -33,7 +33,7 @@ from rattail.db import model
from deform import widget as dfwidget from deform import widget as dfwidget
from tailbone import grids from tailbone import grids
from tailbone.views import MasterView4 as MasterView, AutocompleteView from tailbone.views import MasterView, AutocompleteView
class DepartmentsView(MasterView): class DepartmentsView(MasterView):

View file

@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class DepositLinksView(MasterView): class DepositLinksView(MasterView):

View file

@ -37,7 +37,7 @@ from deform import widget as dfwidget
from webhelpers2.html import HTML from webhelpers2.html import HTML
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import View, MasterView4 as MasterView from tailbone.views import View, MasterView
class ProfilesView(MasterView): class ProfilesView(MasterView):

View file

@ -37,7 +37,7 @@ from webhelpers2.html import tags, HTML
from tailbone import grids from tailbone import grids
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView, AutocompleteView from tailbone.views import MasterView, AutocompleteView
class EmployeesView(MasterView): class EmployeesView(MasterView):

View file

@ -36,7 +36,7 @@ from pyramid.response import FileResponse
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
from tailbone import forms2 as forms from tailbone import forms2 as forms
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class ExportMasterView(MasterView): class ExportMasterView(MasterView):

View file

@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class FamiliesView(MasterView): class FamiliesView(MasterView):

View file

@ -35,7 +35,7 @@ import formencode as fe
from webhelpers2.html import tags from webhelpers2.html import tags
from tailbone.db import Session from tailbone.db import Session
from tailbone.views.batch import FileBatchMasterView4 as FileBatchMasterView from tailbone.views.batch import FileBatchMasterView
ACTION_OPTIONS = OrderedDict([ ACTION_OPTIONS = OrderedDict([

View file

@ -43,8 +43,8 @@ from deform import widget as dfwidget
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
from tailbone import forms, forms2, grids from tailbone import forms, forms2, grids
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
from tailbone.views.batch import BatchMasterView4 as BatchMasterView from tailbone.views.batch import BatchMasterView
class InventoryAdjustmentReasonsView(MasterView): class InventoryAdjustmentReasonsView(MasterView):

View file

@ -30,7 +30,7 @@ from rattail.db import model
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
from tailbone.views.batch import BatchMasterView4 as BatchMasterView from tailbone.views.batch import BatchMasterView
class LabelBatchView(BatchMasterView): class LabelBatchView(BatchMasterView):

View file

@ -31,7 +31,7 @@ from rattail.db import model
from pyramid.httpexceptions import HTTPFound from pyramid.httpexceptions import HTTPFound
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class ProfilesView(MasterView): class ProfilesView(MasterView):

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

@ -37,7 +37,7 @@ from pyramid import httpexceptions
from webhelpers2.html import tags, HTML from webhelpers2.html import tags, HTML
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
from tailbone.util import raw_datetime from tailbone.util import raw_datetime

View file

@ -34,7 +34,7 @@ from rattail.db import model, api
from pyramid.httpexceptions import HTTPFound, HTTPNotFound from pyramid.httpexceptions import HTTPFound, HTTPNotFound
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
from tailbone.views import MasterView4 as MasterView, AutocompleteView from tailbone.views import MasterView, AutocompleteView
class PeopleView(MasterView): class PeopleView(MasterView):

View file

@ -35,7 +35,7 @@ import wtforms
from webhelpers2.html import HTML from webhelpers2.html import HTML
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class PrincipalMasterView(MasterView): class PrincipalMasterView(MasterView):

View file

@ -47,7 +47,7 @@ from webhelpers2.html import tags, HTML
from tailbone import forms2 as forms, grids from tailbone import forms2 as forms, grids
from tailbone.db import Session 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.progress import SessionProgress
from tailbone.util import raw_datetime from tailbone.util import raw_datetime

View file

@ -31,7 +31,7 @@ from rattail.db import model
from webhelpers2.html import HTML, tags from webhelpers2.html import HTML, tags
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class PurchaseView(MasterView): class PurchaseView(MasterView):

View file

@ -31,7 +31,7 @@ from rattail.db import model
from webhelpers2.html import tags from webhelpers2.html import tags
from tailbone import grids from tailbone import grids
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class PurchaseCreditView(MasterView): class PurchaseCreditView(MasterView):

View file

@ -38,7 +38,7 @@ from webhelpers2.html import tags
# from tailbone import forms # from tailbone import forms
from tailbone import forms2 from tailbone import forms2
from tailbone.views.batch import BatchMasterView4 as BatchMasterView from tailbone.views.batch import BatchMasterView
class PurchasingBatchView(BatchMasterView): class PurchasingBatchView(BatchMasterView):

View file

@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class ReportCodesView(MasterView): class ReportCodesView(MasterView):

View file

@ -32,7 +32,7 @@ from rattail.db import model
import colander import colander
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class SettingsView(MasterView): class SettingsView(MasterView):

View file

@ -33,7 +33,7 @@ import humanize
from rattail.db import model from rattail.db import model
from rattail.time import localtime from rattail.time import localtime
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
def render_shift_length(shift, field): def render_shift_length(shift, field):

View file

@ -32,7 +32,7 @@ from rattail.db import model
import colander import colander
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class StoresView(MasterView): class StoresView(MasterView):

View file

@ -29,7 +29,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class SubdepartmentsView(MasterView): class SubdepartmentsView(MasterView):

View file

@ -26,7 +26,7 @@ Views with info about the underlying Rattail tables
from __future__ import unicode_literals, absolute_import from __future__ import unicode_literals, absolute_import
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class TablesView(MasterView): class TablesView(MasterView):

View file

@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import
from rattail.db import model from rattail.db import model
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class TaxesView(MasterView): class TaxesView(MasterView):

View file

@ -32,7 +32,7 @@ from tailbone import views
from tailbone.db import TempmonSession from tailbone.db import TempmonSession
class MasterView(views.MasterView4): class MasterView(views.MasterView):
""" """
Base class for tempmon views. Base class for tempmon views.
""" """

View file

@ -31,7 +31,7 @@ import six
from rattail.time import localtime from rattail.time import localtime
from tailbone.db import TrainwreckSession from tailbone.db import TrainwreckSession
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
class TransactionView(MasterView): class TransactionView(MasterView):

View file

@ -43,7 +43,7 @@ from rattail.upgrades import get_upgrade_handler
from deform import widget as dfwidget from deform import widget as dfwidget
from webhelpers2.html import tags, HTML 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 from tailbone.progress import SessionProgress, get_progress_session

View file

@ -40,7 +40,7 @@ from webhelpers2.html import HTML, tags
from tailbone import forms2 as forms from tailbone import forms2 as forms
from tailbone.db import Session from tailbone.db import Session
from tailbone.views import MasterView4 as MasterView from tailbone.views import MasterView
from tailbone.views.principal import PrincipalMasterView, PermissionsRenderer from tailbone.views.principal import PrincipalMasterView, PermissionsRenderer

View file

@ -39,7 +39,7 @@ from webhelpers2.html import tags
from tailbone import forms2 as forms from tailbone import forms2 as forms
from tailbone.db import Session from tailbone.db import Session
from tailbone.views.batch import FileBatchMasterView4 as FileBatchMasterView from tailbone.views.batch import FileBatchMasterView
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

View file

@ -32,7 +32,7 @@ from rattail.db import model
from webhelpers2.html import tags from webhelpers2.html import tags
from tailbone.views import MasterView4 as MasterView, AutocompleteView from tailbone.views import MasterView, AutocompleteView
class VendorsView(MasterView): class VendorsView(MasterView):

View file

@ -33,7 +33,7 @@ from rattail.vendors.invoices import iter_invoice_parsers, require_invoice_parse
import colander import colander
from deform import widget as dfwidget from deform import widget as dfwidget
from tailbone.views.batch import FileBatchMasterView4 as FileBatchMasterView from tailbone.views.batch import FileBatchMasterView
class VendorInvoicesView(FileBatchMasterView): class VendorInvoicesView(FileBatchMasterView):