3
0
Fork 0
wuttaweb/tests/forms/test_base.py

912 lines
32 KiB
Python

# -*- coding: utf-8; -*-
import os
from unittest.mock import MagicMock, patch
import sqlalchemy as sa
from sqlalchemy import orm
from sqlalchemy.ext.associationproxy import AssociationProxy
import deform
import colander
from colanderalchemy import SQLAlchemySchemaNode
from pyramid.renderers import get_renderer
from wuttjamaican.testing import DataTestCase
from wuttaweb.forms import base as mod, widgets
from wuttaweb.grids import Grid
from wuttaweb.testing import WebTestCase
here = os.path.dirname(__file__)
class TestForm(WebTestCase):
mako_directories = ["wuttaweb:templates", here]
def make_form(self, **kwargs):
return mod.Form(self.request, **kwargs)
def make_schema(self):
schema = colander.Schema(
children=[
colander.SchemaNode(colander.String(), name="foo"),
colander.SchemaNode(colander.String(), name="bar"),
]
)
return schema
def test_init_with_none(self):
form = self.make_form()
self.assertEqual(form.fields, [])
def test_init_with_fields(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.fields, ["foo", "bar"])
def test_init_with_schema(self):
schema = self.make_schema()
form = self.make_form(schema=schema)
self.assertEqual(form.fields, ["foo", "bar"])
def test_vue_tagname(self):
form = self.make_form()
self.assertEqual(form.vue_tagname, "wutta-form")
def test_vue_component(self):
form = self.make_form()
self.assertEqual(form.vue_component, "WuttaForm")
def test_contains(self):
form = self.make_form(fields=["foo", "bar"])
self.assertIn("foo", form)
self.assertNotIn("baz", form)
def test_iter(self):
form = self.make_form(fields=["foo", "bar"])
fields = list(iter(form))
self.assertEqual(fields, ["foo", "bar"])
fields = []
for field in form:
fields.append(field)
self.assertEqual(fields, ["foo", "bar"])
def test_set_fields(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.fields, ["foo", "bar"])
form.set_fields(["baz"])
self.assertEqual(form.fields, ["baz"])
def test_append(self):
form = self.make_form(fields=["one", "two"])
self.assertEqual(form.fields, ["one", "two"])
form.append("one", "two", "three")
self.assertEqual(form.fields, ["one", "two", "three"])
def test_remove(self):
form = self.make_form(fields=["one", "two", "three", "four"])
self.assertEqual(form.fields, ["one", "two", "three", "four"])
form.remove("two", "three")
self.assertEqual(form.fields, ["one", "four"])
def test_set_node(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.nodes, {})
# complete node
node = colander.SchemaNode(colander.Bool(), name="foo")
form.set_node("foo", node)
self.assertIs(form.nodes["foo"], node)
# type only
typ = colander.Bool()
form.set_node("foo", typ)
node = form.nodes["foo"]
self.assertIsInstance(node, colander.SchemaNode)
self.assertIsInstance(node.typ, colander.Bool)
self.assertEqual(node.name, "foo")
# schema is updated if already present
schema = form.get_schema()
self.assertIsNotNone(schema)
typ = colander.Date()
form.set_node("foo", typ)
node = form.nodes["foo"]
self.assertIsInstance(node, colander.SchemaNode)
self.assertIsInstance(node.typ, colander.Date)
self.assertEqual(node.name, "foo")
def test_set_widget(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.widgets, {})
# basic
widget = widgets.SelectWidget()
form.set_widget("foo", widget)
self.assertIs(form.widgets["foo"], widget)
# schema is updated if already present
schema = form.get_schema()
self.assertIsNotNone(schema)
self.assertIs(schema["foo"].widget, widget)
new_widget = widgets.TextInputWidget()
form.set_widget("foo", new_widget)
self.assertIs(form.widgets["foo"], new_widget)
self.assertIs(schema["foo"].widget, new_widget)
# can also just specify widget pseudo-type (invalid)
self.assertNotIn("bar", form.widgets)
self.assertRaises(ValueError, form.set_widget, "bar", "ldjfadjfadj")
# can also just specify widget pseudo-type (valid)
self.assertNotIn("bar", form.widgets)
form.set_widget("bar", "notes")
self.assertIsInstance(form.widgets["bar"], widgets.NotesWidget)
def test_make_widget(self):
form = self.make_form(fields=["foo", "bar"])
# notes
widget = form.make_widget("notes")
self.assertIsInstance(widget, widgets.NotesWidget)
# invalid
widget = form.make_widget("fdajvdafjjf")
self.assertIsNone(widget)
def test_set_default_widgets(self):
model = self.app.model
# no defaults for "plain" schema
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.widgets, {})
# no defaults for "plain" mapped class
form = self.make_form(model_class=model.Setting)
self.assertEqual(form.widgets, {})
class MyWidget(widgets.Widget):
pass
# widget set for datetime mapped field
form = self.make_form(model_class=model.Upgrade)
self.assertIn("created", form.widgets)
self.assertIsNot(form.widgets["created"], MyWidget)
self.assertNotIsInstance(form.widgets["created"], MyWidget)
# widget *not* set for datetime, if override present
form = self.make_form(
model_class=model.Upgrade, widgets={"created": MyWidget()}
)
self.assertIn("created", form.widgets)
self.assertIsInstance(form.widgets["created"], MyWidget)
# mock up a table with all relevant column types
class Whatever(model.Base):
__tablename__ = "whatever"
id = sa.Column(sa.Integer(), primary_key=True)
date = sa.Column(sa.Date())
date_time = sa.Column(sa.DateTime())
# widget set for all known types
form = self.make_form(model_class=Whatever)
self.assertIsInstance(form.widgets["date"], widgets.WuttaDateWidget)
self.assertIsInstance(form.widgets["date_time"], widgets.WuttaDateTimeWidget)
def test_set_grid(self):
form = self.make_form(fields=["foo", "bar"])
self.assertNotIn("foo", form.widgets)
self.assertNotIn("foogrid", form.grid_vue_context)
grid = Grid(
self.request,
key="foogrid",
columns=["a", "b"],
data=[{"a": 1, "b": 2}, {"a": 3, "b": 4}],
)
form.set_grid("foo", grid)
self.assertIn("foo", form.widgets)
self.assertIsInstance(form.widgets["foo"], widgets.GridWidget)
self.assertIn("foogrid", form.grid_vue_context)
def test_set_validator(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.validators, {})
def validate1(node, value):
pass
# basic
form.set_validator("foo", validate1)
self.assertIs(form.validators["foo"], validate1)
def validate2(node, value):
pass
# schema is updated if already present
schema = form.get_schema()
self.assertIsNotNone(schema)
self.assertIs(schema["foo"].validator, validate1)
form.set_validator("foo", validate2)
self.assertIs(form.validators["foo"], validate2)
self.assertIs(schema["foo"].validator, validate2)
def test_set_default(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.defaults, {})
# basic
form.set_default("foo", 42)
self.assertEqual(form.defaults["foo"], 42)
def test_get_schema(self):
model = self.app.model
form = self.make_form()
self.assertIsNone(form.schema)
# provided schema is returned
schema = self.make_schema()
form = self.make_form(schema=schema)
self.assertIs(form.schema, schema)
self.assertIs(form.get_schema(), schema)
# schema is auto-generated if fields provided
form = self.make_form(fields=["foo", "bar"])
schema = form.get_schema()
self.assertEqual(len(schema.children), 2)
self.assertEqual(schema["foo"].name, "foo")
# but auto-generating without fields is not supported
form = self.make_form()
self.assertIsNone(form.schema)
self.assertRaises(ValueError, 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)
# but node overrides are honored when auto-generating
form = self.make_form(model_class=model.Setting)
value_node = colander.SchemaNode(colander.Bool(), name="value")
form.set_node("value", value_node)
schema = form.get_schema()
self.assertIs(schema["value"], value_node)
# 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)
# ColanderAlchemy schema still has *all* requested fields
form = self.make_form(
model_instance=model.Setting(name="uhoh"),
fields=["name", "value", "foo", "bar"],
)
self.assertEqual(form.fields, ["name", "value", "foo", "bar"])
self.assertIsNone(form.schema)
schema = form.get_schema()
self.assertEqual(len(schema.children), 4)
self.assertIn("name", schema)
self.assertIn("value", schema)
self.assertIn("foo", schema)
self.assertIn("bar", schema)
# schema nodes are required by default
form = self.make_form(fields=["foo", "bar"])
schema = form.get_schema()
self.assertIs(schema["foo"].missing, colander.required)
self.assertIs(schema["bar"].missing, colander.required)
# but fields can be marked *not* required
form = self.make_form(fields=["foo", "bar"])
form.set_required("bar", False)
schema = form.get_schema()
self.assertIs(schema["foo"].missing, colander.required)
self.assertIs(schema["bar"].missing, colander.null)
# validator overrides are honored
def validate(node, value):
pass
form = self.make_form(model_class=model.Setting)
form.set_validator("name", validate)
schema = form.get_schema()
self.assertIs(schema["name"].validator, validate)
# validator can be set for whole form
form = self.make_form(model_class=model.Setting)
schema = form.get_schema()
self.assertIsNone(schema.validator)
form = self.make_form(model_class=model.Setting)
form.set_validator(None, validate)
schema = form.get_schema()
self.assertIs(schema.validator, validate)
# default value overrides are honored
form = self.make_form(model_class=model.Setting)
form.set_default("name", "foo")
schema = form.get_schema()
self.assertEqual(schema["name"].default, "foo")
def test_get_deform(self):
model = self.app.model
schema = self.make_schema()
# basic
form = self.make_form(schema=schema)
self.assertIsNone(form.deform_form)
dform = form.get_deform()
self.assertIsInstance(dform, deform.Form)
self.assertIs(form.deform_form, dform)
# with model instance as dict
myobj = {"foo": "one", "bar": "two"}
form = self.make_form(schema=schema, model_instance=myobj)
dform = form.get_deform()
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):
# is referrer by default
form = self.make_form()
self.request.get_referrer = MagicMock(return_value="/cancel-default")
self.assertEqual(form.get_cancel_url(), "/cancel-default")
del self.request.get_referrer
# or can be static URL
form = self.make_form(cancel_url="/cancel-static")
self.assertEqual(form.get_cancel_url(), "/cancel-static")
# or can be fallback URL (nb. 'NOPE' indicates no referrer)
form = self.make_form(cancel_url_fallback="/cancel-fallback")
self.request.get_referrer = MagicMock(return_value="NOPE")
self.assertEqual(form.get_cancel_url(), "/cancel-fallback")
del self.request.get_referrer
# or can be referrer fallback, i.e. home page
form = self.make_form()
def get_referrer(default=None):
if default == "NOPE":
return "NOPE"
return "/home-page"
self.request.get_referrer = get_referrer
self.assertEqual(form.get_cancel_url(), "/home-page")
del self.request.get_referrer
def test_get_label(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.get_label("foo"), "Foo")
form.set_label("foo", "Baz")
self.assertEqual(form.get_label("foo"), "Baz")
def test_set_label(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.get_label("foo"), "Foo")
form.set_label("foo", "Baz")
self.assertEqual(form.get_label("foo"), "Baz")
# schema should be updated when setting label
schema = self.make_schema()
form = self.make_form(schema=schema)
form.set_label("foo", "Woohoo")
self.assertEqual(form.get_label("foo"), "Woohoo")
self.assertEqual(schema["foo"].title, "Woohoo")
def test_readonly_fields(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.readonly_fields, set())
self.assertFalse(form.is_readonly("foo"))
form.set_readonly("foo")
self.assertEqual(form.readonly_fields, {"foo"})
self.assertTrue(form.is_readonly("foo"))
self.assertFalse(form.is_readonly("bar"))
form.set_readonly("bar")
self.assertEqual(form.readonly_fields, {"foo", "bar"})
self.assertTrue(form.is_readonly("foo"))
self.assertTrue(form.is_readonly("bar"))
form.set_readonly("foo", False)
self.assertEqual(form.readonly_fields, {"bar"})
self.assertFalse(form.is_readonly("foo"))
self.assertTrue(form.is_readonly("bar"))
def test_required_fields(self):
form = self.make_form(fields=["foo", "bar"])
self.assertEqual(form.required_fields, {})
self.assertIsNone(form.is_required("foo"))
form.set_required("foo")
self.assertEqual(form.required_fields, {"foo": True})
self.assertTrue(form.is_required("foo"))
self.assertIsNone(form.is_required("bar"))
form.set_required("bar")
self.assertEqual(form.required_fields, {"foo": True, "bar": True})
self.assertTrue(form.is_required("foo"))
self.assertTrue(form.is_required("bar"))
form.set_required("foo", False)
self.assertEqual(form.required_fields, {"foo": False, "bar": True})
self.assertFalse(form.is_required("foo"))
self.assertTrue(form.is_required("bar"))
def test_render_vue_tag(self):
schema = self.make_schema()
form = self.make_form(schema=schema)
html = form.render_vue_tag()
self.assertEqual(html, "<wutta-form></wutta-form>")
def test_render_vue_template(self):
self.pyramid_config.include("pyramid_mako")
self.pyramid_config.add_subscriber(
"wuttaweb.subscribers.before_render", "pyramid.events.BeforeRender"
)
# form button is disabled on @submit by default
schema = self.make_schema()
form = self.make_form(schema=schema, cancel_url="/")
html = form.render_vue_template()
self.assertIn('<script type="text/x-template" id="wutta-form-template">', html)
self.assertIn("@submit", html)
# but not if form is configured otherwise
form = self.make_form(schema=schema, auto_disable_submit=False, cancel_url="/")
html = form.render_vue_template()
self.assertIn('<script type="text/x-template" id="wutta-form-template">', html)
self.assertNotIn("@submit", html)
def test_add_grid_vue_context(self):
form = self.make_form()
# grid must have key
grid = Grid(self.request)
self.assertRaises(ValueError, form.add_grid_vue_context, grid)
# otherwise it works
grid = Grid(self.request, key="foo")
self.assertEqual(len(form.grid_vue_context), 0)
form.add_grid_vue_context(grid)
self.assertEqual(len(form.grid_vue_context), 1)
self.assertIn("foo", form.grid_vue_context)
self.assertEqual(
form.grid_vue_context["foo"],
{
"data": [],
"row_classes": {},
},
)
# calling again with same key will replace data
records = [{"foo": 1}, {"foo": 2}]
grid = Grid(self.request, key="foo", columns=["foo"], data=records)
form.add_grid_vue_context(grid)
self.assertEqual(len(form.grid_vue_context), 1)
self.assertIn("foo", form.grid_vue_context)
self.assertEqual(
form.grid_vue_context["foo"],
{
"data": records,
"row_classes": {},
},
)
def test_render_vue_finalize(self):
form = self.make_form()
html = form.render_vue_finalize()
self.assertIn("<script>", html)
self.assertIn("Vue.component('wutta-form', WuttaForm)", html)
def test_get_field_vmodel(self):
model = self.app.model
form = self.make_form(model_class=model.Setting)
result = form.get_field_vmodel("name")
self.assertEqual(result, "modelData.deformField1")
def test_render_vue_fields(self):
model = self.app.model
form = self.make_form(model_class=model.Setting)
context = form.get_vue_context()
# standard behavior
html = form.render_vue_fields(context)
self.assertIn("<b-field", html)
self.assertNotIn("SOMETHING CRAZY", html)
self.assertNotIn("RANDOM TEXT", html)
# declare main template, so form will look for the fields def
# (but this template has no def)
template = get_renderer("/main_template.mako").template
with patch.dict(context, {"main_template": template}):
html = form.render_vue_fields(context)
self.assertIn("<b-field", html)
self.assertNotIn("SOMETHING CRAZY", html)
self.assertNotIn("RANDOM TEXT", html)
# now use a main template which has the fields def
template = get_renderer("/main_template_with_fields.mako").template
with patch.dict(context, {"main_template": template}):
html = form.render_vue_fields(context)
self.assertIn("<b-field", html)
self.assertIn("SOMETHING CRAZY", html)
self.assertNotIn("RANDOM TEXT", html)
def test_render_vue_field(self):
self.pyramid_config.include("pyramid_deform")
schema = self.make_schema()
form = self.make_form(schema=schema)
dform = form.get_deform()
# typical
html = form.render_vue_field("foo")
self.assertIn('<b-field :horizontal="true" label="Foo">', html)
self.assertIn('<b-input name="foo"', html)
# nb. no error message
self.assertNotIn("message", html)
# readonly
html = form.render_vue_field("foo", readonly=True)
self.assertIn('<b-field :horizontal="true" label="Foo">', html)
self.assertNotIn('<b-input name="foo"', html)
# nb. no error message
self.assertNotIn("message", html)
# with error message
with patch.object(
form, "get_field_errors", return_value=["something is wrong"]
):
html = form.render_vue_field("foo")
self.assertIn("something is wrong", html)
# add another field, but not to deform, so it should still
# display but with no widget
form.fields.append("zanzibar")
html = form.render_vue_field("zanzibar")
self.assertIn('<b-field :horizontal="true" label="Zanzibar">', html)
self.assertNotIn("<b-input", html)
# nb. no error message
self.assertNotIn("message", html)
# try that once more but with a model record instance
with patch.object(form, "model_instance", new={"zanzibar": "omgwtfbbq"}):
html = form.render_vue_field("zanzibar")
self.assertIn("<b-field", html)
self.assertIn('label="Zanzibar"', html)
self.assertNotIn("<b-input", html)
self.assertIn(">omgwtfbbq<", html)
# nb. no error message
self.assertNotIn("message", html)
def test_get_vue_field_value(self):
schema = self.make_schema()
form = self.make_form(schema=schema)
# TODO: yikes what a hack (?)
dform = form.get_deform()
dform.set_appstruct({"foo": "one", "bar": "two"})
# null for missing field
value = form.get_vue_field_value("doesnotexist")
self.assertIsNone(value)
# normal value is returned
value = form.get_vue_field_value("foo")
self.assertEqual(value, "one")
# but not if we remove field from deform
# TODO: what is the use case here again?
dform.children.remove(dform["foo"])
value = form.get_vue_field_value("foo")
self.assertIsNone(value)
def test_get_vue_model_data(self):
schema = self.make_schema()
form = self.make_form(schema=schema)
# 2 fields by default (foo, bar)
data = form.get_vue_model_data()
self.assertEqual(len(data), 2)
# still just 2 fields even if we request more
form.set_fields(["foo", "bar", "baz"])
data = form.get_vue_model_data()
self.assertEqual(len(data), 2)
# confirm bool values make it thru as-is
schema.add(colander.SchemaNode(colander.Bool(), name="baz"))
form = self.make_form(
schema=schema,
model_instance={
"foo": "one",
"bar": "two",
"baz": True,
},
)
data = form.get_vue_model_data()
self.assertEqual(list(data.values()), ["one", "two", True])
def test_has_global_errors(self):
def fail(node, value):
node.raise_invalid("things are bad!")
schema = self.make_schema()
schema.validator = fail
form = self.make_form(schema=schema)
self.assertFalse(form.has_global_errors())
self.request.method = "POST"
self.request.POST = {"foo": "one", "bar": "two"}
self.assertFalse(form.validate())
self.assertTrue(form.has_global_errors())
def test_get_global_errors(self):
def fail(node, value):
node.raise_invalid("things are bad!")
schema = self.make_schema()
schema.validator = fail
form = self.make_form(schema=schema)
self.assertEqual(form.get_global_errors(), [])
self.request.method = "POST"
self.request.POST = {"foo": "one", "bar": "two"}
self.assertFalse(form.validate())
self.assertTrue(form.get_global_errors(), ["things are bad!"])
def test_get_field_errors(self):
schema = self.make_schema()
# simple 'Required' validation failure
form = self.make_form(schema=schema)
self.request.method = "POST"
self.request.POST = {"foo": "one"}
self.assertFalse(form.validate())
errors = form.get_field_errors("bar")
self.assertEqual(errors, ["Required"])
# no errors
form = self.make_form(schema=schema)
self.request.POST = {"foo": "one", "bar": "two"}
self.assertTrue(form.validate())
errors = form.get_field_errors("bar")
self.assertEqual(errors, [])
def test_validate(self):
schema = self.make_schema()
form = self.make_form(schema=schema)
self.assertIsNone(form.validated)
# will not validate unless request is POST
self.request.POST = {"foo": "blarg", "bar": "baz"}
self.request.method = "GET"
self.assertFalse(form.validate())
self.request.method = "POST"
data = form.validate()
self.assertEqual(data, {"foo": "blarg", "bar": "baz"})
# validating a second time updates form.validated
self.request.POST = {"foo": "BLARG", "bar": "BAZ"}
data = form.validate()
self.assertEqual(data, {"foo": "BLARG", "bar": "BAZ"})
self.assertIs(form.validated, data)
# bad data does not validate
self.request.POST = {"foo": 42, "bar": None}
self.assertFalse(form.validate())
dform = form.get_deform()
self.assertEqual(len(dform.error.children), 2)
self.assertEqual(dform["foo"].errormsg, "Pstruct is not a string")
# when a form has readonly fields, validating it will *remove*
# those fields from deform/schema as well as final data dict
schema = self.make_schema()
form = self.make_form(schema=schema)
form.set_readonly("foo")
self.request.POST = {"foo": "one", "bar": "two"}
data = form.validate()
self.assertEqual(data, {"bar": "two"})
dform = form.get_deform()
self.assertNotIn("foo", schema)
self.assertNotIn("foo", dform)
self.assertIn("bar", schema)
self.assertIn("bar", dform)
class TestWuttaSchemaNode(WebTestCase):
def test_add_nodes(self):
model = self.app.model
# ColanderAlchemy does not add nodes for association proxy fields
schema = SQLAlchemySchemaNode(
model.User, includes=["username", "first_name", "last_name"]
)
self.assertIn("username", schema)
self.assertNotIn("first_name", schema)
self.assertNotIn("last_name", schema)
# but our custom class handles them okay
schema = mod.WuttaSchemaNode(
model.User, includes=["username", "first_name", "last_name"]
)
self.assertIn("username", schema)
self.assertIn("first_name", schema)
self.assertIn("last_name", schema)
def test_dictify(self):
model = self.app.model
person = model.Person(first_name="Fred", last_name="Flintstone")
user = model.User(username="freddie", person=person)
user.foo_field = "bar"
foo_field = colander.SchemaNode(colander.String())
# ColanderAlchemy does not include association proxy fields
schema = SQLAlchemySchemaNode(
model.User, includes=["username", "first_name", "last_name", foo_field]
)
dct = schema.dictify(user)
self.assertIn("username", dct)
self.assertEqual(dct["username"], "freddie")
self.assertNotIn("first_name", dct)
self.assertNotIn("last_name", dct)
self.assertNotIn("foo_field", dct)
# but our custom class handles them okay
schema = mod.WuttaSchemaNode(
model.User, includes=["username", "first_name", "last_name", foo_field]
)
dct = schema.dictify(user)
self.assertIn("username", dct)
self.assertEqual(dct["username"], "freddie")
self.assertIn("first_name", dct)
self.assertEqual(dct["first_name"], "Fred")
self.assertIn("last_name", dct)
self.assertEqual(dct["last_name"], "Flintstone")
# nb. foo_field is not assn proxy, so not auto-dictified
self.assertNotIn("foo_field", dct)
# also note special handling for null values on string proxy fields
user.last_name = None
dct = schema.dictify(user)
self.assertIn("first_name", dct)
self.assertIs(dct["first_name"], "Fred")
self.assertIn("last_name", dct)
self.assertIsNotNone(dct["last_name"])
self.assertIs(dct["last_name"], colander.null)
def test_objectify(self):
model = self.app.model
# ColanderAlchemy does not set association proxy fields
schema = SQLAlchemySchemaNode(
model.User,
includes=["username", "first_name", "last_name"],
)
user = schema.objectify(
{
"username": "freddie",
"first_name": "Fred",
"last_name": "Flintstone",
}
)
self.assertIsInstance(user, model.User)
self.assertEqual(user.username, "freddie")
self.assertIsNone(user.person)
self.assertIsNone(user.first_name)
self.assertIsNone(user.last_name)
# but our custom class handles them okay
schema = mod.WuttaSchemaNode(
model.User,
includes=["username", "first_name", "last_name"],
)
user = schema.objectify(
{
"username": "freddie",
"first_name": "Fred",
"last_name": "Flintstone",
}
)
self.assertIsInstance(user, model.User)
self.assertEqual(user.username, "freddie")
self.assertIsInstance(user.person, model.Person)
self.assertEqual(user.person.first_name, "Fred")
self.assertEqual(user.person.last_name, "Flintstone")
self.assertEqual(user.first_name, "Fred")
self.assertEqual(user.last_name, "Flintstone")
# also note special handling for null values on string proxy fields
user = schema.objectify(
{
"username": "freddie",
"first_name": "Fred",
"last_name": colander.null,
}
)
self.assertIsInstance(user, model.User)
self.assertEqual(user.username, "freddie")
self.assertIsInstance(user.person, model.Person)
self.assertEqual(user.person.first_name, "Fred")
self.assertIsNone(user.person.last_name)
self.assertEqual(user.first_name, "Fred")
self.assertIsNone(user.last_name)
class TestGetAssociationProxy(DataTestCase):
def test_basic(self):
model = self.app.model
mapper = sa.inspect(model.User)
# typical
proxy = mod.get_association_proxy(mapper, "first_name")
self.assertIsInstance(proxy, AssociationProxy)
self.assertEqual(proxy.target_collection, "person")
self.assertEqual(proxy.value_attr, "first_name")
# no such proxy
proxy = mod.get_association_proxy(mapper, "blarg")
self.assertIsNone(proxy)
class TestGetAssociationProxyTarget(DataTestCase):
def test_basic(self):
model = self.app.model
mapper = sa.inspect(model.User)
# typical
target = mod.get_association_proxy_target(mapper, "first_name")
self.assertIsInstance(target, orm.RelationshipProperty)
self.assertIs(target, mapper.get_property("person"))
# no such proxy
target = mod.get_association_proxy_target(mapper, "blarg")
self.assertIsNone(target)
class TestGetAssociationProxyColumn(DataTestCase):
def test_basic(self):
model = self.app.model
mapper = sa.inspect(model.User)
# typical
column = mod.get_association_proxy_column(mapper, "first_name")
self.assertIsInstance(column, orm.ColumnProperty)
self.assertIs(column, sa.inspect(model.Person).get_property("first_name"))
# no such proxy
column = mod.get_association_proxy_column(mapper, "blarg")
self.assertIsNone(column)