Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/deform/field.py : 22%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Field."""
2# Standard Library
3import itertools
4import re
5import unicodedata
6import weakref
8# Pyramid
9import colander
10from chameleon.utils import Markup
11import peppercorn
13# Deform
14from deform.widget import HiddenWidget
16from . import compat
17from . import decorator
18from . import exception
19from . import schema
20from . import template
21from . import widget
24class _Marker(object):
25 def __repr__(self): # pragma: no cover
26 return "(Default)"
28 __str__ = __repr__
31_marker = _Marker()
34class Field(object):
35 """Represents an individual form field (a visible object in a
36 form rendering).
38 A :class:`deform.form.Field` object instance is meant to last for
39 the duration of a single web request. As a result, a field object
40 is often used as a scratchpad by the widget associated with that
41 field. Using a field as a scratchpad makes it possible to build
42 implementations of state-retaining widgets while instances of
43 those widget still only need to be constructed once instead of on
44 each request.
46 *Attributes*
48 schema
49 The schema node associated with this field.
51 widget
52 The widget associated with this field. When no widget is
53 defined in the schema node, a default widget will be created.
54 The default widget will have a generated item_css_class
55 containing the normalized version of the ``name`` attribute
56 (with ``item`` prepended, e.g. ``item-username``).
57 NOTE: This behaviour is deprecated and will be removed in
58 the future. Mapping and Sequence Widget templates simply
59 render a css class on an item's container based on Field
60 information.
62 order
63 An integer indicating the relative order of this field's
64 construction to its children and parents.
66 oid
67 A string incorporating the ``order`` attribute that can be
68 used as a unique identifier in HTML code (often for ``id``
69 attributes of field-related elements). A default oid is
70 generated that looks like this: ``deformField0``. A
71 custom oid can provided, but if the field is cloned,
72 the clones will get unique default oids.
74 name
75 An alias for self.schema.name
77 title
78 An alias for self.schema.title
80 description
81 An alias for self.schema.description
83 required
84 An alias for self.schema.required
86 typ
87 An alias for self.schema.typ
89 children
90 Child fields of this field.
92 parent
93 The parent field of this field or ``None`` if this field is
94 the root. This is actually a property that returns the result
95 of ``weakref.ref(actualparent)()`` to avoid leaks due to circular
96 references, but it can be treated like the field itself.
98 error
99 The exception raised by the last attempted validation of the
100 schema element associated with this field. By default, this
101 attribute is ``None``. If non-None, this attribute is usually
102 an instance of the exception class
103 :exc:`colander.Invalid`, which has a ``msg`` attribute
104 providing a human-readable validation error message.
106 errormsg
107 The ``msg`` attribute of the ``error`` attached to this field
108 or ``None`` if the ``error`` attached to this field is ``None``.
110 renderer
111 The template :term:`renderer` associated with the form. If a
112 renderer is not passed to the constructor, the default deform
113 renderer will be used (the :term:`default renderer`).
115 counter
116 ``None`` or an instance of ``itertools.counter`` which is used
117 to generate sequential order-related attributes such as
118 ``oid`` and ``order``.
120 resource_registry
121 The :term:`resource registry` associated with this field.
123 autofocus
124 If the field's parent form has its ``focus`` argument set to
125 ``on``, the first field out of all fields in this form with
126 ``autofocus`` set to a true-ish value (``on``, ``True``, or
127 ``autofocus``) will receive focus on page load. Default: ``None``.
129 *Constructor Arguments*
131 ``renderer``, ``counter``, ``resource_registry`` and ``appstruct`` are
132 accepted as explicit keyword arguments to the :class:`deform.Field`.
133 These are also available as attribute values. ``renderer``, if passed,
134 is a template renderer as described in :ref:`creating_a_renderer`.
135 ``counter``, if passed, should be an :attr:`itertools.counter` object
136 (useful when rendering multiple forms on the same page, see
137 https://deformdemo.pylonsproject.org/multiple_forms/.
138 ``resource_registry``, if passed should be a widget resource registry
139 (see also :ref:`get_widget_resources`).
141 If any of these values is not passed, a suitable default values is used
142 in its place.
144 The ``appstruct`` constructor argument is used to prepopulate field
145 values related to this form's schema. If an appstruct is not supplied,
146 the form's fields will be rendered with default values unless an
147 appstruct is supplied to the ``render`` method explicitly.
149 The :class:`deform.Field` constructor also accepts *arbitrary*
150 keyword arguments. When an 'unknown' keyword argument is
151 passed, it is attached unmodified to the form field as an
152 attribute.
154 All keyword arguments (explicit and unknown) are also attached to
155 all *children* nodes of the field being constructed.
157 """
159 error = None
160 _cstruct = colander.null
161 default_renderer = template.default_renderer
162 default_resource_registry = widget.default_resource_registry
163 # Allowable input types for automatic focusing
164 focusable_input_types = (
165 type(colander.Boolean()),
166 type(colander.Date()),
167 type(colander.DateTime()),
168 type(colander.Decimal()),
169 type(colander.Float()),
170 type(colander.Integer()),
171 type(colander.Set()),
172 type(colander.String()),
173 type(colander.Time()),
174 )
175 hidden_type = type(HiddenWidget())
177 def __init__(
178 self,
179 schema,
180 renderer=None,
181 counter=None,
182 resource_registry=None,
183 appstruct=colander.null,
184 parent=None,
185 autofocus=None,
186 **kw
187 ):
188 self.counter = counter or itertools.count()
189 self.order = next(self.counter)
190 self.oid = getattr(schema, "oid", "deformField%s" % self.order)
191 self.schema = schema
192 self.typ = schema.typ # required by Invalid exception
193 self.name = schema.name
194 self.title = schema.title
195 self.description = schema.description
196 self.required = schema.required
197 if renderer is None:
198 renderer = self.default_renderer
199 if resource_registry is None:
200 resource_registry = self.default_resource_registry
201 self.renderer = renderer
203 # Parameters passed from parent field to child
204 if "focus" in kw:
205 focus = kw["focus"]
206 else:
207 focus = "on"
208 if "have_first_input" in kw:
209 self.have_first_input = kw["have_first_input"]
210 else:
211 self.have_first_input = False
213 if (
214 focus == "off"
215 or autofocus is None
216 or autofocus is False
217 or str(autofocus).lower() == "off"
218 ):
219 self.autofocus = None
220 else:
221 self.autofocus = "autofocus"
223 self.resource_registry = resource_registry
224 self.children = []
225 if parent is not None:
226 parent = weakref.ref(parent)
227 self._parent = parent
228 self.__dict__.update(kw)
230 first_input_index = -1
231 child_count = 0
232 focused = False
233 for child in schema.children:
234 if (
235 focus == "on"
236 and not focused
237 and type(child.typ) in Field.focusable_input_types
238 and type(child.widget) != Field.hidden_type
239 and not self.have_first_input
240 ):
241 first_input_index = child_count
242 self.found_first() # Notify ancestors
243 autofocus = getattr(child, "autofocus", None)
245 if autofocus is not None:
246 focused = True
248 kw["have_first_input"] = self.have_first_input
249 self.children.append(
250 Field(
251 child,
252 renderer=renderer,
253 counter=self.counter,
254 resource_registry=resource_registry,
255 parent=self,
256 autofocus=autofocus,
257 **kw
258 )
259 )
260 child_count += 1
261 if (
262 focus == "on"
263 and not focused
264 and first_input_index != -1
265 and self.have_first_input
266 ):
267 # User did not set autofocus. Focus on first valid input.
268 self.children[first_input_index].autofocus = "autofocus"
269 self.set_appstruct(appstruct)
271 def found_first(self):
272 """ Set have_first_input of ancestors """
273 self.have_first_input = True
274 if self.parent is not None:
275 self.parent.found_first()
277 @property
278 def parent(self):
279 if self._parent is None:
280 return None
281 return self._parent()
283 def get_root(self):
284 """ Return the root field in the field hierarchy (the form field) """
285 node = self
286 while True:
287 parent = node.parent
288 if parent is None:
289 break
290 node = parent
291 return node
293 @classmethod
294 def set_zpt_renderer(
295 cls,
296 search_path,
297 auto_reload=True,
298 debug=True,
299 encoding="utf-8",
300 translator=None,
301 ):
302 """Create a :term:`Chameleon` ZPT renderer that will act as a
303 :term:`default renderer` for instances of the associated class
304 when no ``renderer`` argument is provided to the class'
305 constructor. The arguments to this classmethod have the same
306 meaning as the arguments provided to a
307 :class:`deform.ZPTRendererFactory`.
309 Calling this method resets the :term:`default renderer`.
311 This method is effectively a shortcut for
312 ``cls.set_default_renderer(ZPTRendererFactory(...))``."""
313 cls.default_renderer = template.ZPTRendererFactory(
314 search_path,
315 auto_reload=auto_reload,
316 debug=debug,
317 encoding=encoding,
318 translator=translator,
319 )
321 @classmethod
322 def set_default_renderer(cls, renderer):
323 """Set the callable that will act as a default renderer for
324 instances of the associated class when no ``renderer``
325 argument is provided to the class' constructor. Useful when
326 you'd like to use an alternate templating system.
328 Calling this method resets the :term:`default renderer`.
329 """
330 cls.default_renderer = staticmethod(renderer)
332 @classmethod
333 def set_default_resource_registry(cls, registry):
335 """Set the callable that will act as a default
336 :term:`resource registry` for instances of the associated
337 class when no ``resource_registry`` argument is provided to
338 the class' constructor. Useful when you'd like to use
339 non-default requirement to resource path mappings for the
340 entirety of a process.
342 Calling this method resets the default :term:`resource registry`.
343 """
344 cls.default_resource_registry = registry
346 def translate(self, msgid):
347 """Use the translator passed to the renderer of this field to
348 translate the msgid into a term and return the term. If the renderer
349 does not have a translator, this method will return the msgid."""
350 translate = getattr(self.renderer, "translate", None)
351 if translate is not None:
352 return translate(msgid)
353 return msgid
355 def __iter__(self):
356 """ Iterate over the children fields of this field. """
357 return iter(self.children)
359 def __getitem__(self, name):
360 """Return the subfield of this field named ``name`` or raise
361 a :exc:`KeyError` if a subfield does not exist named ``name``."""
362 for child in self.children:
363 if child.name == name:
364 return child
365 raise KeyError(name)
367 def __contains__(self, name):
368 for child in self.children:
369 if child.name == name:
370 return True
371 return False
373 def clone(self):
374 """Clone the field and its subfields, retaining attribute
375 information. Return the cloned field. The ``order``
376 attribute of the node is not cloned; instead the field
377 receives a new order attribute; it will be a number larger
378 than the last rendered field of this set. The parent of the cloned
379 node will become ``None`` unconditionally."""
380 cloned = self.__class__(self.schema)
381 cloned.__dict__.update(self.__dict__)
382 cloned.order = next(cloned.counter)
383 cloned.oid = "deformField%s" % cloned.order
384 cloned._parent = None
385 children = []
386 for field in self.children:
387 cloned_child = field.clone()
388 cloned_child._parent = weakref.ref(cloned)
389 children.append(cloned_child)
390 cloned.children = children
391 return cloned
393 @decorator.reify
394 def widget(self):
395 """If a widget is not assigned directly to a field, this
396 function will be called to generate a default widget (only
397 once). The result of this function will then be assigned as
398 the ``widget`` attribute of the field for the rest of the
399 lifetime of this field. If a widget is assigned to a field
400 before form processing, this function will not be called."""
401 wdg = getattr(self.schema, "widget", None)
402 if wdg is not None:
403 return wdg
404 widget_maker = getattr(self.schema.typ, "widget_maker", None)
405 if widget_maker is None:
406 widget_maker = schema.default_widget_makers.get(
407 self.schema.typ.__class__
408 )
409 if widget_maker is None:
410 for (cls, wgt) in schema.default_widget_makers.items():
411 if isinstance(self.schema.typ, cls):
412 widget_maker = wgt
413 break
414 if widget_maker is None:
415 widget_maker = widget.TextInputWidget
416 return widget_maker(item_css_class=self.default_item_css_class())
418 def default_item_css_class(self):
419 if not self.name:
420 return None
422 css_class = (
423 unicodedata.normalize("NFKD", compat.text_type(self.name))
424 .encode("ascii", "ignore")
425 .decode("ascii")
426 )
427 css_class = re.sub(r"[^\w\s-]", "", css_class).strip().lower() # noQA
428 css_class = re.sub(r"[-\s]+", "-", css_class) # noQA
429 return "item-%s" % css_class
431 def get_widget_requirements(self):
432 """Return a sequence of two tuples in the form
433 [(``requirement_name``, ``version``), ..].
435 The first element in each two-tuple represents a requirement
436 name. When a requirement name is returned as part of
437 ``get_widget_requirements``, it means that one or more CSS or
438 Javascript resources need to be loaded by the page performing
439 the form rendering in order for some widget on the page to
440 function properly.
442 The second element in each two-tuple is the requested version
443 of the library resource. It may be ``None``, in which case
444 the version is unspecified.
446 See also the ``requirements`` attribute of
447 :class:`deform.Widget` and the explanation of widget
448 requirements in :ref:`get_widget_requirements`.
449 """
450 L = []
452 requirements = [req for req in self.widget.requirements] + [
453 req
454 for child in self.children
455 for req in child.get_widget_requirements()
456 ]
458 if requirements:
459 for requirement in requirements:
460 if isinstance(requirement, dict):
461 L.append(requirement)
462 else:
463 reqt = tuple(requirement)
464 if reqt not in L:
465 L.append(reqt)
466 return L
468 def get_widget_resources(self, requirements=None):
469 """Return a resources dictionary in the form ``{'js':[seq],
470 'css':[seq]}``. ``js`` represents Javascript resources,
471 ``css`` represents CSS resources. ``seq`` represents a
472 sequence of resource paths. Each path in ``seq`` represents a
473 relative resource name, as defined by the mapping of a
474 requirement to a set of resource specification by the
475 :term:`resource registry` attached to this field or form.
477 This method may raise a :exc:`ValueError` if the resource
478 registry associated with this field or form cannot resolve a
479 requirement to a set of resource paths.
481 The ``requirements`` argument represents a set of requirements
482 as returned by a manual call to
483 :meth:`deform.Field.get_widget_requirements`. If
484 ``requirements`` is not supplied, the requirement are implied
485 by calling the :meth:`deform.Field.get_widget_requirements`
486 method against this form field.
488 See also :ref:`get_widget_resources`.
489 """
490 if requirements is None:
491 requirements = self.get_widget_requirements()
492 resources = self.resource_registry(
493 (req for req in requirements if not isinstance(req, dict))
494 )
495 for req in requirements:
496 if not isinstance(req, dict):
497 continue
498 for key in {'js', 'css'}.intersection(req):
499 value = req[key]
500 if isinstance(value, str):
501 resources[key].append(value)
502 else:
503 resources[key].extend(value)
504 return resources
506 def set_widgets(self, values, separator="."):
507 """set widgets of the child fields of this field
508 or form element. ``widgets`` should be a dictionary in the
509 form::
511 {'dotted.field.name':Widget(),
512 'dotted.field.name2':Widget()}
514 The keys of the dictionary are dotted names. Each dotted name
515 refers to a single field in the tree of fields that are
516 children of the field or form object upon which this method is
517 called.
519 The dotted name is split on its dots and the resulting list of
520 names is used as a search path into the child fields of this
521 field in order to find a field to which to assign the
522 associated widget.
524 Two special cases exist:
526 - If the key is the empty string (``''``), the widget is
527 assigned to the field upon which this method is called.
529 - If the key contains an asterisk as an element name, the
530 first child of the found element is traversed. This is most
531 useful for sequence fields, because the first (and only)
532 child of sequence fields is always the prototype field which
533 is used to render all fields in the sequence within a form
534 rendering.
536 If the ``separator`` argument is passed, it is should be a
537 string to be used as the dot character when splitting the
538 dotted names (useful for supplying if one of your field object
539 has a dot in its name, and you need to use a different
540 separator).
542 Examples follow. If the following form is used::
544 class Person(Schema):
545 first_name = SchemaNode(String())
546 last_name = SchemaNode(String())
548 class People(SequenceSchema):
549 person = Person()
551 class Conference(Schema):
552 people = People()
553 name = SchemaNode(String())
555 schema = Conference()
556 form = Form(schema)
558 The following invocations will have the following results
559 against the schema defined above:
561 ``form.set_widgets({'people.person.first_name':TextAreaWidget()})``
563 Set the ``first_name`` field's widget to a ``TextAreaWidget``.
565 ``form.set_widgets({'people.*.first_name':TextAreaWidget()})``
567 Set the ``first_name`` field's widget to a
568 ``TextAreaWidget``.
570 ``form.set_widgets({'people':MySequenceWidget()})``
572 Set the ``people`` sequence field's widget to a
573 ``MySequenceWidget``.
575 ``form.set_widgets({'people.*':MySequenceWidget()})``
577 Set the *person* field's widget to a ``MySequenceWidget``.
579 ``form.set_widgets({'':MyMappingWidget()})``
581 Set *form* node's widget to a ``MyMappingWidget``.
583 """
584 for k, v in values.items():
585 if not k:
586 self.widget = v
587 else:
588 path = k.split(separator)
589 field = self
590 while path:
591 element = path.pop(0)
592 if element == "*":
593 field = field.children[0]
594 else:
595 field = field[element]
596 field.widget = v
598 @property
599 def errormsg(self):
600 """Return the ``msg`` attribute of the ``error`` attached to
601 this field. If the ``error`` attribute is ``None``,
602 the return value will be ``None``."""
603 return getattr(self.error, "msg", None)
605 def serialize(self, cstruct=_marker, **kw):
606 """Serialize the cstruct into HTML and return the HTML string. This
607 function just turns around and calls ``self.widget.serialize(**kw)``;
608 therefore the field widget's ``serialize`` method should be expecting
609 any values sent in ``kw``. If ``cstruct`` is not passed, the cstruct
610 attached to this node will be injected into ``kw`` as ``cstruct``.
611 If ``field`` is not passed in ``kw``, this field will be injected
612 into ``kw`` as ``field``.
614 .. note::
616 Deform versions before 0.9.8 only accepted a ``readonly``
617 keyword argument to this function. Version 0.9.8 and later accept
618 arbitrary keyword arguments. It also required that
619 ``cstruct`` was passed; it's broken out from
620 ``kw`` in the method signature for backwards compatibility.
621 """
622 if cstruct is _marker:
623 cstruct = self.cstruct
624 values = {"field": self, "cstruct": cstruct}
625 values.update(kw)
626 return self.widget.serialize(**values)
628 def deserialize(self, pstruct):
629 """ Deserialize the pstruct into a cstruct and return the cstruct."""
630 return self.widget.deserialize(self, pstruct)
632 def render(self, appstruct=_marker, **kw):
633 """Render the field (or form) to HTML using ``appstruct`` as a set
634 of default values and returns the HTML string. ``appstruct`` is
635 typically a dictionary of application values matching the schema used
636 by this form, or ``colander.null`` to render all defaults. If it
637 is omitted, the rendering will use the ``appstruct`` passed to the
638 constructor.
640 Calling this method passing an appstruct is the same as calling::
642 cstruct = form.set_appstruct(appstruct)
643 form.serialize(cstruct, **kw)
645 Calling this method without passing an appstruct is the same as
646 calling::
648 cstruct = form.cstruct
649 form.serialize(cstruct, **kw)
651 See the documentation for
652 :meth:`colander.SchemaNode.serialize` and
653 :meth:`deform.widget.Widget.serialize` .
655 .. note::
657 Deform versions before 0.9.8 only accepted a ``readonly``
658 keyword argument to this function. Version 0.9.8 and later accept
659 arbitrary keyword arguments.
660 """
661 if appstruct is not _marker:
662 self.set_appstruct(appstruct)
663 cstruct = self.cstruct
664 kw.pop("cstruct", None) # disallowed
665 html = self.serialize(cstruct, **kw)
666 return html
668 def validate(self, controls, subcontrol=None):
669 """
670 Validate the set of controls returned by a form submission
671 against the schema associated with this field or form.
672 ``controls`` should be a *document-ordered* sequence of
673 two-tuples that represent the form submission data. Each
674 two-tuple should be in the form ``(key, value)``. ``node``
675 should be the schema node associated with this widget.
677 For example, using WebOb, you can compute a suitable value for
678 ``controls`` via::
680 request.POST.items()
682 Or, if you're using a ``cgi.FieldStorage`` object named
683 ``fs``, you can compute a suitable value for ``controls``
684 via::
686 controls = []
687 if fs.list:
688 for control in fs.list:
689 if control.filename:
690 controls.append((control.name, control))
691 else:
692 controls.append((control.name, control.value))
694 Equivalent ways of computing ``controls`` should be available to
695 any web framework.
697 When the ``validate`` method is called:
699 - if the fields are successfully validated, a data structure
700 represented by the deserialization of the data as per the
701 schema is returned. It will be a mapping.
703 - If the fields cannot be successfully validated, a
704 :exc:`deform.exception.ValidationFailure` exception is raised.
706 The typical usage of ``validate`` in the wild is often
707 something like this (at least in terms of code found within
708 the body of a :mod:`pyramid` view function, the particulars
709 will differ in your web framework)::
711 from webob.exc import HTTPFound
712 from deform.exception import ValidationFailure
713 from deform import Form
714 import colander
716 from my_application import do_something
718 class MySchema(colander.MappingSchema):
719 color = colander.SchemaNode(colander.String())
721 schema = MySchema()
723 def view(request):
724 form = Form(schema, buttons=('submit',))
725 if 'submit' in request.POST: # form submission needs validation
726 controls = request.POST.items()
727 try:
728 deserialized = form.validate(controls)
729 do_something(deserialized)
730 return HTTPFound(location='http://example.com/success')
731 except ValidationFailure as e:
732 return {'form':e.render()}
733 else:
734 return {'form':form.render()} # the form just needs rendering
736 .. warning::
738 ``form.validate(controls)`` mutates the ``form`` instance, so the
739 ``form`` instance should be constructed (and live) inside one
740 request.
742 If ``subcontrol`` is supplied, it represents a named subitem in the
743 data returned by ``peppercorn.parse(controls)``. Use this subitem as
744 the pstruct to validate instead of using the entire result of
745 ``peppercorn.parse(controls)`` as the pstruct to validate. For
746 example, if you've embedded a mapping in the form named ``user``, and
747 you want to validate only the data contained in that mapping instead
748 if all of the data in the form post, you might use
749 ``form.validate(controls, subcontrol='user')``.
750 """
751 try:
752 pstruct = peppercorn.parse(controls)
753 except ValueError as e:
754 exc = colander.Invalid(
755 self.schema, "Invalid peppercorn controls: %s" % e
756 )
757 self.widget.handle_error(self, exc)
758 cstruct = colander.null
759 raise exception.ValidationFailure(self, cstruct, exc)
760 if subcontrol is not None:
761 pstruct = pstruct.get(subcontrol, colander.null)
762 return self.validate_pstruct(pstruct)
764 def validate_pstruct(self, pstruct):
765 """
766 Validate the pstruct passed. Works exactly like the
767 :class:`deform.field.validate` method, except it accepts a pstruct
768 instead of a set of form controls. A usage example follows::
770 if 'submit' in request.POST: # the form submission needs validation
771 controls = request.POST.items()
772 pstruct = peppercorn.parse(controls)
773 substruct = pstruct['submapping']
774 try:
775 deserialized = form.validate_pstruct(substruct)
776 do_something(deserialized)
777 return HTTPFound(location='http://example.com/success')
778 except ValidationFailure, e:
779 return {'form':e.render()}
780 else:
781 return {'form':form.render()} # the form just needs rendering
782 """
784 exc = None
786 try:
787 cstruct = self.deserialize(pstruct)
788 except colander.Invalid as e:
789 # fill in errors raised by widgets
790 self.widget.handle_error(self, e)
791 cstruct = e.value
792 exc = e
794 self.cstruct = cstruct
796 try:
797 appstruct = self.schema.deserialize(cstruct)
798 except colander.Invalid as e:
799 # fill in errors raised by schema nodes
800 self.widget.handle_error(self, e)
801 exc = e
803 if exc:
804 raise exception.ValidationFailure(self, cstruct, exc)
806 return appstruct
808 def _get_cstruct(self):
809 return self._cstruct
811 def _set_cstruct(self, cstruct):
812 self._cstruct = cstruct
813 child_cstructs = self.schema.cstruct_children(cstruct)
814 if not isinstance(child_cstructs, colander.SequenceItems):
815 # If the schema's type returns SequenceItems, it means that the
816 # node is a sequence node, which means it has one child
817 # representing its prototype instead of a set of "real" children;
818 # our widget handle cloning the prototype node. The prototype's
819 # cstruct will already be set up with its default value by virtue
820 # of set_appstruct having been called in its constructor, and we
821 # needn't (and can't) do anything more.
822 for n, child in enumerate(self.children):
823 child.cstruct = child_cstructs[n]
825 def _del_cstruct(self):
826 if "_cstruct" in self.__dict__:
827 # rely on class-scope _cstruct (null)
828 del self._cstruct
830 cstruct = property(_get_cstruct, _set_cstruct, _del_cstruct)
832 def __repr__(self):
833 return "<%s.%s object at %d (schemanode %r)>" % (
834 self.__module__,
835 self.__class__.__name__,
836 id(self),
837 self.schema.name,
838 )
840 def set_appstruct(self, appstruct):
841 """Set the cstruct of this node (and its child nodes) using
842 ``appstruct`` as input."""
843 cstruct = self.schema.serialize(appstruct)
844 self.cstruct = cstruct
845 return cstruct
847 def set_pstruct(self, pstruct):
848 """Set the cstruct of this node (and its child nodes) using
849 ``pstruct`` as input."""
850 try:
851 cstruct = self.deserialize(pstruct)
852 except colander.Invalid as e:
853 # explicitly don't set errors
854 cstruct = e.value
855 self.cstruct = cstruct
857 def render_template(self, template, **kw):
858 """Render the template named ``template`` using ``kw`` as the
859 top-level keyword arguments (augmented with ``field`` and ``cstruct``
860 if necessary)"""
861 values = {"field": self, "cstruct": self.cstruct}
862 values.update(kw) # allow caller to override field and cstruct
863 return self.renderer(template, **values)
865 # retail API
867 def start_mapping(self, name=None):
868 """Create a start-mapping tag (a literal). If ``name`` is ``None``,
869 the name of this node will be used to generate the name in the tag.
870 See the :term:`Peppercorn` documentation for more information.
871 """
872 if name is None:
873 name = self.name
874 tag = '<input type="hidden" name="__start__" value="%s:mapping"/>'
875 return Markup(tag % (name,))
877 def end_mapping(self, name=None):
878 """Create an end-mapping tag (a literal). If ``name`` is ``None``,
879 the name of this node will be used to generate the name in the tag.
880 See the :term:`Peppercorn` documentation for more information.
881 """
882 if name is None:
883 name = self.name
884 tag = '<input type="hidden" name="__end__" value="%s:mapping"/>'
885 return Markup(tag % (name,))
887 def start_sequence(self, name=None):
888 """Create a start-sequence tag (a literal). If ``name`` is ``None``,
889 the name of this node will be used to generate the name in the tag.
890 See the :term:`Peppercorn` documentation for more information.
891 """
892 if name is None:
893 name = self.name
894 tag = '<input type="hidden" name="__start__" value="%s:sequence"/>'
895 return Markup(tag % (name,))
897 def end_sequence(self, name=None):
898 """Create an end-sequence tag (a literal). If ``name`` is ``None``,
899 the name of this node will be used to generate the name in the tag.
900 See the :term:`Peppercorn` documentation for more information.
901 """
903 if name is None:
904 name = self.name
905 tag = '<input type="hidden" name="__end__" value="%s:sequence"/>'
906 return Markup(tag % (name,))
908 def start_rename(self, name=None):
909 """Create a start-rename tag (a literal). If ``name`` is ``None``,
910 the name of this node will be used to generate the name in the tag.
911 See the :term:`Peppercorn` documentation for more information.
912 """
913 if name is None:
914 name = self.name
915 tag = '<input type="hidden" name="__start__" value="%s:rename"/>'
916 return Markup(tag % (name,))
918 def end_rename(self, name=None):
919 """Create a start-rename tag (a literal). If ``name`` is ``None``,
920 the name of this node will be used to generate the name in the tag.
921 See the :term:`Peppercorn` documentation for more information.
922 """
923 if name is None:
924 name = self.name
925 tag = '<input type="hidden" name="__end__" value="%s:rename"/>'
926 return Markup(tag % (name,))