feat: overhaul how form vue template is rendered
now a page template can add `<%def name="form_vue_fields()">` and the form should inspect/discover and use that instead of its default
This commit is contained in:
parent
9edf6f298c
commit
49c001c9ad
9 changed files with 300 additions and 109 deletions
|
|
@ -275,6 +275,10 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
deform_form = None
|
deform_form = None
|
||||||
validated = None
|
validated = None
|
||||||
|
|
||||||
|
vue_template = "/forms/vue_template.mako"
|
||||||
|
fields_template = "/forms/vue_fields.mako"
|
||||||
|
buttons_template = "/forms/vue_buttons.mako"
|
||||||
|
|
||||||
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
|
def __init__( # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
|
||||||
self,
|
self,
|
||||||
request,
|
request,
|
||||||
|
|
@ -331,6 +335,7 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
self.show_button_cancel = show_button_cancel
|
self.show_button_cancel = show_button_cancel
|
||||||
self.button_label_cancel = button_label_cancel
|
self.button_label_cancel = button_label_cancel
|
||||||
self.auto_disable_cancel = auto_disable_cancel
|
self.auto_disable_cancel = auto_disable_cancel
|
||||||
|
self.form_attrs = {}
|
||||||
|
|
||||||
self.config = self.request.wutta_config
|
self.config = self.request.wutta_config
|
||||||
self.app = self.config.get_app()
|
self.app = self.config.get_app()
|
||||||
|
|
@ -806,7 +811,10 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
# get fields
|
# get fields
|
||||||
fields = self.get_fields()
|
fields = self.get_fields()
|
||||||
if not fields:
|
if not fields:
|
||||||
raise NotImplementedError
|
raise ValueError(
|
||||||
|
"could not determine fields list; "
|
||||||
|
"please set model_class or fields explicitly"
|
||||||
|
)
|
||||||
|
|
||||||
if self.model_class:
|
if self.model_class:
|
||||||
|
|
||||||
|
|
@ -939,7 +947,7 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
"""
|
"""
|
||||||
return HTML.tag(self.vue_tagname, **kwargs)
|
return HTML.tag(self.vue_tagname, **kwargs)
|
||||||
|
|
||||||
def render_vue_template(self, template="/forms/vue_template.mako", **context):
|
def render_vue_template(self, template=None, **context):
|
||||||
"""
|
"""
|
||||||
Render the Vue template block for the form.
|
Render the Vue template block for the form.
|
||||||
|
|
||||||
|
|
@ -954,8 +962,8 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
WuttaFormData = {}
|
const WuttaFormData = {}
|
||||||
WuttaForm = {
|
const WuttaForm = {
|
||||||
template: 'wutta-form-template',
|
template: 'wutta-form-template',
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -969,36 +977,121 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
Actual output will of course depend on form attributes, i.e.
|
Actual output will of course depend on form attributes, i.e.
|
||||||
:attr:`vue_tagname` and :attr:`fields` list etc.
|
:attr:`vue_tagname` and :attr:`fields` list etc.
|
||||||
|
|
||||||
:param template: Path to Mako template which is used to render
|
Default logic will also invoke (indirectly):
|
||||||
the output.
|
|
||||||
|
* :meth:`render_vue_fields()`
|
||||||
|
* :meth:`render_vue_buttons()`
|
||||||
|
|
||||||
|
:param template: Optional template path to override the class
|
||||||
|
default.
|
||||||
|
|
||||||
|
:returns: HTML literal
|
||||||
"""
|
"""
|
||||||
|
context = self.get_vue_context(**context)
|
||||||
|
html = render(template or self.vue_template, context)
|
||||||
|
return HTML.literal(html)
|
||||||
|
|
||||||
|
def get_vue_context(self, **context): # pylint: disable=missing-function-docstring
|
||||||
context["form"] = self
|
context["form"] = self
|
||||||
context["dform"] = self.get_deform()
|
context["dform"] = self.get_deform()
|
||||||
context.setdefault("request", self.request)
|
context.setdefault("request", self.request)
|
||||||
context["model_data"] = self.get_vue_model_data()
|
context["model_data"] = self.get_vue_model_data()
|
||||||
|
|
||||||
# set form method, enctype
|
# set form method, enctype
|
||||||
context.setdefault("form_attrs", {})
|
form_attrs = context.setdefault("form_attrs", dict(self.form_attrs))
|
||||||
context["form_attrs"].setdefault("method", self.action_method)
|
form_attrs.setdefault("method", self.action_method)
|
||||||
if self.action_method == "post":
|
if self.action_method == "post":
|
||||||
context["form_attrs"].setdefault("enctype", "multipart/form-data")
|
form_attrs.setdefault("enctype", "multipart/form-data")
|
||||||
|
|
||||||
# auto disable button on submit
|
# auto disable button on submit
|
||||||
if self.auto_disable_submit:
|
if self.auto_disable_submit:
|
||||||
context["form_attrs"]["@submit"] = "formSubmitting = true"
|
form_attrs["@submit"] = "formSubmitting = true"
|
||||||
|
|
||||||
output = render(template, context)
|
# duplicate entire context for sake of fields/buttons template
|
||||||
return HTML.literal(output)
|
context["form_context"] = context
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def render_vue_fields(self, context, template=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Render the fields section within the form template.
|
||||||
|
|
||||||
|
This is normally invoked from within the form's
|
||||||
|
``vue_template`` like this:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
${form.render_vue_fields(form_context)}
|
||||||
|
|
||||||
|
There is a default ``fields_template`` but that is only the
|
||||||
|
last resort. Logic will first look for a
|
||||||
|
``form_vue_fields()`` def within the *main template* being
|
||||||
|
rendered for the page.
|
||||||
|
|
||||||
|
An example will surely help:
|
||||||
|
|
||||||
|
.. code-block:: mako
|
||||||
|
|
||||||
|
<%inherit file="/master/edit.mako" />
|
||||||
|
|
||||||
|
<%def name="form_vue_fields()">
|
||||||
|
|
||||||
|
<p>this is my custom fields section:</p>
|
||||||
|
|
||||||
|
${form.render_vue_field("myfield")}
|
||||||
|
|
||||||
|
</%def>
|
||||||
|
|
||||||
|
This keeps the custom fields section within the main page
|
||||||
|
template as opposed to yet another file. But if your page
|
||||||
|
template has no ``form_vue_fields()`` def, then the class
|
||||||
|
default template is used. (Unless the ``template`` param
|
||||||
|
is specified.)
|
||||||
|
|
||||||
|
See also :meth:`render_vue_template()` and
|
||||||
|
:meth:`render_vue_buttons()`.
|
||||||
|
|
||||||
|
:param context: This must be the original context as provided
|
||||||
|
to the form's ``vue_template``. See example above.
|
||||||
|
|
||||||
|
:param template: Optional template path to use instead of the
|
||||||
|
defaults described above.
|
||||||
|
|
||||||
|
:returns: HTML literal
|
||||||
|
"""
|
||||||
|
context.update(kwargs)
|
||||||
|
html = False
|
||||||
|
|
||||||
|
if not template:
|
||||||
|
|
||||||
|
if main_template := context.get("main_template"):
|
||||||
|
try:
|
||||||
|
vue_fields = main_template.get_def("form_vue_fields")
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
html = vue_fields.render(**context)
|
||||||
|
|
||||||
|
if html is False:
|
||||||
|
template = self.fields_template
|
||||||
|
|
||||||
|
if html is False:
|
||||||
|
html = render(template, context)
|
||||||
|
|
||||||
|
return HTML.literal(html)
|
||||||
|
|
||||||
def render_vue_field( # pylint: disable=unused-argument,too-many-locals
|
def render_vue_field( # pylint: disable=unused-argument,too-many-locals
|
||||||
self,
|
self,
|
||||||
fieldname,
|
fieldname,
|
||||||
readonly=None,
|
readonly=None,
|
||||||
|
label=True,
|
||||||
|
horizontal=True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Render the given field completely, i.e. ``<b-field>`` wrapper
|
Render the given field completely, i.e. ``<b-field>`` wrapper
|
||||||
with label and containing a widget.
|
with label and a widget, with validation errors flagged as
|
||||||
|
needed.
|
||||||
|
|
||||||
Actual output will depend on the field attributes etc.
|
Actual output will depend on the field attributes etc.
|
||||||
Typical output might look like:
|
Typical output might look like:
|
||||||
|
|
@ -1009,14 +1102,23 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
horizontal
|
horizontal
|
||||||
type="is-danger"
|
type="is-danger"
|
||||||
message="something went wrong!">
|
message="something went wrong!">
|
||||||
<!-- widget element(s) -->
|
<b-input name="foo"
|
||||||
|
v-model="${form.get_field_vmodel('foo')}" />
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
.. warning::
|
:param fieldname: Name of field to render.
|
||||||
|
|
||||||
Any ``**kwargs`` received from caller are ignored by this
|
:param readonly: Optional override for readonly flag.
|
||||||
method. For now they are allowed, for sake of backwawrd
|
|
||||||
compatibility. This may change in the future.
|
:param label: Whether to include/set the field label.
|
||||||
|
|
||||||
|
:param horizontal: Boolean value for the ``horizontal`` flag
|
||||||
|
on the field.
|
||||||
|
|
||||||
|
:param \\**kwargs: Remaining kwargs are passed to widget's
|
||||||
|
``serialize()`` method.
|
||||||
|
|
||||||
|
:returns: HTML literal
|
||||||
"""
|
"""
|
||||||
# readonly comes from: caller, field flag, or form flag
|
# readonly comes from: caller, field flag, or form flag
|
||||||
if readonly is None:
|
if readonly is None:
|
||||||
|
|
@ -1034,10 +1136,9 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
|
|
||||||
# render proper widget if field is in deform/schema
|
# render proper widget if field is in deform/schema
|
||||||
field = dform[fieldname]
|
field = dform[fieldname]
|
||||||
kw = {}
|
|
||||||
if readonly:
|
if readonly:
|
||||||
kw["readonly"] = True
|
kwargs["readonly"] = True
|
||||||
html = field.serialize(**kw)
|
html = field.serialize(**kwargs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# render static text if field not in deform/schema
|
# render static text if field not in deform/schema
|
||||||
|
|
@ -1052,12 +1153,13 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
html = HTML.literal(html or " ")
|
html = HTML.literal(html or " ")
|
||||||
|
|
||||||
# render field label
|
# render field label
|
||||||
|
if label:
|
||||||
label = self.get_label(fieldname)
|
label = self.get_label(fieldname)
|
||||||
|
|
||||||
# b-field attrs
|
# b-field attrs
|
||||||
attrs = {
|
attrs = {
|
||||||
":horizontal": "true",
|
":horizontal": "true" if horizontal else "false",
|
||||||
"label": label,
|
"label": label or "",
|
||||||
}
|
}
|
||||||
|
|
||||||
# next we will build array of messages to display..some
|
# next we will build array of messages to display..some
|
||||||
|
|
@ -1085,6 +1187,36 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
|
|
||||||
return HTML.tag("b-field", c=[html], **attrs)
|
return HTML.tag("b-field", c=[html], **attrs)
|
||||||
|
|
||||||
|
def render_vue_buttons(self, context, template=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Render the buttons section within the form template.
|
||||||
|
|
||||||
|
This is normally invoked from within the form's
|
||||||
|
``vue_template`` like this:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
${form.render_vue_buttons(form_context)}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This method does not yet inspect the main page template,
|
||||||
|
unlike :meth:`render_vue_fields()`.
|
||||||
|
|
||||||
|
See also :meth:`render_vue_template()`.
|
||||||
|
|
||||||
|
:param context: This must be the original context as provided
|
||||||
|
to the form's ``vue_template``. See example above.
|
||||||
|
|
||||||
|
:param template: Optional template path to override the class
|
||||||
|
default.
|
||||||
|
|
||||||
|
:returns: HTML literal
|
||||||
|
"""
|
||||||
|
context.update(kwargs)
|
||||||
|
html = render(template or self.buttons_template, context)
|
||||||
|
return HTML.literal(html)
|
||||||
|
|
||||||
def render_vue_finalize(self):
|
def render_vue_finalize(self):
|
||||||
"""
|
"""
|
||||||
Render the Vue "finalize" script for the form.
|
Render the Vue "finalize" script for the form.
|
||||||
|
|
@ -1103,6 +1235,25 @@ class Form: # pylint: disable=too-many-instance-attributes,too-many-public-meth
|
||||||
"""
|
"""
|
||||||
return render_vue_finalize(self.vue_tagname, self.vue_component)
|
return render_vue_finalize(self.vue_tagname, self.vue_component)
|
||||||
|
|
||||||
|
def get_field_vmodel(self, field):
|
||||||
|
"""
|
||||||
|
Convenience to return the ``v-model`` data reference for the
|
||||||
|
given field. For instance:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
<b-input name="myfield"
|
||||||
|
v-model="${form.get_field_vmodel('myfield')}" />
|
||||||
|
|
||||||
|
<div v-show="${form.get_field_vmodel('myfield')} == 'easter'">
|
||||||
|
easter egg!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
:returns: JS-valid string referencing the field value
|
||||||
|
"""
|
||||||
|
dform = self.get_deform()
|
||||||
|
return f"modelData.{dform[field].oid}"
|
||||||
|
|
||||||
def get_vue_model_data(self):
|
def get_vue_model_data(self):
|
||||||
"""
|
"""
|
||||||
Returns a dict with form model data. Values may be nested
|
Returns a dict with form model data. Values may be nested
|
||||||
|
|
|
||||||
|
|
@ -34,17 +34,19 @@
|
||||||
|
|
||||||
<%def name="tool_panels()"></%def>
|
<%def name="tool_panels()"></%def>
|
||||||
|
|
||||||
<%def name="render_vue_template_form()">
|
|
||||||
% if form is not Undefined:
|
|
||||||
${form.render_vue_template()}
|
|
||||||
% endif
|
|
||||||
</%def>
|
|
||||||
|
|
||||||
<%def name="render_vue_templates()">
|
<%def name="render_vue_templates()">
|
||||||
${parent.render_vue_templates()}
|
${parent.render_vue_templates()}
|
||||||
${self.render_vue_template_form()}
|
${self.render_vue_template_form()}
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
<%def name="render_vue_template_form()">
|
||||||
|
% if form is not Undefined:
|
||||||
|
## nb. must provide main template to form, so it can
|
||||||
|
## do 'def' lookup as needed for fields template etc.
|
||||||
|
${form.render_vue_template(main_template=self.template)}
|
||||||
|
% endif
|
||||||
|
</%def>
|
||||||
|
|
||||||
<%def name="make_vue_components()">
|
<%def name="make_vue_components()">
|
||||||
${parent.make_vue_components()}
|
${parent.make_vue_components()}
|
||||||
% if form is not Undefined:
|
% if form is not Undefined:
|
||||||
|
|
|
||||||
43
src/wuttaweb/templates/forms/vue_buttons.mako
Normal file
43
src/wuttaweb/templates/forms/vue_buttons.mako
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
% if not form.readonly:
|
||||||
|
<br />
|
||||||
|
<div class="buttons"
|
||||||
|
% if form.align_buttons_right:
|
||||||
|
style="justify-content: right;"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
|
||||||
|
% if form.show_button_cancel:
|
||||||
|
<wutta-button ${'once' if form.auto_disable_cancel else ''}
|
||||||
|
tag="a" href="${form.get_cancel_url()}"
|
||||||
|
label="${form.button_label_cancel}" />
|
||||||
|
% endif
|
||||||
|
|
||||||
|
% if form.show_button_reset:
|
||||||
|
<b-button
|
||||||
|
% if form.reset_url:
|
||||||
|
tag="a" href="${form.reset_url}"
|
||||||
|
% else:
|
||||||
|
native-type="reset"
|
||||||
|
% endif
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</b-button>
|
||||||
|
% endif
|
||||||
|
|
||||||
|
<b-button type="${form.button_type_submit}"
|
||||||
|
native-type="submit"
|
||||||
|
% if form.auto_disable_submit:
|
||||||
|
:disabled="formSubmitting"
|
||||||
|
% endif
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="${form.button_icon_submit}">
|
||||||
|
% if form.auto_disable_submit:
|
||||||
|
{{ formSubmitting ? "Working, please wait..." : "${form.button_label_submit}" }}
|
||||||
|
% else:
|
||||||
|
${form.button_label_submit}
|
||||||
|
% endif
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
% endif
|
||||||
4
src/wuttaweb/templates/forms/vue_fields.mako
Normal file
4
src/wuttaweb/templates/forms/vue_fields.mako
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
% for fieldname in form:
|
||||||
|
${form.render_vue_field(fieldname, horizontal=True)}
|
||||||
|
% endfor
|
||||||
|
|
@ -14,66 +14,21 @@
|
||||||
% endfor
|
% endfor
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
<section>
|
${form.render_vue_fields(form_context)}
|
||||||
% for fieldname in form:
|
|
||||||
${form.render_vue_field(fieldname)}
|
|
||||||
% endfor
|
|
||||||
</section>
|
|
||||||
|
|
||||||
% if not form.readonly:
|
${form.render_vue_buttons(form_context)}
|
||||||
<br />
|
|
||||||
<div class="buttons"
|
|
||||||
% if form.align_buttons_right:
|
|
||||||
style="justify-content: right;"
|
|
||||||
% endif
|
|
||||||
>
|
|
||||||
|
|
||||||
% if form.show_button_cancel:
|
|
||||||
<wutta-button ${'once' if form.auto_disable_cancel else ''}
|
|
||||||
tag="a" href="${form.get_cancel_url()}"
|
|
||||||
label="${form.button_label_cancel}" />
|
|
||||||
% endif
|
|
||||||
|
|
||||||
% if form.show_button_reset:
|
|
||||||
<b-button
|
|
||||||
% if form.reset_url:
|
|
||||||
tag="a" href="${form.reset_url}"
|
|
||||||
% else:
|
|
||||||
native-type="reset"
|
|
||||||
% endif
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</b-button>
|
|
||||||
% endif
|
|
||||||
|
|
||||||
<b-button type="${form.button_type_submit}"
|
|
||||||
native-type="submit"
|
|
||||||
% if form.auto_disable_submit:
|
|
||||||
:disabled="formSubmitting"
|
|
||||||
% endif
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="${form.button_icon_submit}">
|
|
||||||
% if form.auto_disable_submit:
|
|
||||||
{{ formSubmitting ? "Working, please wait..." : "${form.button_label_submit}" }}
|
|
||||||
% else:
|
|
||||||
${form.button_label_submit}
|
|
||||||
% endif
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
% endif
|
|
||||||
|
|
||||||
${h.end_form()}
|
${h.end_form()}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
let ${form.vue_component} = {
|
const ${form.vue_component} = {
|
||||||
template: '#${form.vue_tagname}-template',
|
template: '#${form.vue_tagname}-template',
|
||||||
methods: {},
|
methods: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
let ${form.vue_component}Data = {
|
const ${form.vue_component}Data = {
|
||||||
|
|
||||||
% if not form.readonly:
|
% if not form.readonly:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,8 @@ class WebTestCase(DataTestCase):
|
||||||
Base class for test suites requiring a full (typical) web app.
|
Base class for test suites requiring a full (typical) web app.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
mako_directories = ["wuttaweb:templates"]
|
||||||
|
|
||||||
def setUp(self): # pylint: disable=empty-docstring
|
def setUp(self): # pylint: disable=empty-docstring
|
||||||
""" """
|
""" """
|
||||||
self.setup_web()
|
self.setup_web()
|
||||||
|
|
@ -57,7 +59,7 @@ class WebTestCase(DataTestCase):
|
||||||
request=self.request,
|
request=self.request,
|
||||||
settings={
|
settings={
|
||||||
"wutta_config": self.config,
|
"wutta_config": self.config,
|
||||||
"mako.directories": ["wuttaweb:templates"],
|
"mako.directories": self.mako_directories,
|
||||||
"pyramid_deform.template_search_path": "wuttaweb:templates/deform",
|
"pyramid_deform.template_search_path": "wuttaweb:templates/deform",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
9
tests/forms/main_template.mako
Normal file
9
tests/forms/main_template.mako
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/form.mako" />
|
||||||
|
|
||||||
|
<%def name="page_content()">
|
||||||
|
|
||||||
|
RANDOM TEXT
|
||||||
|
|
||||||
|
${parent.page_content()}
|
||||||
|
</%def>
|
||||||
11
tests/forms/main_template_with_fields.mako
Normal file
11
tests/forms/main_template_with_fields.mako
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
## -*- coding: utf-8; -*-
|
||||||
|
<%inherit file="/form.mako" />
|
||||||
|
|
||||||
|
<%def name="form_vue_fields()">
|
||||||
|
|
||||||
|
SOMETHING CRAZY
|
||||||
|
|
||||||
|
<b-field label="name">
|
||||||
|
<b-input name="name" v-model="${form.get_field_vmodel('name')}" />
|
||||||
|
</b-field>
|
||||||
|
</%def>
|
||||||
|
|
@ -1,48 +1,28 @@
|
||||||
# -*- coding: utf-8; -*-
|
# -*- coding: utf-8; -*-
|
||||||
|
|
||||||
from unittest import TestCase
|
import os
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
import colander
|
import colander
|
||||||
import deform
|
import deform
|
||||||
from pyramid import testing
|
from pyramid.renderers import get_renderer
|
||||||
|
|
||||||
from wuttjamaican.conf import WuttaConfig
|
from wuttaweb.forms import base as mod, widgets
|
||||||
from wuttaweb.forms import base, widgets
|
|
||||||
from wuttaweb import helpers, subscribers
|
|
||||||
from wuttaweb.grids import Grid
|
from wuttaweb.grids import Grid
|
||||||
|
from wuttaweb.testing import WebTestCase
|
||||||
|
|
||||||
|
|
||||||
class TestForm(TestCase):
|
here = os.path.dirname(__file__)
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.config = WuttaConfig(
|
|
||||||
defaults={
|
|
||||||
"wutta.web.menus.handler_spec": "tests.util:NullMenuHandler",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.app = self.config.get_app()
|
|
||||||
self.request = testing.DummyRequest(wutta_config=self.config, use_oruga=False)
|
|
||||||
|
|
||||||
self.pyramid_config = testing.setUp(
|
class TestForm(WebTestCase):
|
||||||
request=self.request,
|
|
||||||
settings={
|
|
||||||
"wutta_config": self.config,
|
|
||||||
"mako.directories": ["wuttaweb:templates"],
|
|
||||||
"pyramid_deform.template_search_path": "wuttaweb:templates/deform",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
event = MagicMock(request=self.request)
|
mako_directories = ["wuttaweb:templates", here]
|
||||||
subscribers.new_request(event)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
testing.tearDown()
|
|
||||||
|
|
||||||
def make_form(self, **kwargs):
|
def make_form(self, **kwargs):
|
||||||
return base.Form(self.request, **kwargs)
|
return mod.Form(self.request, **kwargs)
|
||||||
|
|
||||||
def make_schema(self):
|
def make_schema(self):
|
||||||
schema = colander.Schema(
|
schema = colander.Schema(
|
||||||
|
|
@ -279,7 +259,7 @@ class TestForm(TestCase):
|
||||||
# but auto-generating without fields is not supported
|
# but auto-generating without fields is not supported
|
||||||
form = self.make_form()
|
form = self.make_form()
|
||||||
self.assertIsNone(form.schema)
|
self.assertIsNone(form.schema)
|
||||||
self.assertRaises(NotImplementedError, form.get_schema)
|
self.assertRaises(ValueError, form.get_schema)
|
||||||
|
|
||||||
# schema is auto-generated if model_class provided
|
# schema is auto-generated if model_class provided
|
||||||
form = self.make_form(model_class=model.Setting)
|
form = self.make_form(model_class=model.Setting)
|
||||||
|
|
@ -541,6 +521,40 @@ class TestForm(TestCase):
|
||||||
self.assertIn("<script>", html)
|
self.assertIn("<script>", html)
|
||||||
self.assertIn("Vue.component('wutta-form', WuttaForm)", 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):
|
def test_render_vue_field(self):
|
||||||
self.pyramid_config.include("pyramid_deform")
|
self.pyramid_config.include("pyramid_deform")
|
||||||
schema = self.make_schema()
|
schema = self.make_schema()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue