feat: add basic support for SQLAlchemy model in master view
must more to be done for this yet, but basics are in place for the Setting view
This commit is contained in:
parent
73014964cb
commit
fc01fa283a
|
@ -32,7 +32,7 @@ import deform
|
||||||
from pyramid.renderers import render
|
from pyramid.renderers import render
|
||||||
from webhelpers2.html import HTML
|
from webhelpers2.html import HTML
|
||||||
|
|
||||||
from wuttaweb.util import get_form_data
|
from wuttaweb.util import get_form_data, get_model_fields
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -126,9 +126,10 @@ class Form:
|
||||||
|
|
||||||
.. attribute:: model_class
|
.. attribute:: model_class
|
||||||
|
|
||||||
Optional "class" for the model. If set, this usually would be
|
Model class for the form, if applicable. When set, this is
|
||||||
a SQLAlchemy mapped class. This may be used instead of
|
usually a SQLAlchemy mapped class. This (or
|
||||||
specifying the :attr:`schema`.
|
:attr:`model_instance`) may be used instead of specifying the
|
||||||
|
:attr:`schema`.
|
||||||
|
|
||||||
.. attribute:: model_instance
|
.. attribute:: model_instance
|
||||||
|
|
||||||
|
@ -309,11 +310,12 @@ class Form:
|
||||||
|
|
||||||
self.model_class = model_class
|
self.model_class = model_class
|
||||||
self.model_instance = model_instance
|
self.model_instance = model_instance
|
||||||
|
if self.model_instance and not self.model_class:
|
||||||
|
self.model_class = type(self.model_instance)
|
||||||
|
|
||||||
if fields is not None:
|
fields = fields or self.get_fields()
|
||||||
|
if fields:
|
||||||
self.set_fields(fields)
|
self.set_fields(fields)
|
||||||
elif self.schema:
|
|
||||||
self.set_fields([f.name for f in self.schema])
|
|
||||||
else:
|
else:
|
||||||
self.fields = None
|
self.fields = None
|
||||||
|
|
||||||
|
@ -485,6 +487,43 @@ class Form:
|
||||||
"""
|
"""
|
||||||
return self.labels.get(key, self.app.make_title(key))
|
return self.labels.get(key, self.app.make_title(key))
|
||||||
|
|
||||||
|
def get_fields(self):
|
||||||
|
"""
|
||||||
|
Returns the official list of field names for the form, or
|
||||||
|
``None``.
|
||||||
|
|
||||||
|
If :attr:`fields` is set and non-empty, it is returned.
|
||||||
|
|
||||||
|
Or, if :attr:`schema` is set, the field list is derived
|
||||||
|
from that.
|
||||||
|
|
||||||
|
Or, if :attr:`model_class` is set, the field list is derived
|
||||||
|
from that, via :meth:`get_model_fields()`.
|
||||||
|
|
||||||
|
Otherwise ``None`` is returned.
|
||||||
|
"""
|
||||||
|
if hasattr(self, 'fields') and self.fields:
|
||||||
|
return self.fields
|
||||||
|
|
||||||
|
if self.schema:
|
||||||
|
return [field.name for field in self.schema]
|
||||||
|
|
||||||
|
fields = self.get_model_fields()
|
||||||
|
if fields:
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def get_model_fields(self, model_class=None):
|
||||||
|
"""
|
||||||
|
This method is a shortcut which calls
|
||||||
|
:func:`~wuttaweb.util.get_model_fields()`.
|
||||||
|
|
||||||
|
:param model_class: Optional model class for which to return
|
||||||
|
fields. If not set, the form's :attr:`model_class` is
|
||||||
|
assumed.
|
||||||
|
"""
|
||||||
|
return get_model_fields(self.config,
|
||||||
|
model_class=model_class or self.model_class)
|
||||||
|
|
||||||
def get_schema(self):
|
def get_schema(self):
|
||||||
"""
|
"""
|
||||||
Return the :class:`colander:colander.Schema` object for the
|
Return the :class:`colander:colander.Schema` object for the
|
||||||
|
@ -495,9 +534,14 @@ class Form:
|
||||||
"""
|
"""
|
||||||
if not self.schema:
|
if not self.schema:
|
||||||
|
|
||||||
if self.fields:
|
# get fields
|
||||||
|
fields = self.get_fields()
|
||||||
|
if not fields:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# make basic schema
|
||||||
schema = colander.Schema()
|
schema = colander.Schema()
|
||||||
for name in self.fields:
|
for name in fields:
|
||||||
schema.add(colander.SchemaNode(
|
schema.add(colander.SchemaNode(
|
||||||
colander.String(),
|
colander.String(),
|
||||||
name=name))
|
name=name))
|
||||||
|
@ -512,9 +556,6 @@ class Form:
|
||||||
|
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
|
||||||
else: # no fields
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
return self.schema
|
return self.schema
|
||||||
|
|
||||||
def get_deform(self):
|
def get_deform(self):
|
||||||
|
@ -523,12 +564,21 @@ class Form:
|
||||||
generating it automatically if necessary.
|
generating it automatically if necessary.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, 'deform_form'):
|
if not hasattr(self, 'deform_form'):
|
||||||
|
model = self.app.model
|
||||||
schema = self.get_schema()
|
schema = self.get_schema()
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
if self.model_instance:
|
if self.model_instance:
|
||||||
|
if isinstance(self.model_instance, model.Base):
|
||||||
|
kwargs['appstruct'] = dict(self.model_instance)
|
||||||
|
else:
|
||||||
kwargs['appstruct'] = self.model_instance
|
kwargs['appstruct'] = self.model_instance
|
||||||
|
|
||||||
|
# TODO: ugh why is this necessary?
|
||||||
|
for key, value in list(kwargs['appstruct'].items()):
|
||||||
|
if value is None:
|
||||||
|
kwargs['appstruct'][key] = colander.null
|
||||||
|
|
||||||
form = deform.Form(schema, **kwargs)
|
form = deform.Form(schema, **kwargs)
|
||||||
self.deform_form = form
|
self.deform_form = form
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ from pyramid.renderers import render
|
||||||
from webhelpers2.html import HTML
|
from webhelpers2.html import HTML
|
||||||
|
|
||||||
from wuttaweb.forms import FieldList
|
from wuttaweb.forms import FieldList
|
||||||
|
from wuttaweb.util import get_model_fields
|
||||||
|
|
||||||
|
|
||||||
class Grid:
|
class Grid:
|
||||||
|
@ -52,13 +53,19 @@ class Grid:
|
||||||
Presumably unique key for the grid; used to track per-grid
|
Presumably unique key for the grid; used to track per-grid
|
||||||
sort/filter settings etc.
|
sort/filter settings etc.
|
||||||
|
|
||||||
|
.. attribute:: model_class
|
||||||
|
|
||||||
|
Model class for the grid, if applicable. When set, this is
|
||||||
|
usually a SQLAlchemy mapped class. This may be used for
|
||||||
|
deriving the default :attr:`columns` among other things.
|
||||||
|
|
||||||
.. attribute:: columns
|
.. attribute:: columns
|
||||||
|
|
||||||
:class:`~wuttaweb.forms.base.FieldList` instance containing
|
:class:`~wuttaweb.forms.base.FieldList` instance containing
|
||||||
string column names for the grid. Columns will appear in the
|
string column names for the grid. Columns will appear in the
|
||||||
same order as they are in this list.
|
same order as they are in this list.
|
||||||
|
|
||||||
See also :meth:`set_columns()`.
|
See also :meth:`set_columns()` and :meth:`get_columns()`.
|
||||||
|
|
||||||
.. attribute:: data
|
.. attribute:: data
|
||||||
|
|
||||||
|
@ -88,6 +95,7 @@ class Grid:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
request,
|
request,
|
||||||
|
model_class=None,
|
||||||
key=None,
|
key=None,
|
||||||
columns=None,
|
columns=None,
|
||||||
data=None,
|
data=None,
|
||||||
|
@ -96,6 +104,7 @@ class Grid:
|
||||||
vue_tagname='wutta-grid',
|
vue_tagname='wutta-grid',
|
||||||
):
|
):
|
||||||
self.request = request
|
self.request = request
|
||||||
|
self.model_class = model_class
|
||||||
self.key = key
|
self.key = key
|
||||||
self.data = data
|
self.data = data
|
||||||
self.actions = actions or []
|
self.actions = actions or []
|
||||||
|
@ -105,11 +114,43 @@ class Grid:
|
||||||
self.config = self.request.wutta_config
|
self.config = self.request.wutta_config
|
||||||
self.app = self.config.get_app()
|
self.app = self.config.get_app()
|
||||||
|
|
||||||
if columns is not None:
|
columns = columns or self.get_columns()
|
||||||
|
if columns:
|
||||||
self.set_columns(columns)
|
self.set_columns(columns)
|
||||||
else:
|
else:
|
||||||
self.columns = None
|
self.columns = None
|
||||||
|
|
||||||
|
def get_columns(self):
|
||||||
|
"""
|
||||||
|
Returns the official list of column names for the grid, or
|
||||||
|
``None``.
|
||||||
|
|
||||||
|
If :attr:`columns` is set and non-empty, it is returned.
|
||||||
|
|
||||||
|
Or, if :attr:`model_class` is set, the field list is derived
|
||||||
|
from that, via :meth:`get_model_columns()`.
|
||||||
|
|
||||||
|
Otherwise ``None`` is returned.
|
||||||
|
"""
|
||||||
|
if hasattr(self, 'columns') and self.columns:
|
||||||
|
return self.columns
|
||||||
|
|
||||||
|
columns = self.get_model_columns()
|
||||||
|
if columns:
|
||||||
|
return columns
|
||||||
|
|
||||||
|
def get_model_columns(self, model_class=None):
|
||||||
|
"""
|
||||||
|
This method is a shortcut which calls
|
||||||
|
:func:`~wuttaweb.util.get_model_fields()`.
|
||||||
|
|
||||||
|
:param model_class: Optional model class for which to return
|
||||||
|
fields. If not set, the grid's :attr:`model_class` is
|
||||||
|
assumed.
|
||||||
|
"""
|
||||||
|
return get_model_fields(self.config,
|
||||||
|
model_class=model_class or self.model_class)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vue_component(self):
|
def vue_component(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -357,3 +357,22 @@ def render_csrf_token(request, name='_csrf'):
|
||||||
"""
|
"""
|
||||||
token = get_csrf_token(request)
|
token = get_csrf_token(request)
|
||||||
return HTML.tag('div', tags.hidden(name, value=token), style='display:none;')
|
return HTML.tag('div', tags.hidden(name, value=token), style='display:none;')
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_fields(config, model_class=None):
|
||||||
|
"""
|
||||||
|
Convenience function to return a list of field names for the given
|
||||||
|
model class.
|
||||||
|
|
||||||
|
This logic only supports SQLAlchemy mapped classes and will use
|
||||||
|
that to determine the field listing if applicable. Otherwise this
|
||||||
|
returns ``None``.
|
||||||
|
"""
|
||||||
|
if model_class:
|
||||||
|
import sqlalchemy as sa
|
||||||
|
app = config.get_app()
|
||||||
|
model = app.model
|
||||||
|
if model_class and issubclass(model_class, model.Base):
|
||||||
|
mapper = sa.inspect(model_class)
|
||||||
|
fields = list([prop.key for prop in mapper.iterate_properties])
|
||||||
|
return fields
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
Base Logic for Master Views
|
Base Logic for Master Views
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
|
||||||
from pyramid.renderers import render_to_response
|
from pyramid.renderers import render_to_response
|
||||||
|
|
||||||
from wuttaweb.views import View
|
from wuttaweb.views import View
|
||||||
|
@ -166,7 +169,7 @@ class MasterView(View):
|
||||||
|
|
||||||
List of columns for the :meth:`index()` view grid.
|
List of columns for the :meth:`index()` view grid.
|
||||||
|
|
||||||
This is optional; see also :meth:`index_get_grid_columns()`.
|
This is optional; see also :meth:`get_grid_columns()`.
|
||||||
|
|
||||||
.. attribute:: creatable
|
.. attribute:: creatable
|
||||||
|
|
||||||
|
@ -246,7 +249,7 @@ class MasterView(View):
|
||||||
|
|
||||||
See also related methods, which are called by this one:
|
See also related methods, which are called by this one:
|
||||||
|
|
||||||
* :meth:`index_make_grid()`
|
* :meth:`make_model_grid()`
|
||||||
"""
|
"""
|
||||||
self.listing = True
|
self.listing = True
|
||||||
|
|
||||||
|
@ -255,94 +258,10 @@ class MasterView(View):
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.has_grid:
|
if self.has_grid:
|
||||||
context['grid'] = self.index_make_grid()
|
context['grid'] = self.make_model_grid()
|
||||||
|
|
||||||
return self.render_to_response('index', context)
|
return self.render_to_response('index', context)
|
||||||
|
|
||||||
def index_make_grid(self, **kwargs):
|
|
||||||
"""
|
|
||||||
Create and return a :class:`~wuttaweb.grids.base.Grid`
|
|
||||||
instance for use with the :meth:`index()` view.
|
|
||||||
|
|
||||||
See also related methods, which are called by this one:
|
|
||||||
|
|
||||||
* :meth:`get_grid_key()`
|
|
||||||
* :meth:`index_get_grid_columns()`
|
|
||||||
* :meth:`index_get_grid_data()`
|
|
||||||
* :meth:`configure_grid()`
|
|
||||||
"""
|
|
||||||
if 'key' not in kwargs:
|
|
||||||
kwargs['key'] = self.get_grid_key()
|
|
||||||
|
|
||||||
if 'columns' not in kwargs:
|
|
||||||
kwargs['columns'] = self.index_get_grid_columns()
|
|
||||||
|
|
||||||
if 'data' not in kwargs:
|
|
||||||
kwargs['data'] = self.index_get_grid_data()
|
|
||||||
|
|
||||||
if 'actions' not in kwargs:
|
|
||||||
actions = []
|
|
||||||
|
|
||||||
# TODO: should split this off into index_get_grid_actions() ?
|
|
||||||
|
|
||||||
if self.viewable:
|
|
||||||
actions.append(self.make_grid_action('view', icon='eye',
|
|
||||||
url=self.get_action_url_view))
|
|
||||||
|
|
||||||
if self.editable:
|
|
||||||
actions.append(self.make_grid_action('edit', icon='edit',
|
|
||||||
url=self.get_action_url_edit))
|
|
||||||
|
|
||||||
if self.deletable:
|
|
||||||
actions.append(self.make_grid_action('delete', icon='trash',
|
|
||||||
url=self.get_action_url_delete,
|
|
||||||
link_class='has-text-danger'))
|
|
||||||
|
|
||||||
kwargs['actions'] = actions
|
|
||||||
|
|
||||||
grid = self.make_grid(**kwargs)
|
|
||||||
self.configure_grid(grid)
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def index_get_grid_columns(self):
|
|
||||||
"""
|
|
||||||
Returns the default list of grid column names, for the
|
|
||||||
:meth:`index()` view.
|
|
||||||
|
|
||||||
This is called by :meth:`index_make_grid()`; in the resulting
|
|
||||||
:class:`~wuttaweb.grids.base.Grid` instance, this becomes
|
|
||||||
:attr:`~wuttaweb.grids.base.Grid.columns`.
|
|
||||||
|
|
||||||
This method may return ``None``, in which case the grid may
|
|
||||||
(try to) generate its own default list.
|
|
||||||
|
|
||||||
Subclass may define :attr:`grid_columns` for simple cases, or
|
|
||||||
can override this method if needed.
|
|
||||||
|
|
||||||
Also note that :meth:`configure_grid()` may be used to further
|
|
||||||
modify the final column set, regardless of what this method
|
|
||||||
returns. So a common pattern is to declare all "supported"
|
|
||||||
columns by setting :attr:`grid_columns` but then optionally
|
|
||||||
remove or replace some of those within
|
|
||||||
:meth:`configure_grid()`.
|
|
||||||
"""
|
|
||||||
if hasattr(self, 'grid_columns'):
|
|
||||||
return self.grid_columns
|
|
||||||
|
|
||||||
def index_get_grid_data(self):
|
|
||||||
"""
|
|
||||||
Returns the grid data for the :meth:`index()` view.
|
|
||||||
|
|
||||||
This is called by :meth:`index_make_grid()`; in the resulting
|
|
||||||
:class:`~wuttaweb.grids.base.Grid` instance, this becomes
|
|
||||||
:attr:`~wuttaweb.grids.base.Grid.data`.
|
|
||||||
|
|
||||||
As of now there is not yet a "sane" default for this method;
|
|
||||||
it simply returns an empty list. Subclass should override as
|
|
||||||
needed.
|
|
||||||
"""
|
|
||||||
return []
|
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# create methods
|
# create methods
|
||||||
##############################
|
##############################
|
||||||
|
@ -573,7 +492,8 @@ class MasterView(View):
|
||||||
|
|
||||||
This method is called by :meth:`delete_save_form()`.
|
This method is called by :meth:`delete_save_form()`.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
session = self.app.get_session(obj)
|
||||||
|
session.delete(obj)
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# configure methods
|
# configure methods
|
||||||
|
@ -966,11 +886,119 @@ class MasterView(View):
|
||||||
route_prefix = self.get_route_prefix()
|
route_prefix = self.get_route_prefix()
|
||||||
return self.request.route_url(route_prefix, **kwargs)
|
return self.request.route_url(route_prefix, **kwargs)
|
||||||
|
|
||||||
|
def make_model_grid(self, session=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Create and return a :class:`~wuttaweb.grids.base.Grid`
|
||||||
|
instance for use with the :meth:`index()` view.
|
||||||
|
|
||||||
|
See also related methods, which are called by this one:
|
||||||
|
|
||||||
|
* :meth:`get_grid_key()`
|
||||||
|
* :meth:`get_grid_columns()`
|
||||||
|
* :meth:`get_grid_data()`
|
||||||
|
* :meth:`configure_grid()`
|
||||||
|
"""
|
||||||
|
if 'key' not in kwargs:
|
||||||
|
kwargs['key'] = self.get_grid_key()
|
||||||
|
|
||||||
|
if 'model_class' not in kwargs:
|
||||||
|
model_class = self.get_model_class()
|
||||||
|
if model_class:
|
||||||
|
kwargs['model_class'] = model_class
|
||||||
|
|
||||||
|
if 'columns' not in kwargs:
|
||||||
|
kwargs['columns'] = self.get_grid_columns()
|
||||||
|
|
||||||
|
if 'data' not in kwargs:
|
||||||
|
kwargs['data'] = self.get_grid_data(session=session)
|
||||||
|
|
||||||
|
if 'actions' not in kwargs:
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
# TODO: should split this off into index_get_grid_actions() ?
|
||||||
|
|
||||||
|
if self.viewable:
|
||||||
|
actions.append(self.make_grid_action('view', icon='eye',
|
||||||
|
url=self.get_action_url_view))
|
||||||
|
|
||||||
|
if self.editable:
|
||||||
|
actions.append(self.make_grid_action('edit', icon='edit',
|
||||||
|
url=self.get_action_url_edit))
|
||||||
|
|
||||||
|
if self.deletable:
|
||||||
|
actions.append(self.make_grid_action('delete', icon='trash',
|
||||||
|
url=self.get_action_url_delete,
|
||||||
|
link_class='has-text-danger'))
|
||||||
|
|
||||||
|
kwargs['actions'] = actions
|
||||||
|
|
||||||
|
grid = self.make_grid(**kwargs)
|
||||||
|
self.configure_grid(grid)
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def get_grid_columns(self):
|
||||||
|
"""
|
||||||
|
Returns the default list of grid column names, for the
|
||||||
|
:meth:`index()` view.
|
||||||
|
|
||||||
|
This is called by :meth:`make_model_grid()`; in the resulting
|
||||||
|
:class:`~wuttaweb.grids.base.Grid` instance, this becomes
|
||||||
|
:attr:`~wuttaweb.grids.base.Grid.columns`.
|
||||||
|
|
||||||
|
This method may return ``None``, in which case the grid may
|
||||||
|
(try to) generate its own default list.
|
||||||
|
|
||||||
|
Subclass may define :attr:`grid_columns` for simple cases, or
|
||||||
|
can override this method if needed.
|
||||||
|
|
||||||
|
Also note that :meth:`configure_grid()` may be used to further
|
||||||
|
modify the final column set, regardless of what this method
|
||||||
|
returns. So a common pattern is to declare all "supported"
|
||||||
|
columns by setting :attr:`grid_columns` but then optionally
|
||||||
|
remove or replace some of those within
|
||||||
|
:meth:`configure_grid()`.
|
||||||
|
"""
|
||||||
|
if hasattr(self, 'grid_columns'):
|
||||||
|
return self.grid_columns
|
||||||
|
|
||||||
|
def get_grid_data(self, session=None):
|
||||||
|
"""
|
||||||
|
Returns the grid data for the :meth:`index()` view.
|
||||||
|
|
||||||
|
This is called by :meth:`make_model_grid()`; in the resulting
|
||||||
|
:class:`~wuttaweb.grids.base.Grid` instance, this becomes
|
||||||
|
:attr:`~wuttaweb.grids.base.Grid.data`.
|
||||||
|
|
||||||
|
Default logic will call :meth:`get_query()` and if successful,
|
||||||
|
return the list from ``query.all()``. Otherwise returns an
|
||||||
|
empty list. Subclass should override as needed.
|
||||||
|
"""
|
||||||
|
query = self.get_query(session=session)
|
||||||
|
if query is not None:
|
||||||
|
return query.all()
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_query(self, session=None):
|
||||||
|
"""
|
||||||
|
Returns the main SQLAlchemy query object for the
|
||||||
|
:meth:`index()` view. This is called by
|
||||||
|
:meth:`get_grid_data()`.
|
||||||
|
|
||||||
|
Default logic for this method returns a "plain" query on the
|
||||||
|
:attr:`model_class` if that is defined; otherwise ``None``.
|
||||||
|
"""
|
||||||
|
model = self.app.model
|
||||||
|
model_class = self.get_model_class()
|
||||||
|
if model_class and issubclass(model_class, model.Base):
|
||||||
|
session = session or Session()
|
||||||
|
return session.query(model_class)
|
||||||
|
|
||||||
def configure_grid(self, grid):
|
def configure_grid(self, grid):
|
||||||
"""
|
"""
|
||||||
Configure the grid for the :meth:`index()` view.
|
Configure the grid for the :meth:`index()` view.
|
||||||
|
|
||||||
This is called by :meth:`index_make_grid()`.
|
This is called by :meth:`make_model_grid()`.
|
||||||
|
|
||||||
There is no default logic here; subclass should override as
|
There is no default logic here; subclass should override as
|
||||||
needed. The ``grid`` param will already be "complete" and
|
needed. The ``grid`` param will already be "complete" and
|
||||||
|
@ -981,7 +1009,7 @@ class MasterView(View):
|
||||||
grid.set_link(key)
|
grid.set_link(key)
|
||||||
# print("set link:", key)
|
# print("set link:", key)
|
||||||
|
|
||||||
def get_instance(self):
|
def get_instance(self, session=None):
|
||||||
"""
|
"""
|
||||||
This should return the "current" model instance based on the
|
This should return the "current" model instance based on the
|
||||||
request details (e.g. route kwargs).
|
request details (e.g. route kwargs).
|
||||||
|
@ -992,6 +1020,27 @@ class MasterView(View):
|
||||||
There is no "sane" default logic here; subclass *must*
|
There is no "sane" default logic here; subclass *must*
|
||||||
override or else a ``NotImplementedError`` is raised.
|
override or else a ``NotImplementedError`` is raised.
|
||||||
"""
|
"""
|
||||||
|
model_class = self.get_model_class()
|
||||||
|
if model_class:
|
||||||
|
session = session or Session()
|
||||||
|
|
||||||
|
def filtr(query, model_key):
|
||||||
|
key = self.request.matchdict[model_key]
|
||||||
|
query = query.filter(getattr(self.model_class, model_key) == key)
|
||||||
|
return query
|
||||||
|
|
||||||
|
query = session.query(model_class)
|
||||||
|
|
||||||
|
for key in self.get_model_key():
|
||||||
|
query = filtr(query, key)
|
||||||
|
|
||||||
|
try:
|
||||||
|
return query.one()
|
||||||
|
except orm.exc.NoResultFound:
|
||||||
|
pass
|
||||||
|
|
||||||
|
raise self.notfound()
|
||||||
|
|
||||||
raise NotImplementedError("you must define get_instance() method "
|
raise NotImplementedError("you must define get_instance() method "
|
||||||
f" for view class: {self.__class__}")
|
f" for view class: {self.__class__}")
|
||||||
|
|
||||||
|
@ -1075,10 +1124,18 @@ class MasterView(View):
|
||||||
* :meth:`get_form_fields()`
|
* :meth:`get_form_fields()`
|
||||||
* :meth:`configure_form()`
|
* :meth:`configure_form()`
|
||||||
"""
|
"""
|
||||||
|
if 'model_class' not in kwargs:
|
||||||
|
model_class = self.get_model_class()
|
||||||
|
if model_class:
|
||||||
|
kwargs['model_class'] = model_class
|
||||||
|
|
||||||
kwargs['model_instance'] = model_instance
|
kwargs['model_instance'] = model_instance
|
||||||
|
|
||||||
if 'fields' not in kwargs:
|
# if 'fields' not in kwargs:
|
||||||
kwargs['fields'] = self.get_form_fields()
|
if not kwargs.get('fields'):
|
||||||
|
fields = self.get_form_fields()
|
||||||
|
if fields:
|
||||||
|
kwargs['fields'] = fields
|
||||||
|
|
||||||
form = self.make_form(**kwargs)
|
form = self.make_form(**kwargs)
|
||||||
self.configure_form(form)
|
self.configure_form(form)
|
||||||
|
@ -1146,9 +1203,25 @@ class MasterView(View):
|
||||||
|
|
||||||
See also :meth:`edit_save_form()` which calls this method.
|
See also :meth:`edit_save_form()` which calls this method.
|
||||||
"""
|
"""
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
model_class = self.get_model_class()
|
||||||
|
if model_class and issubclass(model_class, model.Base):
|
||||||
|
|
||||||
|
# update instance attrs for sqlalchemy model
|
||||||
|
if self.creating:
|
||||||
|
obj = model_class()
|
||||||
|
else:
|
||||||
|
obj = form.model_instance
|
||||||
|
data = form.validated
|
||||||
|
for key in form.fields:
|
||||||
|
if key in data:
|
||||||
|
setattr(obj, key, data[key])
|
||||||
|
return obj
|
||||||
|
|
||||||
return form.validated
|
return form.validated
|
||||||
|
|
||||||
def persist(self, obj):
|
def persist(self, obj, session=None):
|
||||||
"""
|
"""
|
||||||
If applicable, this method should persist ("save") the given
|
If applicable, this method should persist ("save") the given
|
||||||
object's data (e.g. to DB), creating or updating it as needed.
|
object's data (e.g. to DB), creating or updating it as needed.
|
||||||
|
@ -1165,6 +1238,13 @@ class MasterView(View):
|
||||||
|
|
||||||
See also :meth:`edit_save_form()` which calls this method.
|
See also :meth:`edit_save_form()` which calls this method.
|
||||||
"""
|
"""
|
||||||
|
model = self.app.model
|
||||||
|
model_class = self.get_model_class()
|
||||||
|
if model_class and issubclass(model_class, model.Base):
|
||||||
|
|
||||||
|
# add sqlalchemy model to session
|
||||||
|
session = session or Session()
|
||||||
|
session.add(obj)
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# class methods
|
# class methods
|
||||||
|
@ -1285,6 +1365,11 @@ class MasterView(View):
|
||||||
keys = [keys]
|
keys = [keys]
|
||||||
return tuple(keys)
|
return tuple(keys)
|
||||||
|
|
||||||
|
model_class = cls.get_model_class()
|
||||||
|
if model_class:
|
||||||
|
mapper = sa.inspect(model_class)
|
||||||
|
return tuple([column.key for column in mapper.primary_key])
|
||||||
|
|
||||||
raise AttributeError(f"you must define model_key for view class: {cls}")
|
raise AttributeError(f"you must define model_key for view class: {cls}")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1390,7 +1475,7 @@ class MasterView(View):
|
||||||
grid in the :meth:`index()` view. This key may also be used
|
grid in the :meth:`index()` view. This key may also be used
|
||||||
as the basis (key prefix) for secondary grids.
|
as the basis (key prefix) for secondary grids.
|
||||||
|
|
||||||
This is called from :meth:`index_make_grid()`; in the
|
This is called from :meth:`make_model_grid()`; in the
|
||||||
resulting :class:`~wuttaweb.grids.base.Grid` instance, this
|
resulting :class:`~wuttaweb.grids.base.Grid` instance, this
|
||||||
becomes :attr:`~wuttaweb.grids.base.Grid.key`.
|
becomes :attr:`~wuttaweb.grids.base.Grid.key`.
|
||||||
|
|
||||||
|
|
|
@ -149,85 +149,19 @@ class SettingView(MasterView):
|
||||||
model_class = Setting
|
model_class = Setting
|
||||||
model_title = "Raw Setting"
|
model_title = "Raw Setting"
|
||||||
|
|
||||||
# TODO: this should be deduced by master
|
# TODO: master should handle this, possibly via configure_form()
|
||||||
model_key = 'name'
|
def get_query(self, session=None):
|
||||||
|
|
||||||
# TODO: try removing these
|
|
||||||
grid_columns = [
|
|
||||||
'name',
|
|
||||||
'value',
|
|
||||||
]
|
|
||||||
form_fields = list(grid_columns)
|
|
||||||
|
|
||||||
# TODO: should define query, let master handle the rest
|
|
||||||
def index_get_grid_data(self, session=None):
|
|
||||||
""" """
|
""" """
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
query = super().get_query(session=session)
|
||||||
|
return query.order_by(model.Setting.name)
|
||||||
|
|
||||||
session = session or Session()
|
# TODO: master should handle this (per column nullable)
|
||||||
query = session.query(model.Setting)\
|
|
||||||
.order_by(model.Setting.name)
|
|
||||||
|
|
||||||
settings = []
|
|
||||||
for setting in query:
|
|
||||||
settings.append(self.normalize_setting(setting))
|
|
||||||
|
|
||||||
return settings
|
|
||||||
|
|
||||||
# TODO: master should handle this (but not as dict)
|
|
||||||
def normalize_setting(self, setting):
|
|
||||||
""" """
|
|
||||||
return {
|
|
||||||
'name': setting.name,
|
|
||||||
# TODO: when viewing the record, 'None' is displayed for null
|
|
||||||
# field values. not so if we return colander.null here, but
|
|
||||||
# then that causes other problems..
|
|
||||||
#'value': setting.value if setting.value is not None else colander.null,
|
|
||||||
'value': setting.value,
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: master should handle this
|
|
||||||
def get_instance(self, session=None):
|
|
||||||
""" """
|
|
||||||
model = self.app.model
|
|
||||||
session = session or Session()
|
|
||||||
name = self.request.matchdict['name']
|
|
||||||
setting = session.query(model.Setting).get(name)
|
|
||||||
if setting:
|
|
||||||
setting = self.normalize_setting(setting)
|
|
||||||
if setting['value'] is None:
|
|
||||||
setting['value'] = colander.null
|
|
||||||
return setting
|
|
||||||
|
|
||||||
return self.notfound()
|
|
||||||
|
|
||||||
# TODO: master should handle this
|
|
||||||
def get_instance_title(self, setting):
|
|
||||||
""" """
|
|
||||||
return setting['name']
|
|
||||||
|
|
||||||
# TODO: master should handle this
|
|
||||||
def configure_form(self, f):
|
def configure_form(self, f):
|
||||||
|
""" """
|
||||||
super().configure_form(f)
|
super().configure_form(f)
|
||||||
|
|
||||||
f.set_required('value', False)
|
f.set_required('value', False)
|
||||||
|
|
||||||
# TODO: master should handle this
|
|
||||||
def persist(self, setting, session=None):
|
|
||||||
""" """
|
|
||||||
if self.creating:
|
|
||||||
name = setting['name']
|
|
||||||
else:
|
|
||||||
name = self.request.matchdict['name']
|
|
||||||
session = session or Session()
|
|
||||||
self.app.save_setting(session, name, setting['value'])
|
|
||||||
|
|
||||||
# TODO: master should handle this
|
|
||||||
def delete_instance(self, obj, session=None):
|
|
||||||
""" """
|
|
||||||
session = session or Session()
|
|
||||||
self.app.delete_setting(session, obj['name'])
|
|
||||||
|
|
||||||
|
|
||||||
def defaults(config, **kwargs):
|
def defaults(config, **kwargs):
|
||||||
base = globals()
|
base = globals()
|
||||||
|
|
|
@ -49,6 +49,7 @@ class TestForm(TestCase):
|
||||||
self.config = WuttaConfig(defaults={
|
self.config = WuttaConfig(defaults={
|
||||||
'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
|
'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
|
||||||
})
|
})
|
||||||
|
self.app = self.config.get_app()
|
||||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||||
|
|
||||||
self.pyramid_config = testing.setUp(request=self.request, settings={
|
self.pyramid_config = testing.setUp(request=self.request, settings={
|
||||||
|
@ -115,6 +116,7 @@ class TestForm(TestCase):
|
||||||
self.assertEqual(form.fields, ['baz'])
|
self.assertEqual(form.fields, ['baz'])
|
||||||
|
|
||||||
def test_get_schema(self):
|
def test_get_schema(self):
|
||||||
|
model = self.app.model
|
||||||
form = self.make_form()
|
form = self.make_form()
|
||||||
self.assertIsNone(form.schema)
|
self.assertIsNone(form.schema)
|
||||||
|
|
||||||
|
@ -135,6 +137,24 @@ class TestForm(TestCase):
|
||||||
self.assertIsNone(form.schema)
|
self.assertIsNone(form.schema)
|
||||||
self.assertRaises(NotImplementedError, form.get_schema)
|
self.assertRaises(NotImplementedError, form.get_schema)
|
||||||
|
|
||||||
|
# schema is auto-generated if model_class provided
|
||||||
|
form = self.make_form(model_class=model.Setting)
|
||||||
|
schema = form.get_schema()
|
||||||
|
self.assertEqual(len(schema.children), 2)
|
||||||
|
self.assertIn('name', schema)
|
||||||
|
self.assertIn('value', schema)
|
||||||
|
|
||||||
|
# schema is auto-generated if model_instance provided
|
||||||
|
form = self.make_form(model_instance=model.Setting(name='uhoh'))
|
||||||
|
self.assertEqual(form.fields, ['name', 'value'])
|
||||||
|
self.assertIsNone(form.schema)
|
||||||
|
# nb. force method to get new fields
|
||||||
|
del form.fields
|
||||||
|
schema = form.get_schema()
|
||||||
|
self.assertEqual(len(schema.children), 2)
|
||||||
|
self.assertIn('name', schema)
|
||||||
|
self.assertIn('value', schema)
|
||||||
|
|
||||||
# schema nodes are required by default
|
# schema nodes are required by default
|
||||||
form = self.make_form(fields=['foo', 'bar'])
|
form = self.make_form(fields=['foo', 'bar'])
|
||||||
schema = form.get_schema()
|
schema = form.get_schema()
|
||||||
|
@ -149,6 +169,7 @@ class TestForm(TestCase):
|
||||||
self.assertIsNone(schema['bar'].missing)
|
self.assertIsNone(schema['bar'].missing)
|
||||||
|
|
||||||
def test_get_deform(self):
|
def test_get_deform(self):
|
||||||
|
model = self.app.model
|
||||||
schema = self.make_schema()
|
schema = self.make_schema()
|
||||||
|
|
||||||
# basic
|
# basic
|
||||||
|
@ -158,12 +179,24 @@ class TestForm(TestCase):
|
||||||
self.assertIsInstance(dform, deform.Form)
|
self.assertIsInstance(dform, deform.Form)
|
||||||
self.assertIs(form.deform_form, dform)
|
self.assertIs(form.deform_form, dform)
|
||||||
|
|
||||||
# with model instance / cstruct
|
# with model instance as dict
|
||||||
myobj = {'foo': 'one', 'bar': 'two'}
|
myobj = {'foo': 'one', 'bar': 'two'}
|
||||||
form = self.make_form(schema=schema, model_instance=myobj)
|
form = self.make_form(schema=schema, model_instance=myobj)
|
||||||
dform = form.get_deform()
|
dform = form.get_deform()
|
||||||
self.assertEqual(dform.cstruct, myobj)
|
self.assertEqual(dform.cstruct, myobj)
|
||||||
|
|
||||||
|
# with sqlalchemy model instance
|
||||||
|
myobj = model.Setting(name='foo', value='bar')
|
||||||
|
form = self.make_form(model_instance=myobj)
|
||||||
|
dform = form.get_deform()
|
||||||
|
self.assertEqual(dform.cstruct, {'name': 'foo', 'value': 'bar'})
|
||||||
|
|
||||||
|
# sqlalchemy instance with null value
|
||||||
|
myobj = model.Setting(name='foo', value=None)
|
||||||
|
form = self.make_form(model_instance=myobj)
|
||||||
|
dform = form.get_deform()
|
||||||
|
self.assertEqual(dform.cstruct, {'name': 'foo', 'value': colander.null})
|
||||||
|
|
||||||
def test_get_cancel_url(self):
|
def test_get_cancel_url(self):
|
||||||
|
|
||||||
# is referrer by default
|
# is referrer by default
|
||||||
|
|
|
@ -16,6 +16,7 @@ class TestGrid(TestCase):
|
||||||
self.config = WuttaConfig(defaults={
|
self.config = WuttaConfig(defaults={
|
||||||
'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
|
'wutta.web.menus.handler_spec': 'tests.utils:NullMenuHandler',
|
||||||
})
|
})
|
||||||
|
self.app = self.config.get_app()
|
||||||
|
|
||||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
||||||
|
|
||||||
|
@ -50,6 +51,24 @@ class TestGrid(TestCase):
|
||||||
grid = self.make_grid()
|
grid = self.make_grid()
|
||||||
self.assertEqual(grid.vue_component, 'WuttaGrid')
|
self.assertEqual(grid.vue_component, 'WuttaGrid')
|
||||||
|
|
||||||
|
def test_get_columns(self):
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# empty
|
||||||
|
grid = self.make_grid()
|
||||||
|
self.assertIsNone(grid.columns)
|
||||||
|
self.assertIsNone(grid.get_columns())
|
||||||
|
|
||||||
|
# explicit
|
||||||
|
grid = self.make_grid(columns=['foo', 'bar'])
|
||||||
|
self.assertEqual(grid.columns, ['foo', 'bar'])
|
||||||
|
self.assertEqual(grid.get_columns(), ['foo', 'bar'])
|
||||||
|
|
||||||
|
# derived from model
|
||||||
|
grid = self.make_grid(model_class=model.Setting)
|
||||||
|
self.assertEqual(grid.columns, ['name', 'value'])
|
||||||
|
self.assertEqual(grid.get_columns(), ['name', 'value'])
|
||||||
|
|
||||||
def test_linked_columns(self):
|
def test_linked_columns(self):
|
||||||
grid = self.make_grid(columns=['foo', 'bar'])
|
grid = self.make_grid(columns=['foo', 'bar'])
|
||||||
self.assertEqual(grid.linked_columns, [])
|
self.assertEqual(grid.linked_columns, [])
|
||||||
|
|
|
@ -403,6 +403,18 @@ class TestGetFormData(TestCase):
|
||||||
self.assertEqual(data, {'foo2': 'baz'})
|
self.assertEqual(data, {'foo2': 'baz'})
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetModelFields(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.config = WuttaConfig()
|
||||||
|
self.app = self.config.get_app()
|
||||||
|
|
||||||
|
def test_basic(self):
|
||||||
|
model = self.app.model
|
||||||
|
fields = util.get_model_fields(self.config, model.Setting)
|
||||||
|
self.assertEqual(fields, ['name', 'value'])
|
||||||
|
|
||||||
|
|
||||||
class TestGetCsrfToken(TestCase):
|
class TestGetCsrfToken(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -6,7 +6,7 @@ from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from pyramid import testing
|
from pyramid import testing
|
||||||
from pyramid.response import Response
|
from pyramid.response import Response
|
||||||
from pyramid.httpexceptions import HTTPFound
|
from pyramid.httpexceptions import HTTPFound, HTTPNotFound
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
from wuttjamaican.conf import WuttaConfig
|
||||||
from wuttaweb.views import master
|
from wuttaweb.views import master
|
||||||
|
@ -348,10 +348,120 @@ class TestMasterView(WebTestCase):
|
||||||
self.assertEqual(view.get_index_title(), "Wutta Widgets")
|
self.assertEqual(view.get_index_title(), "Wutta Widgets")
|
||||||
del master.MasterView.model_title_plural
|
del master.MasterView.model_title_plural
|
||||||
|
|
||||||
|
def test_make_model_grid(self):
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# no model class
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_name='Widget',
|
||||||
|
model_key='uuid'):
|
||||||
|
view = master.MasterView(self.request)
|
||||||
|
grid = view.make_model_grid()
|
||||||
|
self.assertIsNone(grid.model_class)
|
||||||
|
|
||||||
|
# explicit model class
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
grid = view.make_model_grid(session=self.session)
|
||||||
|
self.assertIs(grid.model_class, model.Setting)
|
||||||
|
|
||||||
def test_get_instance(self):
|
def test_get_instance(self):
|
||||||
|
model = self.app.model
|
||||||
|
self.app.save_setting(self.session, 'foo', 'bar')
|
||||||
|
self.session.commit()
|
||||||
|
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
||||||
|
|
||||||
|
# default not implemented
|
||||||
view = master.MasterView(self.request)
|
view = master.MasterView(self.request)
|
||||||
self.assertRaises(NotImplementedError, view.get_instance)
|
self.assertRaises(NotImplementedError, view.get_instance)
|
||||||
|
|
||||||
|
# fetch from DB if model class is known
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
view = master.MasterView(self.request)
|
||||||
|
|
||||||
|
# existing setting is returned
|
||||||
|
self.request.matchdict = {'name': 'foo'}
|
||||||
|
setting = view.get_instance(session=self.session)
|
||||||
|
self.assertIsInstance(setting, model.Setting)
|
||||||
|
self.assertEqual(setting.name, 'foo')
|
||||||
|
self.assertEqual(setting.value, 'bar')
|
||||||
|
|
||||||
|
# missing setting not found
|
||||||
|
self.request.matchdict = {'name': 'blarg'}
|
||||||
|
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session)
|
||||||
|
|
||||||
|
def test_make_model_form(self):
|
||||||
|
model = self.app.model
|
||||||
|
|
||||||
|
# no model class
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_name='Widget',
|
||||||
|
model_key='uuid'):
|
||||||
|
view = master.MasterView(self.request)
|
||||||
|
form = view.make_model_form()
|
||||||
|
self.assertIsNone(form.model_class)
|
||||||
|
|
||||||
|
# explicit model class
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
form = view.make_model_form()
|
||||||
|
self.assertIs(form.model_class, model.Setting)
|
||||||
|
|
||||||
|
def test_objectify(self):
|
||||||
|
model = self.app.model
|
||||||
|
self.app.save_setting(self.session, 'foo', 'bar')
|
||||||
|
self.session.commit()
|
||||||
|
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
||||||
|
|
||||||
|
# no model class
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_name='Widget',
|
||||||
|
model_key='uuid'):
|
||||||
|
view = master.MasterView(self.request)
|
||||||
|
form = view.make_model_form()
|
||||||
|
form.validated = {'name': 'first'}
|
||||||
|
obj = view.objectify(form)
|
||||||
|
self.assertIs(obj, form.validated)
|
||||||
|
|
||||||
|
# explicit model class (editing)
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting,
|
||||||
|
editing=True):
|
||||||
|
form = view.make_model_form()
|
||||||
|
form.validated = {'name': 'foo', 'value': 'blarg'}
|
||||||
|
form.model_instance = self.session.query(model.Setting).one()
|
||||||
|
obj = view.objectify(form)
|
||||||
|
self.assertIsInstance(obj, model.Setting)
|
||||||
|
self.assertEqual(obj.name, 'foo')
|
||||||
|
self.assertEqual(obj.value, 'blarg')
|
||||||
|
|
||||||
|
# explicit model class (creating)
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting,
|
||||||
|
creating=True):
|
||||||
|
form = view.make_model_form()
|
||||||
|
form.validated = {'name': 'another', 'value': 'whatever'}
|
||||||
|
obj = view.objectify(form)
|
||||||
|
self.assertIsInstance(obj, model.Setting)
|
||||||
|
self.assertEqual(obj.name, 'another')
|
||||||
|
self.assertEqual(obj.value, 'whatever')
|
||||||
|
|
||||||
|
def test_persist(self):
|
||||||
|
model = self.app.model
|
||||||
|
with patch.multiple(master.MasterView, create=True,
|
||||||
|
model_class=model.Setting):
|
||||||
|
view = master.MasterView(self.request)
|
||||||
|
|
||||||
|
# new instance is persisted
|
||||||
|
setting = model.Setting(name='foo', value='bar')
|
||||||
|
self.assertEqual(self.session.query(model.Setting).count(), 0)
|
||||||
|
view.persist(setting, session=self.session)
|
||||||
|
self.session.commit()
|
||||||
|
setting = self.session.query(model.Setting).one()
|
||||||
|
self.assertEqual(setting.name, 'foo')
|
||||||
|
self.assertEqual(setting.value, 'bar')
|
||||||
|
|
||||||
##############################
|
##############################
|
||||||
# view methods
|
# view methods
|
||||||
##############################
|
##############################
|
||||||
|
@ -366,7 +476,7 @@ class TestMasterView(WebTestCase):
|
||||||
response = view.index()
|
response = view.index()
|
||||||
# then again with data, to include view action url
|
# then again with data, to include view action url
|
||||||
data = [{'name': 'foo', 'value': 'bar'}]
|
data = [{'name': 'foo', 'value': 'bar'}]
|
||||||
with patch.object(view, 'index_get_grid_data', return_value=data):
|
with patch.object(view, 'get_grid_data', return_value=data):
|
||||||
response = view.index()
|
response = view.index()
|
||||||
del master.MasterView.model_name
|
del master.MasterView.model_name
|
||||||
del master.MasterView.model_key
|
del master.MasterView.model_key
|
||||||
|
@ -544,7 +654,9 @@ class TestMasterView(WebTestCase):
|
||||||
model_class=model.Setting,
|
model_class=model.Setting,
|
||||||
form_fields=['name', 'value']):
|
form_fields=['name', 'value']):
|
||||||
view = master.MasterView(self.request)
|
view = master.MasterView(self.request)
|
||||||
self.assertRaises(NotImplementedError, view.delete_instance, setting)
|
view.delete_instance(setting)
|
||||||
|
self.session.commit()
|
||||||
|
self.assertEqual(self.session.query(model.Setting).count(), 0)
|
||||||
|
|
||||||
def test_configure(self):
|
def test_configure(self):
|
||||||
model = self.app.model
|
model = self.app.model
|
||||||
|
|
|
@ -35,39 +35,19 @@ class TestSettingView(WebTestCase):
|
||||||
def make_view(self):
|
def make_view(self):
|
||||||
return settings.SettingView(self.request)
|
return settings.SettingView(self.request)
|
||||||
|
|
||||||
def test_index_get_grid_data(self):
|
def test_get_grid_data(self):
|
||||||
|
|
||||||
# empty data by default
|
# empty data by default
|
||||||
view = self.make_view()
|
view = self.make_view()
|
||||||
data = view.index_get_grid_data(session=self.session)
|
data = view.get_grid_data(session=self.session)
|
||||||
self.assertEqual(len(data), 0)
|
self.assertEqual(len(data), 0)
|
||||||
|
|
||||||
# unless we save some settings
|
# unless we save some settings
|
||||||
self.app.save_setting(self.session, 'foo', 'bar')
|
self.app.save_setting(self.session, 'foo', 'bar')
|
||||||
self.session.commit()
|
self.session.commit()
|
||||||
data = view.index_get_grid_data(session=self.session)
|
data = view.get_grid_data(session=self.session)
|
||||||
self.assertEqual(len(data), 1)
|
self.assertEqual(len(data), 1)
|
||||||
|
|
||||||
def test_get_instance(self):
|
|
||||||
view = self.make_view()
|
|
||||||
self.request.matchdict = {'name': 'foo'}
|
|
||||||
|
|
||||||
# setting not found
|
|
||||||
setting = view.get_instance(session=self.session)
|
|
||||||
self.assertIsInstance(setting, HTTPNotFound)
|
|
||||||
|
|
||||||
# setting is returned
|
|
||||||
self.app.save_setting(self.session, 'foo', 'bar')
|
|
||||||
self.session.commit()
|
|
||||||
setting = view.get_instance(session=self.session)
|
|
||||||
self.assertEqual(setting, {'name': 'foo', 'value': 'bar'})
|
|
||||||
|
|
||||||
def test_get_instance_title(self):
|
|
||||||
setting = {'name': 'foo', 'value': 'bar'}
|
|
||||||
view = self.make_view()
|
|
||||||
title = view.get_instance_title(setting)
|
|
||||||
self.assertEqual(title, 'foo')
|
|
||||||
|
|
||||||
def test_configure_form(self):
|
def test_configure_form(self):
|
||||||
view = self.make_view()
|
view = self.make_view()
|
||||||
form = view.make_form(fields=view.get_form_fields())
|
form = view.make_form(fields=view.get_form_fields())
|
||||||
|
@ -75,42 +55,3 @@ class TestSettingView(WebTestCase):
|
||||||
view.configure_form(form)
|
view.configure_form(form)
|
||||||
self.assertIn('value', form.required_fields)
|
self.assertIn('value', form.required_fields)
|
||||||
self.assertFalse(form.required_fields['value'])
|
self.assertFalse(form.required_fields['value'])
|
||||||
|
|
||||||
def test_persist(self):
|
|
||||||
model = self.app.model
|
|
||||||
view = self.make_view()
|
|
||||||
|
|
||||||
# setup
|
|
||||||
self.app.save_setting(self.session, 'foo', 'bar')
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
||||||
|
|
||||||
# setting is updated
|
|
||||||
self.request.matchdict = {'name': 'foo'}
|
|
||||||
view.persist({'name': 'foo', 'value': 'frazzle'}, session=self.session)
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
||||||
self.assertEqual(self.app.get_setting(self.session, 'foo'), 'frazzle')
|
|
||||||
|
|
||||||
# new setting is created
|
|
||||||
self.request.matchdict = {}
|
|
||||||
with patch.object(view, 'creating', new=True):
|
|
||||||
view.persist({'name': 'foo', 'value': 'frazzle'}, session=self.session)
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
||||||
self.assertEqual(self.app.get_setting(self.session, 'foo'), 'frazzle')
|
|
||||||
|
|
||||||
def test_delete_instance(self):
|
|
||||||
model = self.app.model
|
|
||||||
view = self.make_view()
|
|
||||||
|
|
||||||
# setup
|
|
||||||
self.app.save_setting(self.session, 'foo', 'bar')
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
||||||
|
|
||||||
# setting is deleted
|
|
||||||
self.request.matchdict = {'name': 'foo'}
|
|
||||||
view.delete_instance({'name': 'foo', 'value': 'frazzle'}, session=self.session)
|
|
||||||
self.session.commit()
|
|
||||||
self.assertEqual(self.session.query(model.Setting).count(), 0)
|
|
||||||
|
|
Loading…
Reference in a new issue