Add basic "new model view" wizard
This commit is contained in:
parent
f4bc280da7
commit
00548a259b
10 changed files with 681 additions and 5 deletions
|
@ -2841,9 +2841,12 @@ class MasterView(View):
|
|||
|
||||
def make_grid_action_view(self):
|
||||
use_buefy = self.get_use_buefy()
|
||||
url = self.get_view_index_url if self.use_index_links else None
|
||||
icon = 'eye' if use_buefy else 'zoomin'
|
||||
return self.make_action('view', icon=icon, url=url)
|
||||
return self.make_action('view', icon=icon, url=self.default_view_url())
|
||||
|
||||
def default_view_url(self):
|
||||
if self.use_index_links:
|
||||
return self.get_view_index_url
|
||||
|
||||
def get_view_index_url(self, row, i):
|
||||
route = '{}.view_index'.format(self.get_route_prefix())
|
||||
|
@ -4978,6 +4981,22 @@ class MasterView(View):
|
|||
|
||||
# list/search
|
||||
if cls.listable:
|
||||
|
||||
# master views which represent a typical model class, and
|
||||
# allow for an index view, are registered specially so the
|
||||
# admin may browse the full list of such views
|
||||
modclass = cls.get_model_class(error=False)
|
||||
if modclass:
|
||||
config.add_tailbone_model_view(modclass.__name__,
|
||||
model_title_plural,
|
||||
route_prefix,
|
||||
permission_prefix)
|
||||
|
||||
# but regardless we register the index view, for similar reasons
|
||||
config.add_tailbone_index_page(route_prefix, model_title_plural,
|
||||
'{}.list'.format(permission_prefix))
|
||||
|
||||
# index view
|
||||
config.add_tailbone_permission(permission_prefix, '{}.list'.format(permission_prefix),
|
||||
"List / search {}".format(model_title_plural))
|
||||
config.add_route(route_prefix, '{}/'.format(url_prefix))
|
||||
|
@ -4985,8 +5004,6 @@ class MasterView(View):
|
|||
config.add_view(cls, attr='index', route_name=route_prefix,
|
||||
permission='{}.list'.format(permission_prefix),
|
||||
**kwargs)
|
||||
config.add_tailbone_index_page(route_prefix, model_title_plural,
|
||||
'{}.list'.format(permission_prefix))
|
||||
|
||||
# download results
|
||||
# this is the "new" more flexible approach, but we only want to
|
||||
|
|
|
@ -140,6 +140,11 @@ class AppInfoView(MasterView):
|
|||
{'section': 'rattail',
|
||||
'option': 'production',
|
||||
'type': bool},
|
||||
{'section': 'rattail',
|
||||
'option': 'running_from_source',
|
||||
'type': bool},
|
||||
{'section': 'rattail',
|
||||
'option': 'running_from_source.rootpkg'},
|
||||
|
||||
# display
|
||||
{'section': 'tailbone',
|
||||
|
|
|
@ -67,6 +67,7 @@ class TableView(MasterView):
|
|||
]
|
||||
|
||||
has_rows = True
|
||||
rows_title = "Columns"
|
||||
rows_pageable = False
|
||||
rows_filterable = False
|
||||
rows_viewable = False
|
||||
|
@ -170,6 +171,27 @@ class TableView(MasterView):
|
|||
def make_form_schema(self):
|
||||
return TableSchema()
|
||||
|
||||
def get_xref_buttons(self, table):
|
||||
buttons = super(TableView, self).get_xref_buttons(table)
|
||||
|
||||
if table.get('model_name'):
|
||||
all_views = self.request.registry.settings['tailbone_model_views']
|
||||
model_views = all_views.get(table['model_name'], [])
|
||||
for view in model_views:
|
||||
url = self.request.route_url(view['route_prefix'])
|
||||
buttons.append(self.make_xref_button(url=url, text=view['label'],
|
||||
internal=True))
|
||||
|
||||
if self.request.has_perm('model_views.create'):
|
||||
url = self.request.route_url('model_views.create',
|
||||
_query={'model_name': table['model_name']})
|
||||
buttons.append(self.make_buefy_button("New View",
|
||||
is_primary=True,
|
||||
url=url,
|
||||
icon_left='plus'))
|
||||
|
||||
return buttons
|
||||
|
||||
def template_kwargs_create(self, **kwargs):
|
||||
kwargs = super(TableView, self).template_kwargs_create(**kwargs)
|
||||
app = self.get_rattail_app()
|
||||
|
|
219
tailbone/views/views.py
Normal file
219
tailbone/views/views.py
Normal file
|
@ -0,0 +1,219 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Rattail -- Retail Software Framework
|
||||
# Copyright © 2010-2023 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/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Views for views
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals, absolute_import
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from rattail.db.util import get_fieldnames
|
||||
from rattail.util import simple_error
|
||||
|
||||
import colander
|
||||
from deform import widget as dfwidget
|
||||
|
||||
from tailbone.views import MasterView
|
||||
|
||||
|
||||
class ModelViewView(MasterView):
|
||||
"""
|
||||
Master view for views
|
||||
"""
|
||||
normalized_model_name = 'model_view'
|
||||
model_key = 'route_prefix'
|
||||
model_title = "Model View"
|
||||
url_prefix = '/views/model'
|
||||
viewable = True
|
||||
creatable = True
|
||||
editable = False
|
||||
deletable = False
|
||||
filterable = False
|
||||
pageable = False
|
||||
|
||||
grid_columns = [
|
||||
'label',
|
||||
'model_name',
|
||||
'route_prefix',
|
||||
'permission_prefix',
|
||||
]
|
||||
|
||||
def get_data(self, **kwargs):
|
||||
"""
|
||||
Fetch existing model views from app registry
|
||||
"""
|
||||
data = []
|
||||
|
||||
all_views = self.request.registry.settings['tailbone_model_views']
|
||||
for model_name in sorted(all_views):
|
||||
model_views = all_views[model_name]
|
||||
for view in model_views:
|
||||
data.append({
|
||||
'model_name': model_name,
|
||||
'label': view['label'],
|
||||
'route_prefix': view['route_prefix'],
|
||||
'permission_prefix': view['permission_prefix'],
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
def configure_grid(self, g):
|
||||
super(ModelViewView, self).configure_grid(g)
|
||||
|
||||
# label
|
||||
g.sorters['label'] = g.make_simple_sorter('label')
|
||||
g.set_sort_defaults('label')
|
||||
g.set_link('label')
|
||||
|
||||
# model_name
|
||||
g.sorters['model_name'] = g.make_simple_sorter('model_name', foldcase=True)
|
||||
g.set_searchable('model_name')
|
||||
|
||||
# route
|
||||
g.sorters['route'] = g.make_simple_sorter('route')
|
||||
|
||||
# permission
|
||||
g.sorters['permission'] = g.make_simple_sorter('permission')
|
||||
|
||||
def default_view_url(self, view, i=None):
|
||||
return self.request.route_url(view['route_prefix'])
|
||||
|
||||
def make_form_schema(self):
|
||||
return ModelViewSchema()
|
||||
|
||||
def template_kwargs_create(self, **kwargs):
|
||||
kwargs = super(ModelViewView, self).template_kwargs_create(**kwargs)
|
||||
app = self.get_rattail_app()
|
||||
db_handler = app.get_db_handler()
|
||||
|
||||
model_classes = db_handler.get_model_classes()
|
||||
kwargs['model_names'] = [cls.__name__ for cls in model_classes]
|
||||
|
||||
pkg = self.rattail_config.get('rattail', 'running_from_source.rootpkg')
|
||||
if pkg:
|
||||
kwargs['pkgroot'] = pkg
|
||||
pkg = sys.modules[pkg]
|
||||
pkgdir = os.path.dirname(pkg.__file__)
|
||||
kwargs['view_dir'] = os.path.join(pkgdir, 'web', 'views') + os.sep
|
||||
else:
|
||||
kwargs['pkgroot'] = 'poser'
|
||||
kwargs['view_dir'] = '??' + os.sep
|
||||
|
||||
return kwargs
|
||||
|
||||
def write_view_file(self):
|
||||
data = self.request.json_body
|
||||
path = data['view_file']
|
||||
|
||||
if os.path.exists(path):
|
||||
if data['overwrite']:
|
||||
os.remove(path)
|
||||
else:
|
||||
return {'error': "File already exists"}
|
||||
|
||||
app = self.get_rattail_app()
|
||||
tb = app.get_tailbone_handler()
|
||||
model_class = getattr(self.model, data['model_name'])
|
||||
|
||||
data['model_module_name'] = self.model.__name__
|
||||
data['model_title_plural'] = getattr(model_class,
|
||||
'model_title_plural',
|
||||
# TODO
|
||||
model_class.__name__)
|
||||
|
||||
data['model_versioned'] = hasattr(model_class, '__versioned__')
|
||||
|
||||
fieldnames = get_fieldnames(self.rattail_config,
|
||||
model_class)
|
||||
fieldnames.remove('uuid')
|
||||
data['model_fieldnames'] = fieldnames
|
||||
|
||||
tb.write_model_view(data, path)
|
||||
|
||||
return {'ok': True}
|
||||
|
||||
def check_view(self):
|
||||
data = self.request.json_body
|
||||
|
||||
try:
|
||||
url = self.request.route_url(data['route_prefix'])
|
||||
except Exception as error:
|
||||
return {'ok': True,
|
||||
'problem': simple_error(error)}
|
||||
|
||||
return {'ok': True, 'url': url}
|
||||
|
||||
@classmethod
|
||||
def defaults(cls, config):
|
||||
rattail_config = config.registry.settings.get('rattail_config')
|
||||
|
||||
# allow creating views only if *not* production
|
||||
if not rattail_config.production():
|
||||
cls.creatable = True
|
||||
|
||||
cls._model_view_defaults(config)
|
||||
cls._defaults(config)
|
||||
|
||||
@classmethod
|
||||
def _model_view_defaults(cls, config):
|
||||
route_prefix = cls.get_route_prefix()
|
||||
url_prefix = cls.get_url_prefix()
|
||||
permission_prefix = cls.get_permission_prefix()
|
||||
|
||||
if cls.creatable:
|
||||
|
||||
# write view class to file
|
||||
config.add_route('{}.write_view_file'.format(route_prefix),
|
||||
'{}/write-view-file'.format(url_prefix),
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='write_view_file',
|
||||
route_name='{}.write_view_file'.format(route_prefix),
|
||||
renderer='json',
|
||||
permission='{}.create'.format(permission_prefix))
|
||||
|
||||
# check view
|
||||
config.add_route('{}.check_view'.format(route_prefix),
|
||||
'{}/check-view'.format(url_prefix),
|
||||
request_method='POST')
|
||||
config.add_view(cls, attr='check_view',
|
||||
route_name='{}.check_view'.format(route_prefix),
|
||||
renderer='json',
|
||||
permission='{}.create'.format(permission_prefix))
|
||||
|
||||
|
||||
class ModelViewSchema(colander.Schema):
|
||||
|
||||
model_name = colander.SchemaNode(colander.String())
|
||||
|
||||
|
||||
def defaults(config, **kwargs):
|
||||
base = globals()
|
||||
|
||||
ModelViewView = kwargs.get('ModelViewView', base['ModelViewView'])
|
||||
ModelViewView.defaults(config)
|
||||
|
||||
|
||||
def includeme(config):
|
||||
defaults(config)
|
Loading…
Add table
Add a link
Reference in a new issue