{ "info": { "author": "Jim Fulton", "author_email": "jim@zope.com", "bugtrack_url": null, "classifiers": [], "description": "********************\nAjax Support\n********************\n\nThe zc.ajaxform package provides framework to support:\n\n- A single-class application model\n\n- Nested-application support\n\n- Integration with zope.formlib\n\n.. contents::\n\nDetailed Documentation\n**********************\n\nApplication support\n===================\n\nThe zc.ajaxform.application module provides support for writing ajax\n[#ajax]_ applications. This framework started out as an experiment in\nsimplifying writing applications with Zope 3. I was frustrated with\nZCML situps and generally too much indirection. I ended up with a\nmodel that I'm pretty happy with. It might not be for everybody. :)\n\nThe basic idea is that an application can be provided using a single\nZope 3 view plus necessary resource-library definitions. This view\nhas a URL. It typically provides many ajax methods whose URLs have the\nview URL as a base.\n\nMany applications can be implemented using a simple class that can be\nregistered as a view.\n\nLet's look at a simple stupid application. :)\n\n::\n\n import zc.ajaxform.application\n import zope.exceptions\n \n class Calculator(zc.ajaxform.application.Trusted,\n zc.ajaxform.application.Application):\n \n resource_library_name = None\n \n @zc.ajaxform.application.jsonpage\n def about(self):\n return 'Calculator 1.0'\n \n @zc.ajaxform.application.jsonpage\n def operations(self):\n return ['add', \"subtract\"]\n \n @zc.ajaxform.application.jsonpage\n def value(self):\n return dict(value=getattr(self.context, 'calculator_value', 0))\n \n def do_add(self, value):\n value += getattr(self.context, 'calculator_value', 0)\n self.context.calculator_value = value\n return dict(value=value)\n \n @zc.ajaxform.application.jsonpage\n def add(self, value):\n if not isinstance(value, int):\n return dict(error=\"The value must be an integer!\")\n return self.do_add(value)\n \n @zc.ajaxform.application.jsonpage\n def subtract(self, value):\n if not isinstance(value, int):\n raise zope.exceptions.UserError(\n \"The value must be an integer!\")\n return self.do_add(-value)\n \n @zc.ajaxform.application.jsonpage\n def noop(self):\n pass\n \n @zc.ajaxform.application.page\n def none(self):\n return \"null\"\n \n @zc.ajaxform.application.jsonpage\n def echo_form(self):\n def maybe_file(v):\n if hasattr(v, 'read'):\n return (\"\"\n % (v.filename, v.headers['content-type'], len(v.read()))\n )\n else:\n return v\n \n return dict(\n (name, maybe_file(v))\n for (name, v) in self.request.form.items()\n )\n \n \n @zc.ajaxform.application.jsonpage\n def doh(self):\n raise TypeError(\"Doh!\")\n \n\n\n\nWe subclass zc.ajaxform.application.Trusted. This is a minimal\nbase class that provides a constructor that takes a context and a\nrequest and removes the security proxy from the context. It\noverrides the constructor from zc.ajaxform.application.Application.\n\nWe also subclass zc.ajaxform.application.Application. This is a base\nclass that provides:\n\n- a basic constructor that takes context and request arguments and sets\n corresponding attributes,\n\n- traversal to attributes that provide IBrowserPublisher with\n conversion of dots to underscores,\n\n- a default \"page\" named index.html,\n\n- a template method that returns an HTML page with an empty head.\n\n- an index_html method that loads a resource library and calls the\n template,\n\n- an interface declaration that it provides IBrowserPublisher, and\n\n- an adapter declaration that adapts\n zope.traversing.interfaces.IContainmentRoot and\n zope.publisher.interfaces.browser.IBrowserRequest.\n\nThe main goals of this base class are to make it easy to load\nJavascript and to make it easy to define ajax methods to support the\nJavascript. For that reason, we provide a traverser that traverses to\nobject attributes that provide IBrowserPublisher. The\nzc.ajaxform.application.jsonpage decorator is also an important part of\nthis. It makes methods accessible and automatically marshals their\nresult to JSON [#jsoninput]_. There's also a\nzc.ajaxform.application.page decorator that makes methods accessible\nwithout the automatic marshalling. The use of a default page, rather\nthan just a __call__ method is to cause the URL base to be the view,\nrather than the view's context. This allows the Javascript code to\nuse relative URLs to refer to the ajax methods.\n\nThe class expects subclasses to define a resource_library_name\nattribute [#missing_resource_library_name]_. For these applications,\nyou pretty much always want to use an associated Javascript file and\nother resources (supporting JS, CSS, etc.). You can suppress the use\nof the resource library by setting the value of this attribute to\nNone.\n\nFor applications that build pages totally in Javascript, the default\ntemplate is adequate. For applications that need to support\nnon-Javascript-enabled browsers, that want to support search-engine\noptimization [#sso]_, or that want to provide some Javascript data\nduring the initial page load, a custom template can be provided by\nsimply overriding the template method with a page template or a method\nthat calls one.\n\nThe view can be registered with a simple adapter registration:\n\n::\n\n \n \n \n\n\n\nIf we wanted to register it for an object other than the an\nIContainmentRoot, we could just provide specifically adapted interfaces\nor classes in the registration.\n\nLet's access the calculator with a test browser\n\n >>> import zope.testbrowser.testing\n >>> browser = zope.testbrowser.testing.Browser()\n >>> browser.open('http://localhost/')\n Traceback (most recent call last):\n ...\n HTTPError: HTTP Error 401: Unauthorized\n\nBecause our view was registered to require zope.View, the request was\nunauthorized. Let's login. In the demo setup, we login by just\nproviding a login form variable. \n\n >>> browser.open('http://localhost/calculator.html?login')\n >>> print browser.contents # doctest: +NORMALIZE_WHITESPACE\n \n \n \n\nWe registered our view as calculator.html. Because of the way it sets the\nbrowser default page for itself, it becomes the base href for the\npage. This allows us to access ajax methods using relative URLs.\n\nOur calculator view provides a value method. It uses the\nzc.ajaxform.application.jsonpage decorator. This does 2 things:\n\n- Arranges that the method can be traversed to,\n\n- marshals the result to JSON.\n\nThe way results are marshalled to JSON deserves some\nexplanation. To support automation of ajax calls, we:\n\n- Always return objects\n- If there is an error, we include:\n - an error property providing an error messahe, and/or\n - when handling form submissions, an errors property with am object value\n mapping field names to field-specific error messages.\n\n::\n\n >>> import simplejson\n\n >>> browser.open('http://localhost/@@calculator.html/value')\n >>> simplejson.loads(browser.contents)\n {u'value': 0}\n\n >>> browser.open('http://localhost/@@calculator.html/add?value=hi')\n >>> simplejson.loads(browser.contents)\n {u'error': u'The value must be an integer!'}\n\nThings other than a dictionary can be returned:\n\n >>> browser.open('http://localhost/@@calculator.html/about')\n >>> simplejson.loads(browser.contents)\n u'Calculator 1.0'\n\n >>> browser.open('http://localhost/@@calculator.html/operations')\n >>> simplejson.loads(browser.contents)\n [u'add', u'subtract']\n\nIf you want to marshal JSON yourself, you can use the\nzc.ajaxform.application.jsonpage decorator:\n\n >>> browser.open('http://localhost/@@calculator.html/none')\n\nAn alternative way to return errors is to raise user errors, as is\ndone by the subtract method in our example:\n\n >>> browser.open('http://localhost/@@calculator.html/subtract?value=hi')\n >>> simplejson.loads(browser.contents)\n {u'error': u'The value must be an integer!'}\n\nThis works because there is a view registered for\nzope.exceptions.interfaces.IUserError, and\nzc.ajaxform.interfaces.IAjaxRequest.\n\nTesting support\n===============\n\nzc.ajaxform.testing has some helper functions to make it easier to test\najax calls.\n\nThe zc.ajaxform.testing.FormServer class provides some convenience for\nmaking ajax calls in which data are sent as form data and returned as\nJSON. The class takes a browser and returns an object that can be\ncalled to make server calls:\n\n >>> import zc.ajaxform.testing, pprint\n >>> server = zc.ajaxform.testing.FormServer(browser)\n\n >>> pprint.pprint(server('/calculator.html/echo_form',\n ... {'a': 1.0}, b=[1, 2, 3], c=True, d='d', e=u'e\\u1234'\n ... ), width=1)\n {u'a': u'1.0',\n u'b': [u'1',\n u'2',\n u'3'],\n u'c': u'1',\n u'd': u'd',\n u'e': u'e\\u1234'}\n\nWhen we call the server, we pass a URL to invoke, which may be\nrelative, a optional dictionary of parameter values, and optional\nkeyword arguments. \n\nNote that the application will recieve data as strings, which is what\nwe see echoed back in the example above.\n\nIf the application is written using Zope, then we can enable Zope form\nmarshalling, by passing a True value when we create the server:\n\n >>> server = zc.ajaxform.testing.FormServer(browser, True)\n >>> pprint.pprint(server('/calculator.html/echo_form',\n ... {'a': 1.0}, b=[1, 2, 3], c=True, d='d', e=u'e\\u1234'\n ... ), width=1)\n {u'a': 1.0,\n u'b': [1,\n 2,\n 3],\n u'c': True,\n u'd': u'd',\n u'e': u'e\\u1234'}\n\n >>> pprint.pprint(server('/calculator.html/add', {'value': 1}), width=1)\n {u'value': 1}\n\n >>> pprint.pprint(server('/calculator.html/add', value=1), width=1)\n {u'value': 2}\n\nThe methods called are assumed to return JSON and the resulting data\nis converted back into Python.\n\nThe function pprint method combines pprint and calling:\n\n >>> server.pprint('/calculator.html/add', {'value': 1})\n {u'value': 3}\n\n >>> server.pprint('/calculator.html/add', value=1)\n {u'value': 4}\n\n >>> server.pprint('/calculator.html/echo_form',\n ... {'a': 1.0}, b=[1, 2, 3], c=True, d='d', e=u'e\\u1234'\n ... )\n {u'a': 1.0,\n u'b': [1,\n 2,\n 3],\n u'c': True,\n u'd': u'd',\n u'e': u'e\\u1234'}\n\nIn the future, there will be versions of these functions that send\ndata as JSON.\n\nWe can include file-upload data by including a 3-tuple with a file\nname, a content type, and a data string:\n\n >>> server.pprint('/calculator.html/echo_form',\n ... b=[1, 2, 3], c=True, d='d',\n ... file=('foo.xml', 'test/xml', ''), \n ... )\n {u'b': [1,\n 2,\n 3],\n u'c': True,\n u'd': u'd',\n u'file': u\"\"}\n\nas a convenience, you can pass a URL string to the server constructor,\nwhich will create a browser for you that has opened that URL. You can\nalso omit the brower and an unopened browser will be created.\n\n\n >>> server = zc.ajaxform.testing.FormServer(\n ... 'http://localhost/calculator.html?login')\n >>> server.browser.url\n 'http://localhost/calculator.html?login'\n\n >>> server.pprint('/calculator.html/echo_form', x=1)\n {u'x': u'1'}\n\n >>> server = zc.ajaxform.testing.FormServer(zope_form_marshalling=True)\n >>> server.browser.open('http://localhost/calculator.html?login')\n >>> server.pprint('/calculator.html/echo_form', x=1)\n {u'x': 1}\n\nIn the example above, we didn't provide a browser, but we provided the\nzope_form_marshalling flag as a keyword option.\n\n\n.. Edge case: we can't traverse to undecorated methods:\n\n >>> server.pprint('/calculator.html/do_add', value=1)\n Traceback (most recent call last):\n ...\n HTTPError: HTTP Error 404: Not Found\n\n\n\"Library\" applications\n======================\n\nThe \"application\" model described above is pretty appealing in its\nsimplicity -- at least to me. :) Usually, we'd like to make out\napplications a bit more flexible in their use. In particular, we\noften want to assemble applications together. At the Javascript level,\nthis often means having an application return a panel that can be used\nin some higher-level layout. At the server level, we need to provide\na way to access application logic within some larger context. There\nare two parts to this:\n\n1. The containing application needs to support traversal to the\n sub-application. \n\n2. The subapplication needs to know how it was traversed to, at least\n if it generates URLs. For example, the form machinery [#forms]_\n generates URLs for action handlers.\n\nSub-application should expose the URL needed to access then as a\nbase_href attribute. This is usually a relative URL relative to the base\napplication. \n\nThere are a number of classes defined in zc.ajaxform.application that\nhelp with creating sub-applications:\n\nSubApplication\n This class, which Application subclasses, provides traversal to\n attributes that provide IBrowserPublisher. It also stamps\n IAjaxRequest on the request object when an object is traversed\n [#iajaxrequest]_ .\n\n (Maybe this request stamping should be done further down the\n traversal chain or perhaps only done if X-Requested-With is\n xmlhttprequest.)\n\nPublicTraversable\n This class provides security declarations that allow objects to be\n traversable publically. This is appropriate for sub-applications\n that want the same protections as the object being traversed.\n\nLet's look at our calculator example as a subapplication:\n\n::\n\n import zc.ajaxform.application\n import zope.exceptions\n \n class Container(zc.ajaxform.application.Application):\n \n resource_library_name = None\n \n @property\n def calc(self):\n return Calculator(self.context, self.request, base_href='calc')\n \n \n class Calculator(zc.ajaxform.application.Trusted,\n zc.ajaxform.application.SubApplication,\n zc.ajaxform.application.PublicTraversable,\n ):\n \n @zc.ajaxform.application.jsonpage\n def operations(self):\n return [['add', self.base_href+'/add'],\n ['add', self.base_href+'/subtract'],\n ]\n \n @zc.ajaxform.application.jsonpage\n def value(self):\n return dict(value=getattr(self.context, 'calculator_value', 0))\n \n def do_add(self, value):\n value += getattr(self.context, 'calculator_value', 0)\n self.context.calculator_value = value\n return dict(value=value)\n \n @zc.ajaxform.application.jsonpage\n def add(self, value):\n if not isinstance(value, int):\n return dict(error=\"The value must be an integer!\")\n return self.do_add(value)\n \n @zc.ajaxform.application.jsonpage\n def subtract(self, value):\n if not isinstance(value, int):\n raise zope.exceptions.UserError(\n \"The value must be an integer!\")\n return self.do_add(-value)\n \n\n\n\nHere, we've defined a container application that simply provides\ntraversal to a calculator subapplication as a static property. It\ncreates the calculator with the application's context and request. It\npasses a base_href as a keyword argument, which SubApplication's\nconstructor accepts. Our ZCML configuration is pretty simple:\n\n::\n\n \n \n \n \n\n\n\nUsing the container application, we access the calculator via the\ncontainer:\n\n >>> server.pprint('http://localhost/@@container.html/calc/add', value=1)\n {u'value': 5}\n\nWe've updated the operations method to include the URL for each\noperation, which is computed based on the base_href:\n\n >>> server.pprint('http://localhost/@@container.html/calc/operations')\n [[u'add',\n u'calc/add'],\n [u'add',\n u'calc/subtract']]\n\n\nNote that we didn't make any security declarations for the Calculator\nclass. We're relying on the protection for the container. If we\nrestart the browser, we see, indeed, that we can't access the\ncalculator:\n\n >>> server = zc.ajaxform.testing.FormServer()\n >>> server.pprint('http://localhost/@@container.html/calc/operations')\n {u'session_expired': True}\n\nDynamic Traversal\n=================\n\nIn the previous example, we traversed to a sub-application using a\nstatic property. Sometimes, we need to traverse dynamically. We\nmight have a container application with a variable number of\nsubapplications. Examples include a portlet container and a system for\nmanaging user-defined data types. In the later case, as users define\nnew data types, one or more applications get defined for each type.\n\nzc.ajaxform.application provides a helper descriptor that allows custom\ntraversers to be implemented with simple Python methods. Let's look\nat a simple example.\n\n >>> import zc.ajaxform.application \n >>> class Foo:\n ... def __str__(self):\n ... return 'a '+self.__class__.__name__\n ...\n ... @zc.ajaxform.application.traverser\n ... def demo_traverse(self, request, name):\n ... return \"traverse: %s %s %s\" % (self, request, name)\n\nThis is a rather silly traverser for demonstration purposes that just\nreturnes a transformed name.\n\n >>> foo = Foo()\n >>> foo.demo_traverse.publishTraverse(\"a request\", \"xxx\")\n 'traverse: a Foo a request xxx'\n\nWe can still call the method:\n\n >>> foo.demo_traverse(\"a request\", \"xxx\")\n 'traverse: a Foo a request xxx'\n\nThe method provides IBrowserPublisher:\n\n >>> import zope.publisher.interfaces.browser\n >>> zope.publisher.interfaces.browser.IBrowserPublisher.providedBy(\n ... foo.demo_traverse)\n True\n\nThe descriptor has a security declaration that allows it to be\ntraversed but not called from untrusted code:\n\n >>> import zope.security.checker\n >>> checker = zope.security.checker.getChecker(\n ... zope.security.checker.ProxyFactory(foo.demo_traverse))\n >>> checker.get_permissions\n {'publishTraverse': Global(CheckerPublic,zope.security.checker)}\n\n >>> checker.set_permissions\n {}\n\nAcquisition\n===========\n\nApplications and sub-applications have __parent__ properties that\nreturn their context. This is to support frameworks that ise\n__parent__ to perform acquisition.\n\n >>> class MyApp(zc.ajaxform.application.Application):\n ... pass\n\n >>> myapp = MyApp(foo, None)\n >>> myapp.__parent__ is foo\n True\n\n >>> class MySubApp(zc.ajaxform.application.SubApplication):\n ... pass\n\n >>> mysubapp = MySubApp(foo, None)\n >>> mysubapp.__parent__ is foo\n True\n\n\nSystem Errors\n=============\n\n System errors will be rendered as json.\n\n >>> server = zc.ajaxform.testing.FormServer(\n ... 'http://localhost/calculator.html?login')\n >>> server('/calculator.html/doh')\n Traceback (most recent call last):\n ...\n HTTPError: HTTP Error 500: Internal Server Error\n\n >>> pprint.pprint(simplejson.loads(server.browser.contents), width=1)\n {u'error': u'TypeError: Doh!'}\n\n.. [#ajax] Technically, these aren't really AJAX applications, since\n we rarely. if ever, use XML as a serialization format. To\n emphasize this I'll use lower-case \"ajax\" to refer to the generic\n approach of making low-level calls from Javascript rather than\n doing page loads.\n\n.. [#jsoninput] In the near future, there will also be support for\n JSON method input. This will provide a number of benefits:\n\n - It will provide more automatic marshaling of non-string\n data. Now, we either have to de-marshal in the server application\n code or embed marshaling data into parameter names in client\n code.\n\n - It will allow richer data structures than is practical with form data.\n\n - It will probably allow faster ajax requests because:\n\n - Server-side de-marshalling is done with highly optimized code\n in simplejson.\n\n - We will assume that data passed are valid method arguments and\n avoid method introspection.\n\n.. [#missing_resource_library_name] A custom attribute error message\n is used if this attribute is missing that tries to be more\n informative than the default attribute error.\n\n.. [#sso] For search-engine optimization, one generally wants a\n content page to actually contain its content. If one depends on\n Javascript-enabled browsers, one can improve performance and\n search-engine optimization by adding ancilary data in Javascript,\n so as not to dilute the content.\n\n.. [#forms] See form.txt.\n\n.. [#iajaxrequest] Traversing into a subapplication adds IAjaxRequest to the\n list of interfaces provided by the request.\n\n >>> import zc.ajaxform.application\n >>> import zc.ajaxform.interfaces\n >>> import zope.publisher.browser\n >>> request = zope.publisher.browser.TestRequest()\n >>> class SubApp(zc.ajaxform.application.SubApplication):\n ... @zc.ajaxform.application.jsonpage\n ... def foo(self):\n ... return 'bar'\n >>> subapp = SubApp(object(), request)\n\n Now let's try traversing into the subapplication:\n\n >>> zc.ajaxform.interfaces.IAjaxRequest.providedBy(request)\n False\n >>> subapp.publishTraverse(request, 'foo')()\n '\"bar\"'\n >>> zc.ajaxform.interfaces.IAjaxRequest.providedBy(request)\n True\n\n Note that the request keeps any provided interfaces:\n\n >>> request = zope.publisher.browser.TestRequest()\n >>> class IMyInterface(zope.interface.Interface):\n ... pass\n >>> zope.interface.directlyProvides(request, IMyInterface)\n >>> subapp.publishTraverse(request, 'foo')()\n '\"bar\"'\n >>> IMyInterface.providedBy(request)\n True\n\n\nSystem Error Logging\n====================\n\nWhen an error is generated, zope.app.publishing checks to see if the\nerror handler provides the System Error interface. If so, zope.app.publishing\nlogs the error.\n\nRather than use this indirect method of logging, there is an explicit\nstatement in ExceptionView that imports the logging module and logs and error\nitself. We can test this with the zope.testing.loggingsupport functions.\n\n First we set up loggingsupport. This keeps track of all error messages\n written via the module passed as the parameter. In this case, zc.ajaxform.\n\n >>> import logging\n >>> import zope.testing.loggingsupport\n >>> log_handler = zope.testing.loggingsupport.InstalledHandler('zc.ajaxform')\n\n Then we create an error.\n\n >>> server = zc.ajaxform.testing.FormServer(\n ... 'http://localhost/calculator.html?login')\n >>> server('/calculator.html/doh')\n Traceback (most recent call last):\n ...\n HTTPError: HTTP Error 500: Internal Server Error\n\n ...And check to see that it was logged.\n\n >>> print log_handler\n zc.ajaxform.application ERROR\n SysError created by zc.ajaxform\n\nForm Processing\n===============\n\nzc.ajaxform.form provides support for server-generated forms based on\nthe zope.formlib library.\n\nForms are meant to be used as parts of larger applications. A form\nprovides output of JSON data for building javascript forms. Forms also\nprovide validation and call actions with validated data to perform\nactions on form submit.\n\nTo create a form, just create a form class as a subclass of\nzc.ajaxform.form.Form. This base class provides:\n\n- an ajax __call__ method that returns a form definition,\n\n- traversal to form actions, in much the same way that\n zc.ajaxform.application.Application [#application]_ provides traversal\n to json methods,\n\n- a definitions method that can be used by ajax methods to get a form\n definition as Python data, and\n\n- a getObjectData method for getting initial form data from an\n existing object.\n\nHere's a simple example:\n\n::\n\n import zc.ajaxform.application\n import zc.ajaxform.interfaces\n import zc.ajaxform.widgets\n import zc.ajaxform.form\n import zope.component\n import zope.interface\n import zope.formlib\n import zope.schema\n \n class IAddress(zope.interface.Interface):\n \n street = zope.schema.TextLine(\n title = u\"Street\",\n description = u\"The street\",\n )\n \n city = zope.schema.TextLine(\n title = u\"City\",\n description = u\"The city\",\n )\n \n awesomeness = zope.schema.Int(\n title = u\"Awesomeness\",\n description = u\"The awesomeness on a scale of 1 to 10\",\n min = 1,\n max = 10,\n )\n \n \n class Pets(zc.sourcefactory.basic.BasicSourceFactory):\n \n def getValues(self):\n return (u'Dog', u'Cat', u'Fish')\n \n \n class Pet(zope.schema.TextLine):\n \"\"\"A textline representing a pet.\n \n This is just a textline, but we also have a source of common pets that\n the user can choose from.\n \"\"\"\n \n class IPerson(zope.interface.Interface):\n \n first_name = zope.schema.TextLine(\n title = u\"First name\",\n description = u\"Given name.\",\n default= u'Happy'\n )\n \n last_name = zope.schema.TextLine(\n title = u\"Last name\",\n description = u\"Family name.\",\n default= u'Camper'\n )\n \n favorite_color = zope.schema.TextLine(\n title = u\"Favorite color\",\n required = False,\n default= u'Blue'\n )\n \n age = zope.schema.Int(\n title = u\"Age\",\n description = u\"Age in years\",\n min = 0,\n max = 200,\n default= 23\n )\n \n happy = zope.schema.Bool(\n title = u\"Happy\",\n description = u\"Are they happy?\",\n default= True\n )\n \n pet = Pet(\n title=u'Pet',\n description=u'This person\\'s best friend.',\n required=False,\n )\n \n temperment = zope.schema.Choice(\n title = u\"Temperment\",\n description = u\"What is the person like?\",\n values = ['Nice', 'Mean', 'Ornery', 'Right Neighborly'],\n default = u'Right Neighborly'\n )\n \n weight = zope.schema.Decimal(\n title = u\"Weight\",\n description = u\"Weight in lbs?\"\n )\n \n description = zope.schema.Text(\n title = u\"Description\",\n description = u\"What do they look like?\",\n default = u'10ft tall\\nRazor sharp scales.'\n )\n \n secret = zope.schema.TextLine(\n title = u\"Secret Key\",\n description = u\"Don't tell anybody\",\n default = u'5ecret sauce'\n )\n \n siblings = zope.schema.Int(\n title = u\"Siblings\",\n description = u\"Number of siblings\",\n min = 0,\n max = 8,\n default = 1\n )\n \n addresses = zope.schema.List(\n title = u'Addresses',\n description = u\"All my wonderful homes\",\n value_type = zope.schema.Object(schema=IAddress),\n default= [{'street':'123 fake street',\n 'city': 'fakeville',\n 'awesomeness': '9'},\n {'street':'345 false street',\n 'city': 'falsetown',\n 'awesomeness': '9001'}\n ]\n )\n \n other = zope.schema.Text(\n title = u\"Other\",\n description = u\"Any other notes\",\n default = u\"I've got a magic toenail\"\n )\n \n class Person:\n \n zope.interface.implements(IPerson)\n \n def __init__(self, first_name, last_name, favorite_color, age, happy,\n pet, temperment, weight, description, secret, siblings,\n addresses, other):\n self.first_name = first_name\n self.last_name = last_name\n self.favorite_color = favorite_color\n self.age = age\n self.happy = happy\n self.pet = pet\n self.temperment = temperment\n self.weight = weight\n self.description = description\n self.secret = secret\n self.siblings = siblings\n self.addresses = addresses\n self.other = other\n \n \n class FormExample(zc.ajaxform.application.Application):\n \n resource_library_name = None\n \n class ExampleForm(zc.ajaxform.form.Form):\n \n leftFields = ('first_name', 'last_name', 'age', 'other')\n form_fields = zope.formlib.form.Fields(IPerson)\n form_fields['secret'].custom_widget = zc.ajaxform.widgets.Hidden\n form_fields['siblings'].custom_widget = zc.ajaxform.widgets.NumberSpinner\n \n @zope.formlib.form.action(\"Register\")\n def register(self, action, data):\n person = Person(**data)\n return dict(\n data = data,\n self_class_name = self.__class__.__name__,\n self_app_class_name = self.app.__class__.__name__,\n self_context_class_name = self.context.__class__.__name__\n )\n \n \n class PetWidget(zc.ajaxform.widgets.ComboBox):\n zope.component.adapts(\n Pet,\n zc.ajaxform.interfaces.IAjaxRequest)\n zope.interface.implements(\n zc.ajaxform.interfaces.IInputWidget)\n \n def __init__(self, context, request):\n super(PetWidget, self).__init__(context, Pets(), request)\n\n\n\nNote that we've nested our form definition in an application. We can\ndefine the form class elsewhere and use it, but if a form is only used\nin an application, then it's often easiest to define it within an\napplication class. Forms are instantiated by calling them with a\nsingle argument. This argument, the application, becomes the form's `app`\nattribute. The application's context becomes the form's context. Form\nclasses are automatically instantiated when a form class is assigned to\nan attribute in a class and accessed through an instance\n[#form_classes_are_descriptors]_.\n\nLet's try accessing our form, which can be found in its python form\nin form_example.py:\n\n >>> import zope.testbrowser.testing\n >>> from zc.ajaxform.testing import call_form, print_form\n >>> browser = zope.testbrowser.testing.Browser()\n >>> browser.open('http://localhost/form.html?login')\n >>> print_form(browser, 'http://localhost/form.html/ExampleForm')\n {u'definition': {u'actions': [{u'label': u'Register',\n u'name': u'ExampleForm.actions.register',\n u'url': u'ExampleForm/register'}],\n u'left_fields': {u'addresses': False,\n u'age': True,\n u'description': False,\n u'favorite_color': False,\n u'first_name': True,\n u'happy': False,\n u'last_name': True,\n u'other': True,\n u'pet': False,\n u'secret': False,\n u'siblings': False,\n u'temperment': False,\n u'weight': False},\n u'prefix': u'ExampleForm',\n u'widgets': [{u'fieldHint': u'Given name.',\n u'fieldLabel': u'First name',\n u'id': u'first_name',\n u'minLength': 0,\n u'name': u'first_name',\n u'required': True,\n u'value': u'Happy',\n u'widget_constructor': u'zope.schema.TextLine'},\n {u'fieldHint': u'Family name.',\n u'fieldLabel': u'Last name',\n u'id': u'last_name',\n u'minLength': 0,\n u'name': u'last_name',\n u'required': True,\n u'value': u'Camper',\n u'widget_constructor': u'zope.schema.TextLine'},\n {u'fieldHint': u'',\n u'fieldLabel': u'Favorite color',\n u'id': u'favorite_color',\n u'minLength': 0,\n u'name': u'favorite_color',\n u'required': False,\n u'value': u'Blue',\n u'widget_constructor': u'zope.schema.TextLine'},\n {u'allowBlank': False,\n u'fieldHint': u'Age in years',\n u'fieldLabel': u'Age',\n u'field_max': 200,\n u'field_min': 0,\n u'id': u'age',\n u'name': u'age',\n u'required': True,\n u'value': u'23',\n u'widget_constructor': u'zope.schema.Int'},\n {u'fieldHint': u'Are they happy?',\n u'fieldLabel': u'Happy',\n u'id': u'happy',\n u'name': u'happy',\n u'required': True,\n u'value': True,\n u'widget_constructor': u'zope.schema.Bool'},\n {u'fieldHint': u\"This person's best friend.\",\n u'fieldLabel': u'Pet',\n u'id': u'pet',\n u'name': u'pet',\n u'required': False,\n u'values': [[u'c935d187f0b998ef720390f85014ed1e',\n u'Dog'],\n [u'fa3ebd6742c360b2d9652b7f78d9bd7d',\n u'Cat'],\n [u'071642fa72ba780ee90ed36350d82745',\n u'Fish']],\n u'widget_constructor': u'zc.ajaxform.widgets.ComboBox'},\n {u'allowBlank': False,\n u'fieldHint': u'What is the person like?',\n u'fieldLabel': u'Temperment',\n u'hiddenName': u'temperment.value',\n u'id': u'temperment',\n u'name': u'temperment',\n u'required': True,\n u'value': u'Right Neighborly',\n u'values': [[u'Nice',\n u'Nice'],\n [u'Mean',\n u'Mean'],\n [u'Ornery',\n u'Ornery'],\n [u'Right Neighborly',\n u'Right Neighborly']],\n u'widget_constructor': u'zope.schema.Choice'},\n {u'allowBlank': False,\n u'fieldHint': u'Weight in lbs?',\n u'fieldLabel': u'Weight',\n u'id': u'weight',\n u'name': u'weight',\n u'required': True,\n u'widget_constructor': u'zope.schema.Decimal'},\n {u'fieldHint': u'What do they look like?',\n u'fieldLabel': u'Description',\n u'id': u'description',\n u'minLength': 0,\n u'name': u'description',\n u'required': True,\n u'value': u'10ft tall\\nRazor sharp scales.',\n u'widget_constructor': u'zope.schema.Text'},\n {u'fieldHint': u\"Don't tell anybody\",\n u'fieldLabel': u'Secret Key',\n u'id': u'secret',\n u'name': u'secret',\n u'required': True,\n u'value': u'5ecret sauce',\n u'widget_constructor': u'zc.ajaxform.widgets.Hidden'},\n {u'allowBlank': False,\n u'fieldHint': u'Number of siblings',\n u'fieldLabel': u'Siblings',\n u'field_max': 8,\n u'field_min': 0,\n u'id': u'siblings',\n u'name': u'siblings',\n u'required': True,\n u'value': u'1',\n u'widget_constructor': u'zc.ajaxform.widgets.NumberSpinner'},\n {u'fieldHint': u'All my wonderful homes',\n u'fieldLabel': u'Addresses',\n u'id': u'addresses',\n u'name': u'addresses',\n u'record_schema': {u'readonly': False,\n u'widgets': [{u'fieldHint': u'The street',\n u'fieldLabel': u'Street',\n u'id': u'street',\n u'minLength': 0,\n u'name': u'street',\n u'required': True,\n u'widget_constructor': u'zope.schema.TextLine'},\n {u'fieldHint': u'The city',\n u'fieldLabel': u'City',\n u'id': u'city',\n u'minLength': 0,\n u'name': u'city',\n u'required': True,\n u'widget_constructor': u'zope.schema.TextLine'},\n {u'allowBlank': False,\n u'fieldHint': u'The awesomeness on a scale of 1 to 10',\n u'fieldLabel': u'Awesomeness',\n u'field_max': 10,\n u'field_min': 1,\n u'id': u'awesomeness',\n u'name': u'awesomeness',\n u'required': True,\n u'widget_constructor': u'zope.schema.Int'}]},\n u'required': True,\n u'value': [{u'awesomeness': u'9',\n u'city': u'fakeville',\n u'street': u'123 fake street'},\n {u'awesomeness': u'9001',\n u'city': u'falsetown',\n u'street': u'345 false street'}],\n u'widget_constructor': u'zope.schema.List'},\n {u'fieldHint': u'Any other notes',\n u'fieldLabel': u'Other',\n u'id': u'other',\n u'minLength': 0,\n u'name': u'other',\n u'required': True,\n u'value': u\"I've got a magic toenail\",\n u'widget_constructor': u'zope.schema.Text'}]}}\n\nOur application is at: \"http://localhost/form.html\". The form is\nexposed as an ajax method named \"ExampleForm\", which comes from the attribute\nname in the class definition.\n\nThe form definition contains both action definitions and widget\ndefinitions. The widget definitions may be full js field definitions\nor name a widget_constructor, which is a Javascript helper provided by\nthe zc.ajaxform resource library that provides additional information,\nlike Javascript validators, that can't be expressed in JSON.\n\nThere is an action definition for each action defined in the form. The\naction information includes the url to post the result to, relative to\nthe application.\n\nNote that the name of the form class is used as the form prefix and\nthat the form prefix is used as the prefix for widget and action names\nand ids [#actionids]_.\n\nLet's post a result back:\n\n >>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',\n ... {'first_name': 'Bob',\n ... 'last_name': '',\n ... 'favorite_color': '',\n ... 'age': '-1',\n ... })\n {u'errors': {u'addresses': u'Addresses: Missing Input',\n u'age': u'Value is too small',\n u'description': u'Description: Missing Input',\n u'last_name': u'Last name: Missing Input',\n u'other': u'Other: Missing Input',\n u'secret': u'Secret Key: Missing Input',\n u'siblings': u'Siblings: Missing Input',\n u'temperment': u'Temperment: Missing Input',\n u'weight': u'Weight: Missing Input'}}\n\nThe result had 9 problems:\n\n- We didn't provide a last name, description, secret key,\n number of siblings, temperment, or weight, which are all required\n\n- In the form we did not specify deleting either of our two current\n address records, but also ommitted their data, and the first of them\n reported a missing field. Following this we will delete them both and\n add a new record.\n\n- We specified an invalid age.\n\nLet's pass valid data:\n\n >>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',\n ... {'first_name': 'Bob',\n ... 'last_name': 'Zope',\n ... 'favorite_color': '',\n ... 'age': '11',\n ... 'addresses.street.0': '123 Fake Ln.',\n ... 'addresses.city.0': 'Fakeville',\n ... 'addresses.awesomeness.0': 7,\n ... 'description': 'Hello',\n ... 'other': 'So nice to meet you',\n ... 'secret': 'Oh nothing',\n ... 'siblings': 1,\n ... 'temperment': 'Nice',\n ... 'weight': '170.5',\n ... 'pet': 'Carrier Pigeon'\n ... })\n {u'data': {u'addresses': [{u'awesomeness': 7,\n u'city': u'Fakeville',\n u'street': u'123 Fake Ln.'}],\n u'age': 11,\n u'description': u'Hello',\n u'favorite_color': u'',\n u'first_name': u'Bob',\n u'happy': False,\n u'last_name': u'Zope',\n u'other': u'So nice to meet you',\n u'pet': u'Carrier Pigeon',\n u'secret': u'Oh nothing',\n u'siblings': 1,\n u'temperment': u'Nice',\n u'weight': u'170.5'},\n u'self_app_class_name': u'FormExample',\n u'self_class_name': u'ExampleForm',\n u'self_context_class_name': u'Folder'}\n\nHere we get a successful result. Our contrived action in the example\nsimply echoed back the data it was passed, Note, in particular that:\n\n- the data keys have the form prefix removed, and\n\n- the value of the age key is an integer, since the field was an\n integer field.\n\nNote that for the list field, the original request added a prefix of the list\nfield name to prevent collisions with a field with the same name in another\nlist field (if it existed).\n\nThe action also prints out the classes of its self argument, its app\nand its context. Actions are methods of forms so their `self` argument is the\nform. The form's `app` is the app through which it is accessed and\n`context` is the app's context.\n\nFor list widgets if a field in its record is missing and it is required, the\nerror reported lists the field name:\n\n >>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',\n ... {'first_name': 'Bob',\n ... 'last_name': 'Zope',\n ... 'favorite_color': '',\n ... 'age': '11',\n ... 'addresses.street.0': '123 Fake Ln.',\n ... 'addresses.city.0': 'Fakeville',\n ... 'addresses.awesomeness.0': 7,\n ... 'addresses.street.1': 'The 2nd Missing field St.',\n ... 'addresses.awesomeness.1': 3,\n ... 'description': 'Hello',\n ... 'other': 'So nice to meet you',\n ... 'secret': 'Oh nothing',\n ... 'siblings': 1,\n ... 'temperment': 'Nice',\n ... 'weight': '170.5',\n ... 'pet': 'Carrier Pigeon'\n ... })\n {u'errors': {u'addresses': u'City: Missing Input'}}\n\nLet's provide this value now:\n\n >>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',\n ... {'first_name': 'Bob',\n ... 'last_name': 'Zope',\n ... 'favorite_color': '',\n ... 'age': '11',\n ... 'addresses.street.0': '123 Fake Ln.',\n ... 'addresses.city.0': 'Fakeville',\n ... 'addresses.awesomeness.0': 7,\n ... 'addresses.street.1': 'The 2nd Missing field St.',\n ... 'addresses.city.1': 'A Void',\n ... 'addresses.awesomeness.1': '3',\n ... 'description': 'Hello',\n ... 'other': 'So nice to meet you',\n ... 'secret': 'Oh nothing',\n ... 'siblings': 1,\n ... 'temperment': 'Nice',\n ... 'weight': '170.5',\n ... 'pet': 'Carrier Pigeon'\n ... })\n {u'data': {u'addresses': [{u'awesomeness': 7,\n u'city': u'Fakeville',\n u'street': u'123 Fake Ln.'},\n {u'awesomeness': 3,\n u'city': u'A Void',\n u'street': u'The 2nd Missing field St.'}],\n u'age': 11,\n u'description': u'Hello',\n u'favorite_color': u'',\n u'first_name': u'Bob',\n u'happy': False,\n u'last_name': u'Zope',\n u'other': u'So nice to meet you',\n u'pet': u'Carrier Pigeon',\n u'secret': u'Oh nothing',\n u'siblings': 1,\n u'temperment': u'Nice',\n u'weight': u'170.5'},\n u'self_app_class_name': u'FormExample',\n u'self_class_name': u'ExampleForm',\n u'self_context_class_name': u'Folder'}\n\n\nReordering items in a list is accomplished by reordering the suffix for the\nrecord fields:\n\n >>> print_form(browser, 'http://localhost/form.html/ExampleForm/register',\n ... {'first_name': 'Bob',\n ... 'last_name': 'Zope',\n ... 'favorite_color': '',\n ... 'age': '11',\n ... 'addresses.street.1': '123 Fake Ln.',\n ... 'addresses.city.1': 'Fakeville',\n ... 'addresses.awesomeness.1': 7,\n ... 'addresses.street.0': 'The 2nd Missing field St.',\n ... 'addresses.city.0': 'A Void',\n ... 'addresses.awesomeness.0': 3,\n ... 'description': 'Hello',\n ... 'other': 'So nice to meet you',\n ... 'secret': 'Oh nothing',\n ... 'siblings': 1,\n ... 'temperment': 'Nice',\n ... 'weight': '170.5',\n ... 'pet': 'Carrier Pigeon'\n ... })\n {u'data': {u'addresses': [{u'awesomeness': 3,\n u'city': u'A Void',\n u'street': u'The 2nd Missing field St.'},\n {u'awesomeness': 7,\n u'city': u'Fakeville',\n u'street': u'123 Fake Ln.'}],\n u'age': 11,\n u'description': u'Hello',\n u'favorite_color': u'',\n u'first_name': u'Bob',\n u'happy': False,\n u'last_name': u'Zope',\n u'other': u'So nice to meet you',\n u'pet': u'Carrier Pigeon',\n u'secret': u'Oh nothing',\n u'siblings': 1,\n u'temperment': u'Nice',\n u'weight': u'170.5'},\n u'self_app_class_name': u'FormExample',\n u'self_class_name': u'ExampleForm',\n u'self_context_class_name': u'Folder'}\n\n\n\nGetting definitions from Python\n-------------------------------\n\nSometimes we want to get form definitions from Python. The form\n__call__ method returns a JSON string. We can get Python data by\ncalling get_definition.\n\n >>> import zc.ajaxform.form_example\n >>> import zope.publisher.browser\n >>> request = zope.publisher.browser.TestRequest()\n >>> import zc.ajaxform.interfaces\n >>> import zope.interface\n >>> zope.interface.alsoProvides(\n ... request, zc.ajaxform.interfaces.IAjaxRequest)\n >>> ex = zc.ajaxform.form_example.FormExample(None, request)\n >>> from pprint import pprint\n >>> pprint(ex.ExampleForm.get_definition(), width=1)\n {'actions': [{'label': 'Register',\n 'name': u'ExampleForm.actions.register',\n 'url': u'ExampleForm/register'}],\n 'left_fields': {'addresses': False,\n 'age': True,\n 'description': False,\n 'favorite_color': False,\n 'first_name': True,\n 'happy': False,\n 'last_name': True,\n 'other': True,\n 'pet': False,\n 'secret': False,\n 'siblings': False,\n 'temperment': False,\n 'weight': False},\n 'prefix': 'ExampleForm',\n 'widgets': [{'fieldHint': u'Given name.',\n 'fieldLabel': u'First name',\n 'id': 'first_name',\n 'minLength': 0,\n 'name': 'first_name',\n 'required': True,\n 'value': u'Happy',\n 'widget_constructor': 'zope.schema.TextLine'},\n {'fieldHint': u'Family name.',\n 'fieldLabel': u'Last name',\n 'id': 'last_name',\n 'minLength': 0,\n 'name': 'last_name',\n 'required': True,\n 'value': u'Camper',\n 'widget_constructor': 'zope.schema.TextLine'},\n {'fieldHint': u'',\n 'fieldLabel': u'Favorite color',\n 'id': 'favorite_color',\n 'minLength': 0,\n 'name': 'favorite_color',\n 'required': False,\n 'value': u'Blue',\n 'widget_constructor': 'zope.schema.TextLine'},\n {'allowBlank': False,\n 'fieldHint': u'Age in years',\n 'fieldLabel': u'Age',\n 'field_max': 200,\n 'field_min': 0,\n 'id': 'age',\n 'name': 'age',\n 'required': True,\n 'value': u'23',\n 'widget_constructor': 'zope.schema.Int'},\n {'fieldHint': u'Are they happy?',\n 'fieldLabel': u'Happy',\n 'id': 'happy',\n 'name': 'happy',\n 'required': True,\n 'value': True,\n 'widget_constructor': 'zope.schema.Bool'},\n {'fieldHint': u\"This person's best friend.\",\n 'fieldLabel': u'Pet',\n 'id': 'pet',\n 'name': 'pet',\n 'required': False,\n 'values': [['c935d187f0b998ef720390f85014ed1e',\n u'Dog'],\n ['fa3ebd6742c360b2d9652b7f78d9bd7d',\n u'Cat'],\n ['071642fa72ba780ee90ed36350d82745',\n u'Fish']],\n 'widget_constructor': 'zc.ajaxform.widgets.ComboBox'},\n {'allowBlank': False,\n 'fieldHint': u'What is the person like?',\n 'fieldLabel': u'Temperment',\n 'hiddenName': 'temperment.value',\n 'id': 'temperment',\n 'name': 'temperment',\n 'required': True,\n 'value': 'Right Neighborly',\n 'values': [['Nice',\n u'Nice'],\n ['Mean',\n u'Mean'],\n ['Ornery',\n u'Ornery'],\n ['Right Neighborly',\n u'Right Neighborly']],\n 'widget_constructor': 'zope.schema.Choice'},\n {'allowBlank': False,\n 'fieldHint': u'Weight in lbs?',\n 'fieldLabel': u'Weight',\n 'id': 'weight',\n 'name': 'weight',\n 'required': True,\n 'widget_constructor': 'zope.schema.Decimal'},\n {'fieldHint': u'What do they look like?',\n 'fieldLabel': u'Description',\n 'id': 'description',\n 'minLength': 0,\n 'name': 'description',\n 'required': True,\n 'value': u'10ft tall\\nRazor sharp scales.',\n 'widget_constructor': 'zope.schema.Text'},\n {'fieldHint': u\"Don't tell anybody\",\n 'fieldLabel': u'Secret Key',\n 'id': 'secret',\n 'name': 'secret',\n 'required': True,\n 'value': u'5ecret sauce',\n 'widget_constructor': 'zc.ajaxform.widgets.Hidden'},\n {'allowBlank': False,\n 'fieldHint': u'Number of siblings',\n 'fieldLabel': u'Siblings',\n 'field_max': 8,\n 'field_min': 0,\n 'id': 'siblings',\n 'name': 'siblings',\n 'required': True,\n 'value': u'1',\n 'widget_constructor': 'zc.ajaxform.widgets.NumberSpinner'},\n {'fieldHint': u'All my wonderful homes',\n 'fieldLabel': u'Addresses',\n 'id': 'addresses',\n 'name': 'addresses',\n 'record_schema': {'readonly': False,\n 'widgets': [{'fieldHint': u'The street',\n 'fieldLabel': u'Street',\n 'id': 'street',\n 'minLength': 0,\n 'name': 'street',\n 'required': True,\n 'widget_constructor': 'zope.schema.TextLine'},\n {'fieldHint': u'The city',\n 'fieldLabel': u'City',\n 'id': 'city',\n 'minLength': 0,\n 'name': 'city',\n 'required': True,\n 'widget_constructor': 'zope.schema.TextLine'},\n {'allowBlank': False,\n 'fieldHint': u'The awesomeness on a scale of 1 to 10',\n 'fieldLabel': u'Awesomeness',\n 'field_max': 10,\n 'field_min': 1,\n 'id': 'awesomeness',\n 'name': 'awesomeness',\n 'required': True,\n 'widget_constructor': 'zope.schema.Int'}]},\n 'required': True,\n 'value': [{'awesomeness': u'9',\n 'city': u'fakeville',\n 'street': u'123 fake street'},\n {'awesomeness': u'9001',\n 'city': u'falsetown',\n 'street': u'345 false street'}],\n 'widget_constructor': 'zope.schema.List'},\n {'fieldHint': u'Any other notes',\n 'fieldLabel': u'Other',\n 'id': 'other',\n 'minLength': 0,\n 'name': 'other',\n 'required': True,\n 'value': u\"I've got a magic toenail\",\n 'widget_constructor': 'zope.schema.Text'}]}\n\nNote that we had to stamp the request with IAjaxRequest. This is done\nduring application traversal. We need it so widgets can get looked\nup.\n\n\nBase and prefix\n---------------\n\nForms have base_href and prefix variables. The base_href is used to compute\nURLs for form actions. A form's base_href defaults to its class name.\nThe form's base_href also includes the base_href of its app, if its app has\na base_href. This is useful for sub-applications. Let's give our sample\napplication a base_href attribute as if it were a sub-application:\n\n >>> ex = zc.ajaxform.form_example.FormExample(None, request)\n >>> ex.base_href = 'sample'\n >>> ex.ExampleForm.base_href\n 'sample/ExampleForm'\n\n >>> pprint(ex.ExampleForm.get_definition(), width=1)\n {'actions': [{'label': 'Register',\n 'name': u'sample.ExampleForm.actions.register',\n 'url': u'sample/ExampleForm/register'}],\n 'left_fields': {'addresses': False,\n 'age': True,\n 'description': False,\n 'favorite_color': False,\n 'first_name': True,\n 'happy': False,\n 'last_name': True,\n 'other': True,\n 'pet': False,\n 'secret': False,\n 'siblings': False,\n 'temperment': False,\n 'weight': False},\n 'prefix': 'sample.ExampleForm',\n 'widgets': [{'fieldHint': u'Given name.',\n 'fieldLabel': u'First name',\n 'id': 'first_name',\n 'minLength': 0,\n 'name': 'first_name',\n 'required': True,\n 'value': u'Happy',\n 'widget_constructor': 'zope.schema.TextLine'},\n {'fieldHint': u'Family name.',\n 'fieldLabel': u'Last name',\n 'id': 'last_name',\n 'minLength': 0,\n 'name': 'last_name',\n 'required': True,\n 'value': u'Camper',\n 'widget_constructor': 'zope.schema.TextLine'},\n {'fieldHint': u'',\n 'fieldLabel': u'Favorite color',\n 'id': 'favorite_color',\n 'minLength': 0,\n 'name': 'favorite_color',\n 'required': False,\n 'value': u'Blue',\n 'widget_constructor': 'zope.schema.TextLine'},\n {'allowBlank': False,\n 'fieldHint': u'Age in years',\n 'fieldLabel': u'Age',\n 'field_max': 200,\n 'field_min': 0,\n 'id': 'age',\n 'name': 'age',\n 'required': True,\n 'value': u'23',\n 'widget_constructor': 'zope.schema.Int'},\n {'fieldHint': u'Are they happy?',\n 'fieldLabel': u'Happy',\n 'id': 'happy',\n 'name': 'happy',\n 'required': True,\n 'value': True,\n 'widget_constructor': 'zope.schema.Bool'},\n {'fieldHint': u\"This person's best friend.\",\n 'fieldLabel': u'Pet',\n 'id': 'pet',\n 'name': 'pet',\n 'required': False,\n 'values': [['c935d187f0b998ef720390f85014ed1e',\n u'Dog'],\n ['fa3ebd6742c360b2d9652b7f78d9bd7d',\n u'Cat'],\n ['071642fa72ba780ee90ed36350d82745',\n u'Fish']],\n 'widget_constructor': 'zc.ajaxform.widgets.ComboBox'},\n {'allowBlank': False,\n 'fieldHint': u'What is the person like?',\n 'fieldLabel': u'Temperment',\n 'hiddenName': 'temperment.value',\n 'id': 'temperment',\n 'name': 'temperment',\n 'required': True,\n 'value': 'Right Neighborly',\n 'values': [['Nice',\n u'Nice'],\n ['Mean',\n u'Mean'],\n ['Ornery',\n u'Ornery'],\n ['Right Neighborly',\n u'Right Neighborly']],\n 'widget_constructor': 'zope.schema.Choice'},\n {'allowBlank': False,\n 'fieldHint': u'Weight in lbs?',\n 'fieldLabel': u'Weight',\n 'id': 'weight',\n 'name': 'weight',\n 'required': True,\n 'widget_constructor': 'zope.schema.Decimal'},\n {'fieldHint': u'What do they look like?',\n 'fieldLabel': u'Description',\n 'id': 'description',\n 'minLength': 0,\n 'name': 'description',\n 'required': True,\n 'value': u'10ft tall\\nRazor sharp scales.',\n 'widget_constructor': 'zope.schema.Text'},\n {'fieldHint': u\"Don't tell anybody\",\n 'fieldLabel': u'Secret Key',\n 'id': 'secret',\n 'name': 'secret',\n 'required': True,\n 'value': u'5ecret sauce',\n 'widget_constructor': 'zc.ajaxform.widgets.Hidden'},\n {'allowBlank': False,\n 'fieldHint': u'Number of siblings',\n 'fieldLabel': u'Siblings',\n 'field_max': 8,\n 'field_min': 0,\n 'id': 'siblings',\n 'name': 'siblings',\n 'required': True,\n 'value': u'1',\n 'widget_constructor': 'zc.ajaxform.widgets.NumberSpinner'},\n {'fieldHint': u'All my wonderful homes',\n 'fieldLabel': u'Addresses',\n 'id': 'addresses',\n 'name': 'addresses',\n 'record_schema': {'readonly': False,\n 'widgets': [{'fieldHint': u'The street',\n 'fieldLabel': u'Street',\n 'id': 'street',\n 'minLength': 0,\n 'name': 'street',\n 'required': True,\n 'widget_constructor': 'zope.schema.TextLine'},\n {'fieldHint': u'The city',\n 'fieldLabel': u'City',\n 'id': 'city',\n 'minLength': 0,\n 'name': 'city',\n 'required': True,\n 'widget_constructor': 'zope.schema.TextLine'},\n {'allowBlank': False,\n 'fieldHint': u'The awesomeness on a scale of 1 to 10',\n 'fieldLabel': u'Awesomeness',\n 'field_max': 10,\n 'field_min': 1,\n 'id': 'awesomeness',\n 'name': 'awesomeness',\n 'required': True,\n 'widget_constructor': 'zope.schema.Int'}]},\n 'required': True,\n 'value': [{'awesomeness': u'9',\n 'city': u'fakeville',\n 'street': u'123 fake street'},\n {'awesomeness': u'9001',\n 'city': u'falsetown',\n 'street': u'345 false street'}],\n 'widget_constructor': 'zope.schema.List'},\n {'fieldHint': u'Any other notes',\n 'fieldLabel': u'Other',\n 'id': 'other',\n 'minLength': 0,\n 'name': 'other',\n 'required': True,\n 'value': u\"I've got a magic toenail\",\n 'widget_constructor': 'zope.schema.Text'}]}\n\nNote that the action URL now includes \"sample/\" as a prefix. Also\nnote that the widget and action names have \"\" as a prefix. The\nform prefix is simply its base with \"/\"s converted to \".\"s.\n\n >>> ex.ExampleForm.prefix\n 'sample.ExampleForm'\n\n\nForm data\n---------\n\nAjax forms are a bit different from normal web forms because the data\nand the form definition can be fetched separately. For example, we\nmay use the same form to edit multiple objects. Form objects have a\ngetObjectData method that returns data suitable for editing form field\nvalues. Let's create a person and use out form to get data for them:\n\n >>> bob = zc.ajaxform.form_example.Person(\n ... first_name='bob',\n ... last_name='smith',\n ... favorite_color=None,\n ... age=11,\n ... happy=True,\n ... pet=u'Cockatiel',\n ... temperment='Nice',\n ... weight = 175.5,\n ... description = 'A real cool guy',\n ... secret = 'Noone knows!',\n ... siblings = 1,\n ... addresses = [],\n ... other = 'stuff')\n >>> pprint(ex.ExampleForm.getObjectData(bob), width=1)\n {'addresses': [],\n 'age': u'11',\n 'description': u'A real cool guy',\n 'first_name': u'bob',\n 'happy': True,\n 'last_name': u'smith',\n 'other': u'stuff',\n 'pet': u'Cockatiel',\n 'secret': u'Noone knows!',\n 'siblings': u'1',\n 'temperment': 'Nice',\n 'weight': u'175.5'}\n\nWe didn't set the favorite_color for the person, so it is ommitted\nfrom the data.\n\nWe can pass in a dictionary of values that take precedence over object data:\n\n >>> pprint(ex.ExampleForm.getObjectData(\n ... bob, {'age': u'1'}),\n ... width=1)\n {'addresses': [],\n 'age': u'1',\n 'description': u'A real cool guy',\n 'first_name': u'bob',\n 'happy': True,\n 'last_name': u'smith',\n 'other': u'stuff',\n 'pet': u'Cockatiel',\n 'secret': u'Noone knows!',\n 'siblings': u'1',\n 'temperment': 'Nice',\n 'weight': u'175.5'}\n\n\nDisplay Options\n---------------\n\nAdditional display options may be sent in the widget definition if the widget\ncan be adapted to `IDisplayOptions`. The result of the adaptation only need\nbe JSON serializable.\n\n >>> import zope.app.form.interfaces\n\n >>> def example_options(widget):\n ... field, name = widget.context, widget.context.__name__\n ... if name == 'favorite_color':\n ... return {'picker': 'crayons'}\n ... elif name == 'secret':\n ... return 'super-secret'\n ... else:\n ... return None\n\n >>> site_manager = zope.component.getSiteManager()\n >>> site_manager.registerAdapter(\n ... example_options,\n ... required=(zope.app.form.interfaces.IWidget,),\n ... provided=zc.ajaxform.interfaces.IDisplayOptions)\n\n >>> result = call_form(\n ... browser, 'http://localhost/form.html/ExampleForm')\n\n >>> widgets = result['definition']['widgets']\n >>> for widget in widgets:\n ... if widget.get('display_options'):\n ... print widget['name'] + ':', widget['display_options']\n favorite_color: {u'picker': u'crayons'}\n secret: super-secret\n\nFinally, clean up.\n\n >>> site_manager.unregisterAdapter(\n ... example_options,\n ... required=(zope.app.form.interfaces.IWidget,),\n ... provided=zc.ajaxform.interfaces.IDisplayOptions)\n True\n\n\nTo-do (maybe)\n-------------\n\nMore widgets!\n\nInterface invariants\n\nActions:\n\n- conditions\n\n- validators\n\n- failure handlers\n\n\n.. [#application] See application.txt\n\n.. [#form_classes_are_descriptors] Form classes are also\n descriptors. They get called with the instance they're accessed\n through.\n\n.. [#actionids] The Javascript code that sets up action buttons uses\n action name as the button's ID.\n\nDownload\n********", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "UNKNOWN", "keywords": null, "license": "ZPL 2.1", "maintainer": null, "maintainer_email": null, "name": "zc.ajaxform", "package_url": "https://pypi.org/project/zc.ajaxform/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/zc.ajaxform/", "project_urls": { "Download": "UNKNOWN", "Homepage": "UNKNOWN" }, "release_url": "https://pypi.org/project/zc.ajaxform/0.7.0/", "requires_dist": null, "requires_python": null, "summary": "UNKNOWN", "version": "0.7.0" }, "last_serial": 802157, "releases": { "0.7.0": [ { "comment_text": "", "digests": { "md5": "23816783961b2c4c93b7f338e56d1fc1", "sha256": "ff540adc6bac83d3597709cc1a472b83a8ce1440620f45672d1b9476350de3cc" }, "downloads": -1, "filename": "zc.ajaxform-0.7.0.tar.gz", "has_sig": false, "md5_digest": "23816783961b2c4c93b7f338e56d1fc1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 57127, "upload_time": "2010-04-27T17:53:35", "url": "https://files.pythonhosted.org/packages/53/a9/46e1974bc7ed48b6ca173c4b2534af9326d483bad3d44279918f9887c6dd/zc.ajaxform-0.7.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "23816783961b2c4c93b7f338e56d1fc1", "sha256": "ff540adc6bac83d3597709cc1a472b83a8ce1440620f45672d1b9476350de3cc" }, "downloads": -1, "filename": "zc.ajaxform-0.7.0.tar.gz", "has_sig": false, "md5_digest": "23816783961b2c4c93b7f338e56d1fc1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 57127, "upload_time": "2010-04-27T17:53:35", "url": "https://files.pythonhosted.org/packages/53/a9/46e1974bc7ed48b6ca173c4b2534af9326d483bad3d44279918f9887c6dd/zc.ajaxform-0.7.0.tar.gz" } ] }