# -*- coding: utf-8; -*- import os from unittest.mock import MagicMock, patch import sqlalchemy as sa import colander import deform from pyramid.renderers import get_renderer 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, "") 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('