{ "info": { "author": "Sylvain Viollon", "author_email": "sylvain@infrae.com", "bugtrack_url": null, "classifiers": [ "Framework :: Plone", "Framework :: Zope2", "Programming Language :: Python", "Topic :: Software Development :: Libraries :: Python Modules" ], "description": "*******************************************\nZope 3 schema field for plone.app.relations\n*******************************************\n\n.. contents::\n\n\nThe purpose of this extension is to provide a Zope 3 schema for plone\nrelations. This has been tested with Plone 2.5 and Plone 3.\n\nInterface definition\n====================\n\nA new field::\n\n >>> from infrae.plone.relations.schema import PloneRelation\n\nA simple interface with an field::\n\n >>> from zope.interface import Interface, implements\n >>> class IContent(Interface):\n ... \"\"\"Sample interface.\"\"\"\n ... relation = PloneRelation(relation=\"my relation\")\n\nI can get the field from the interface::\n\n >>> r_field = IContent.get('relation')\n\nAnd this is a field::\n\n >>> from zope.interface.verify import verifyObject\n >>> from zope.schema.interfaces import IField\n >>> verifyObject(IField, r_field)\n True\n\nNow, a field to look up reverse relations::\n\n >>> class IBackContent(Interface):\n ... \"\"\"Sample interface.\"\"\"\n ... relation = PloneRelation(relation=\"my relation\",\n ... reverse=True)\n\n\nCreate a simple content for test purpose\n========================================\n\nAnd a simple implementation::\n \n >>> from OFS.Folder import Folder\n >>> class BaseContent(Folder):\n ... def __init__(self, id):\n ... super(BaseContent, self).__init__()\n ... self.id = id\n ... def UID(self):\n ... return 'uid-%s' % self.id\n\nUID are used by the context factory.\n\nStandard base class::\n\n >>> class MyContent(BaseContent):\n ... implements(IContent)\n\nAnd::\n\n >>> class MyBackContent(BaseContent):\n ... implements(IBackContent)\n \n\nNow, create some items::\n\n >>> for id in range(1, 5):\n ... name = 'it%d' % id\n ... item = MyContent(name)\n ... self.portal._setObject(name, item)\n 'it1'\n 'it2'\n 'it3'\n 'it4'\n >>> it1 = self.portal.it1\n >>> it2 = self.portal.it2\n >>> it3 = self.portal.it3\n >>> it4 = self.portal.it4\n\n >>> itb1 = MyBackContent('itb1')\n >>> self.portal._setObject('itb1', itb1)\n 'itb1'\n >>> itb1 = self.portal.itb1\n\n\nUtility to display relation\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAn helper to display a relation::\n\n >>> def display(rels):\n ... for rel in rels:\n ... print \"Objects: %s\" % list(rel['objects'])\n ... if rel.has_key(\"context\"):\n ... print \"Context: %s\" % rel['context']\n ... if not rels:\n ... print \"Empty\"\n\n\nSimple field use\n================\n\nDirect set, and reverse access\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nAnd try some validation on data. Data is a list of dictionary,\nrepresenting all relations of the field. In the dictionary:\n\n- ``objects``: represent a list of object for the relation;\n\n- ``context``: may be an object stored as context of the relation.\n\nExample::\n\n >>> bad_relation1 = [{'bad': None},]\n >>> r_field.validate(bad_relation1)\n Traceback (most recent call last):\n ...\n ValidationError: Invalid structure\n >>> good_relation = [{'objects': [it2, itb1]},]\n >>> r_field.validate(good_relation)\n\nAnd set the field::\n\n >>> r_field.set(it1, good_relation)\n\nAnd get data from the field::\n\n >>> relation = r_field.get(it1)\n >>> relation\n [{'objects': }]\n >>> display(relation)\n Objects: [, ]\n\nNow, we can ask from ``itb1`` content, which has a reverse field::\n\n >>> rb_field = IBackContent.get('relation')\n >>> relation = rb_field.get(itb1)\n >>> relation\n [{'objects': }]\n >>> display(relation)\n Objects: []\n\nWe update the relation::\n\n >>> good_relation = [{'objects': [it2, it3]},]\n >>> r_field.set(it1, good_relation)\n\nSo change is reflected::\n\n >>> display(r_field.get(it1))\n Objects: [, ]\n\n\nAnd there is no more relation in the reverse field::\n\n >>> rb_field = IBackContent.get('relation')\n >>> rb_field.get(itb1)\n []\n\nNow, set on reverse field::\n\n >>> good_relation = [{'objects': [it1, it2]}]\n >>> rb_field.set(itb1, good_relation)\n >>> display(rb_field.get(itb1))\n Objects: [, ]\n\nAnd on the normal::\n\n >>> display(r_field.get(it1))\n Objects: [, ]\n Objects: []\n\n\nDeletion\n~~~~~~~~\n \nYou can delete value by setting the relation to an empty list ``[]``::\n \n >>> display(r_field.get(it2))\n Objects: []\n >>> r_field.set(it2, [])\n >>> display(r_field.get(it2))\n Empty\n >>> display(rb_field.get(itb1))\n Objects: []\n\nAnd::\n\n >>> display(r_field.get(it1))\n Objects: [, ]\n Objects: []\n >>> r_field.set(it1, [])\n >>> display(r_field.get(it1))\n Empty\n >>> display(rb_field.get(itb1))\n Empty\n\n\nField independence\n~~~~~~~~~~~~~~~~~~\n\nOne other relation schema::\n\n >>> class IComplexContent(Interface):\n ... \"\"\"A content with two relation.\"\"\"\n ... relation1 = PloneRelation(relation=\"relation1\")\n ... relation2 = PloneRelation(relation=\"relation2\")\n\nAnd the related content::\n\n >>> class MyComplexContent(BaseContent):\n ... implements(IComplexContent)\n\nCreate three objects like this::\n\n >>> itcx1 = MyComplexContent(\"itcx1\")\n >>> self.portal._setObject(\"itcx1\", itcx1)\n 'itcx1'\n >>> itcx1 = self.portal.itcx1\n >>> itcx2 = MyComplexContent(\"itcx2\")\n >>> self.portal._setObject(\"itcx2\", itcx2)\n 'itcx2'\n >>> itcx2 = self.portal.itcx2\n >>> itcx3 = MyComplexContent(\"itcx3\")\n >>> self.portal._setObject(\"itcx3\", itcx3)\n 'itcx3'\n >>> itcx3 = self.portal.itcx3\n \nNow, add relation::\n\n >>> r1_field = IComplexContent.get(\"relation1\")\n >>> r1_field.set(itcx1, [{'objects': [itcx2,]}])\n >>> display(r1_field.get(itcx1))\n Objects: []\n >>> r2_field = IComplexContent.get(\"relation2\")\n >>> r2_field.set(itcx1, [{'objects': [itcx3,]}])\n >>> display(r2_field.get(itcx1))\n Objects: []\n\nAnd delete one::\n\n >>> r2_field.set(itcx1, [])\n >>> display(r2_field.get(itcx1))\n Empty\n >>> display(r1_field.get(itcx1))\n Objects: []\n\n\nMore Constraints\n================\n\nNow, you have to give at least 1 value, and no more than 3::\n\n >>> class ILengthContent(Interface):\n ... \"\"\"Sample interface with length control.\"\"\"\n ... relation = PloneRelation(relation=\"my relation\",\n ... min_length=1,\n ... max_length=3)\n\n\nThe field implements ``IMinMaxLen``::\n\n >>> from zope.schema.interfaces import IMinMaxLen\n >>> rl_field = ILengthContent.get('relation')\n >>> verifyObject(IMinMaxLen, rl_field)\n True\n\nOk, now some bad tries::\n\n >>> bad_relation = []\n >>> rl_field.validate(bad_relation)\n Traceback (most recent call last):\n ...\n TooSmall: Less than 1 values\n\n >>> bad_relation = [{'objects': [it2,]},\n ... {'objects': [it3,]},\n ... {'objects': [it4,]},\n ... {'objects': [itb1,]},]\n >>> rl_field.validate(bad_relation)\n Traceback (most recent call last):\n ...\n TooBig: More than 3 values\n\nAnd now, one correct::\n\n >>> good_relation = [{'objects': [it2,]},]\n >>> rl_field.validate(good_relation)\n\nBut we want also to have uniques objects in the relation::\n\n >>> class IUniqueContent(Interface):\n ... \"\"\"Sample interface only one item per relation.\"\"\"\n ... relation = PloneRelation(relation=\"my relation\",\n ... unique=True)\n >>> ru_field = IUniqueContent.get('relation')\n\nSome tries now::\n\n >>> bad_relation = [{'objects': [it2, it3,]}]\n >>> ru_field.validate(bad_relation)\n Traceback (most recent call last):\n ...\n ValidationError: Not uniques values in relation\n\n >>> good_relation = [{'objects': [it2,]}]\n >>> ru_field.validate(good_relation)\n\nWe want that every object in the relation implements a particular\ninterface::\n\n >>> class IConstraintContent(Interface):\n ... \"\"\"Sample interface with constraint on relation.\"\"\"\n ... relation = PloneRelation(relation=\"my relation\",\n ... relation_schema=IUniqueContent)\n >>> rs_field = IConstraintContent.get('relation')\n\n\nUse of context object\n=====================\n\nTwo interfaces let you work with context objects::\n\n >>> from infrae.plone.relations.schema import IPloneRelationContext\n >>> from infrae.plone.relations.schema import IPloneRelationContextFactory\n\nThis two next import are helpers, but you can use them since it's good\ncontent start::\n\n >>> from infrae.plone.relations.schema import BasePloneRelationContext\n >>> from infrae.plone.relations.schema import BasePloneRelationContextFactory\n \nThe following context interface::\n\n >>> class IContextObject(IPloneRelationContext):\n ... \"\"\"Simple context object.\"\"\"\n\nAnd its corresponding object::\n\n >>> class MyContextObject(BasePloneRelationContext):\n ... implements(IContextObject)\n\nWe will declare the field like this::\n\n >>> class IContentWithContext(Interface):\n ... \"\"\"Simple content with a context.\"\"\"\n ... relation = PloneRelation(relation=\"context relation\",\n ... context_schema=IContextObject)\n\nWe want an object with this schema::\n\n >>> class MyContentWithContext(BaseContent):\n ... implements(IContentWithContext)\n\nCreate the object::\n\n >>> itc1 = MyContentWithContext('itc1')\n >>> self.portal._setObject('itc1', itc1)\n 'itc1'\n >>> itc1 = self.portal.itc1\n\nPrepare one context object::\n\n >>> ctxt_fac = BasePloneRelationContextFactory(MyContextObject, IContextObject)\n >>> verifyObject(IPloneRelationContextFactory, ctxt_fac)\n True\n >>> ctxt1 = ctxt_fac(itc1, it1, dict())\n >>> ctxt1\n \n >>> verifyObject(IContextObject, ctxt1)\n True\n\nGet the field::\n\n >>> rc_field = IContentWithContext.get('relation')\n\nNow we can try this relation::\n\n >>> bad_relation = [{'objects': [it2, itb1,], 'context': it3,}]\n >>> rc_field.validate(bad_relation)\n Traceback (most recent call last):\n ...\n ValidationError: Invalid context\n >>> good_relation = [{'objects': [it2, itb1,], 'context': ctxt1,}]\n >>> rc_field.validate(good_relation)\n >>> rc_field.set(itc1, good_relation)\n\nIf we consult the relation::\n\n >>> display(rc_field.get(itc1))\n Objects: [, ]\n Context: \n\n\nMany to Many Relation Interface\n===============================\n\nThis interface provides a more generic way to edit relations than the\none provided by ``plone.app.relations``, to let the Zope 3 schema work\nin both way (normal access to the relation, and reverse access).\n\nCreate simple content::\n\n >>> from OFS.SimpleItem import SimpleItem\n >>> class BaseContent(SimpleItem):\n ... def __init__(self, id):\n ... super(BaseContent, self).__init__()\n ... self.id = id\n \n\n >>> for num in range(1, 20):\n ... id = 'it%02d' % num\n ... it = BaseContent(id)\n ... _ = self.portal._setObject(id, it)\n\n >>> self.portal.it01\n \n\nContents must be ``IPersistent``::\n\n >>> from persistent import IPersistent\n >>> from zope.interface.verify import verifyObject\n >>> verifyObject(IPersistent, self.portal.it01)\n True\n\n\nSimple test of the interface\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nWe have a new adapter to work on your relation::\n\n >>> from infrae.plone.relations.schema import IManyToManyRelationship\n >>> manager = IManyToManyRelationship(self.portal.it01)\n >>> verifyObject(IManyToManyRelationship, manager)\n True\n\nOk, try to add relation::\n\n >>> rel = manager.createRelationship((self.portal.it11, self.portal.it12,),\n ... sources=(self.portal.it02,),\n ... relation='test')\n >>> list(rel.sources)\n [, ]\n >>> list(rel.targets)\n [, ]\n\nNow, we can retrieve a list of relation::\n\n >>> list(manager.getRelationships())\n [, ) to (, )>]\n\n\nDirection\n~~~~~~~~~\n\nYou can reverse the way a relation works, with the ``setDirection``\nmethod::\n\n >>> rel = manager.createRelationship(self.portal.it05, relation='reverse')\n >>> list(rel.targets)\n []\n >>> manager.setDirection(False)\n >>> rel = manager.createRelationship(self.portal.it04, relation='reverse')\n >>> list(rel.targets)\n []\n\nYou have also the transitivity for search::\n\n >>> manager = IManyToManyRelationship(self.portal.it04)\n >>> list(manager.getRelationshipChains(relation='reverse', \n ... target=self.portal.it05,\n ... maxDepth=2))\n [(,) to (,)>, ,) to (,)>)]\n\n \nBut relation are always followed from source to target. So if we\nreverse the search, we won't found a result::\n\n >>> manager.setDirection(False)\n >>> list(manager.getRelationshipChains(relation='reverse', \n ... target=self.portal.it05,\n ... maxDepth=2))\n []\n\nDirection just change the meaning of source or target on the relation\nobject. It's doesn't change the relation itself.\n\n\nBigger example with transitivity\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nTaking back the first test, and add a suite::\n\n >>> manager = IManyToManyRelationship(self.portal.it16)\n >>> manager.setDirection(False)\n >>> rel = manager.createRelationship((self.portal.it12, self.portal.it14),\n ... relation='test')\n >>> manager.setDirection(True)\n >>> rel = manager.createRelationship((self.portal.it17, self.portal.it18),\n ... sources=(self.portal.it19,),\n ... relation='test')\n\nNew chain try::\n\n >>> manager = IManyToManyRelationship(self.portal.it02)\n >>> list(manager.getRelationshipChains(relation='test',\n ... target=self.portal.it18,\n ... maxDepth=3))\n [(, ) to (, )>, , ) to (,)>, , ) to (, )>)]\n\n\nAccessor\n~~~~~~~~\n\n``getTargets`` returns a lazy list of objects having a relation with\nthe given object as source, and ``getSources`` returns a lazy list of\nobjects having a relation with the given object as target::\n\n >>> manager = IManyToManyRelationship(self.portal.it16)\n >>> list(manager.getTargets())\n [, ]\n >>> list(manager.getSources())\n [, ]\n\nIf we reverse the direction::\n\n >>> manager.setDirection(False)\n >>> list(manager.getTargets())\n [, ]\n >>> list(manager.getSources())\n [, ]\n\n\nDeletion\n~~~~~~~~\n\nDelete relation::\n\n >>> manager.setDirection(True)\n >>> manager.deleteRelationship()\n >>> list(manager.getRelationships())\n []\n\n >>> manager = IManyToManyRelationship(self.portal.it19)\n >>> list(manager.getRelationships())\n [,) to (, )>]\n\n >>> manager.deleteRelationship(target=self.portal.it17)\n >>> list(manager.getRelationships())\n [,) to (,)>]\n\n >>> manager.deleteRelationship()\n >>> list(manager.getRelationships())\n []\n\n >>> manager = IManyToManyRelationship(self.portal.it01)\n >>> manager.deleteRelationship(remove_all_sources=True, multiple=True)\n >>> manager = IManyToManyRelationship(self.portal.it02)\n >>> list(manager.getRelationships())\n []\n\n\n\nChanges\n=======\n\n1.0\n~~~\n\n- First release.\n\n\nCredits\n=======\n\nPowered by the Flemish government of Belgium, for the application\n.", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://svn.infrae.com/PloneComponent/infrae.plone.relations.schema/trunk", "keywords": "relationships plone schema", "license": "GPL", "maintainer": null, "maintainer_email": null, "name": "infrae.plone.relations.schema", "package_url": "https://pypi.org/project/infrae.plone.relations.schema/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/infrae.plone.relations.schema/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://svn.infrae.com/PloneComponent/infrae.plone.relations.schema/trunk" }, "release_url": "https://pypi.org/project/infrae.plone.relations.schema/1.0/", "requires_dist": null, "requires_python": null, "summary": "Zope 3 schema for plone.app.relations items.", "version": "1.0" }, "last_serial": 945365, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "c6d436d41a8191e2ae485b3528e8b207", "sha256": "7854dd0a898dab9fc1e404072f3519ff4e7508bca4ed912b9105287bfeb044eb" }, "downloads": -1, "filename": "infrae.plone.relations.schema-1.0-py2.4.egg", "has_sig": false, "md5_digest": "c6d436d41a8191e2ae485b3528e8b207", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 28536, "upload_time": "2008-06-11T09:03:08", "url": "https://files.pythonhosted.org/packages/06/59/38fde1c3efa6c101e2ef9a9c2105456cfc6385f4712510703474c0d4af9f/infrae.plone.relations.schema-1.0-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "b14edb57938ba3c42c479dc1ccb116b1", "sha256": "3c9358eaa8bd8df4bda42006b2aa48af81b1cce723d27120efe63f94284f5c94" }, "downloads": -1, "filename": "infrae.plone.relations.schema-1.0.tar.gz", "has_sig": false, "md5_digest": "b14edb57938ba3c42c479dc1ccb116b1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12506, "upload_time": "2008-06-11T09:03:15", "url": "https://files.pythonhosted.org/packages/ca/ee/4b094160bb2362f9923280ac989a64efa65af87dd76cdd36d4bdea76e27f/infrae.plone.relations.schema-1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "c6d436d41a8191e2ae485b3528e8b207", "sha256": "7854dd0a898dab9fc1e404072f3519ff4e7508bca4ed912b9105287bfeb044eb" }, "downloads": -1, "filename": "infrae.plone.relations.schema-1.0-py2.4.egg", "has_sig": false, "md5_digest": "c6d436d41a8191e2ae485b3528e8b207", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 28536, "upload_time": "2008-06-11T09:03:08", "url": "https://files.pythonhosted.org/packages/06/59/38fde1c3efa6c101e2ef9a9c2105456cfc6385f4712510703474c0d4af9f/infrae.plone.relations.schema-1.0-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "b14edb57938ba3c42c479dc1ccb116b1", "sha256": "3c9358eaa8bd8df4bda42006b2aa48af81b1cce723d27120efe63f94284f5c94" }, "downloads": -1, "filename": "infrae.plone.relations.schema-1.0.tar.gz", "has_sig": false, "md5_digest": "b14edb57938ba3c42c479dc1ccb116b1", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 12506, "upload_time": "2008-06-11T09:03:15", "url": "https://files.pythonhosted.org/packages/ca/ee/4b094160bb2362f9923280ac989a64efa65af87dd76cdd36d4bdea76e27f/infrae.plone.relations.schema-1.0.tar.gz" } ] }