{ "info": { "author": "Alec Mitchell", "author_email": "apm13@columbia.edu", "bugtrack_url": null, "classifiers": [ "Framework :: Plone", "Framework :: Zope2" ], "description": "Introduction\n============\n\nTools for defining and querying complex relationships between objects. This\nproduct builds on and requires zc.relationship and also five.intid.\n\nCredits\n-------\n\nAuthor: Alec Mitchell \n\nBased on the index and relationship container from zc.relationship by\nGary Poster from Zope Corporation, and the port of the Zope intid utility\nto Zope 2 by Whit Morriss in five.intid. This package includes a slightly\nmodified version of ``container.txt`` from zc.relationship which is copyright\nZope Corporation and distributed under the ZPL. The package was mostly\ninspired by ideas in the doctests of that product.\n\nThis work was partly sponsored by The Daily Reel (http://www.thedailyreel.com)\n\nChangelog\n=========\n\n2.0 - 2011-10-06\n----------------\n\n- Updated to work with and require Zope 2.13.\n [hannosch]\n\n1.0rc4 - unreleased\n-------------------\n\n- Depend on setuptools as we use namespace packages.\n [maurits]\n\n- Depend on zc.relationship 1.1.1 or higher.\n [maurits]\n\n- Depend on zc.relationship 1.1.1 to allow support for ZODB 3.7 and 3.8.\n [alecm]\n\n\n1.0rc3 - 2008-12-06\n-------------------\n\n- Peg to 1.0 versions of zc.relationship to maximize backwards\n compatibility for now.\n [alecm]\n\n\n1.0rc2 - unreleased\n-------------------\n\n- 1.0rc2 was never released.\n\n\n1.0rc1 - 2008-11-26\n-------------------\n\n- Added a changelog and cleaned up package information boilerplate.\n [hannosch]\n\n- Getting a relation could cause a write transaction. This solved\n http://dev.plone.org/plone/ticket/8631.\n [regebro]\n\n\n1.0b5 - 2008-05-10\n------------------\n\n- Removed unused import statements as reported by pyflakes.\n [tomster]\n\n- Don't assume an IntId already exists.\n [alecm]\n\n- Prep release with fixed dependencies, and new adapter based relationship\n proxying.\n [alecm]\n\n- Added support for relationships to proxy objects obtained using adaptation.\n [massimo]\n\n- It's not safe to use 'not target' when target could be a content item (e.g.\n a folderish one for which an empty folder is false). The new code is now\n identical to the same pattern used in zc.relationship.\n [optilude]\n\n- Update version and zc.relationship dependency.\n [alecm]\n\n- Remove collective.testing dependency.\n [alecm]\n\n- Use a savepoint not a full commit during test.\n [alecm]\n\n- We no longer guarantee that the relation objects themselves are wrapped on\n retrieval, only that they can be wrapped as needed.\n [alecm]\n\n- We need implicit acquisition in order to obtain getPhysicalRoot for\n workflow/template expressions.\n [alecm]\n\n- Remove getPhysicalPath methods as they are no longer needed, make the str\n representation of relationships show something reasonable even if some of\n the sources/targets are missing.\n [alecm]\n\n- Early contributions.\n [optilude, ramon, wichert]\n\n- Initial implementation.\n [alecm]\n\nDetailed Documentation\n======================\n\nOverview\n--------\n\nThis is a product built on the ``zc.relationship`` product for Zope 3.\nIt attempts to allow the functionality of that package to be used from\nZope 2, along with some simple additional functionality derived from\nthat package's basic relationship ``Index``.\n\nThe relationship container provided here is very similar to the one in\n``zc.relationship``. It is used to store and query objects\nimplementing or adaptable to the simple IRelationship interface, but\nmore complex relationships are supported as well. This extra\nfunctionality is defined in a few extensions to the IRelationship\ninterface. These interfaces are described below:\n\n IRelationship defines a basic relationship consisting of\n only ``sources`` and ``targets``. These are sequences of\n objects that comprise the relationship. In the default\n implementation these must all be persistent objects from\n the ZODB (or more generally, objects for which and\n ``intid`` can be generated using the available ``IIntId``\n utility (cf ``zope.intid`` and ``five.intid``)).\n\n IComplexRelationship adds a relationship predicate to\n indicate the type of relationship involved. This\n predicate is retrieved from an attribute called\n ``relation`` which should be an immutable unicode string\n (so a zope.i18n.Message can be used) in the default\n implementation.\n\n IContextAwareRelationship adds a context in which the\n relationship applies. This context is provided by a\n method called ``getContext`` which, in the default\n implementation, should return objects of the same sort\n required by IRelationship (e.g. persistent objects from\n the ZODB). An example: a hierarchical relationship which\n exists only within the _context_ of a specific department\n or project.\n\n IStatefulRelationship adds a relationship state to\n indicate the status of a particular relationship in the\n case that the relationship is one which changes over time\n or as a result of user actions. This state is retrieved\n from an attribute called ``state`` which should be an\n immutable unicode string (see above). For example: a\n relationship which requires explicit approval by the\n involved target objects, it would start in an unapproved\n ``state`` and then transition to approved when the target\n objects had signaled their approval. Also, the ``state``\n may represent a different stages of a particular\n relationship, e.g. ``stranger``, ``acquaintance``,\n ``pal``, ``friend``, ``BFF``.\n\n\nThese additional interfaces are entirely optional and may will be\nlooked up using adaptation to the desired interface. So the\nrelationship objects themselves do not have to directly provide these\nproperties or methods, though that is also possible. Only ``sources``\nand ``targets`` are required to make a query-able relationship.\n\nThis additional richness could have been obtained using post query\nfilters, as supported by the default ``zc.relationship`` container.\nHowever, filtering in this way is much less efficient that allowing\nthese potentially common attributes to be indexed and queried directly\n(especially when doing so only results in a small increase in storage\nrequirements.\n\n\nUsing This Package\n------------------\n\nThe basic functionality provided by this package is demonstrated and\ntested in ``container.txt``, which essentially duplicates the\ncontainer tests from ``zc.relationship`` in a Zope 2 environment.\nThis section demonstrates some basic usage, as well as the features\nprovided by additional interfaces described above.\n\nFirst you need a site with some content and by default an ``IIntId``\nutility. This was created for us by the test setup which has provided\nus with an ``app`` an ``IIntId`` utility provided by the\n``five.intid`` package. Additionally, we need to create a\nrelationship container to use:\n\n >>> from plone.relations import tests\n >>> tests.setUp(app)\n\n >>> import transaction\n >>> from plone.relations import interfaces\n >>> from plone.relations.container import Z2RelationshipContainer\n >>> container = Z2RelationshipContainer()\n >>> from zope.interface.verify import verifyObject\n >>> verifyObject(interfaces.IComplexRelationshipContainer, container)\n True\n >>> app._setOb('references', container)\n >>> container.__name__ = 'references'\n >>> container.__parent__ = app\n >>> container = app['references']\n\n\nThis would generally be registered as a named local utility providing\nthe ``IComplexRelationshipContainer`` interface, but we will use it\ndirectly. Now we make some relationships, using the provided\n``Relationship`` class which implements ``IRelationship`` and has a\nbuilt-in adapter to IComplexRelationship. To properly illustrate the\npotential complexity of relationships we will use some characters and\ncontexts from the 1974 film _Chinatown_:\n\n >>> from plone.relations.tests import ChinatownSetUp\n >>> ChinatownSetUp(app) #creates our characters and contexts\n >>> from plone.relations.relationships import Z2Relationship as Relationship\n >>> rel1 = Relationship((app['noah'],), (app['evelyn'],), relation='parent')\n >>> verifyObject(interfaces.IRelationship, rel1)\n True\n >>> interfaces.IComplexRelationship(rel1).relation\n 'parent'\n >>> container.add(rel1)\n >>> rel2 = Relationship((app['hollis'],), (app['noah'],), relation='business-partner')\n >>> container.add(rel2)\n\nNote that there is a default adatper for IRelationship objects which\nprovides IComplexRelationship using a simple attribute on the\nrelationship.\n\nThen we add a relationship with a state, by directly applying the\ninterface and adding the attribute (which is not such a great way to\ndo this):\n\n >>> rel3 = Relationship((app['hollis'],), (app['evelyn'],), relation='intimate')\n >>> rel3.state = 'married'\n >>> from plone.relations.interfaces import IStatefulRelationship\n >>> from zope.interface import alsoProvides\n >>> alsoProvides(rel3, IStatefulRelationship)\n >>> container.add(rel3)\n\nWe currently have a simple tree::\n\n noah <---(business-partner)---\n | (parent) |\n v |\n evelyn <-(intimate:married)- hollis\n\nNow we can make queries against this simple data set, like finding\nobjects for which a another object is the source or target:\n\n >>> list(container.findTargets(source=app['hollis']))\n [, ]\n >>> list(container.findTargets(source=app['hollis'], relation='intimate'))\n []\n >>> list(container.findTargets(source=app['hollis'], relation='intimate', state='married'))\n []\n >>> list(container.findTargets(source=app['hollis'], relation='intimate', state='divorced'))\n []\n >>> list(container.findTargets(source=app['evelyn'], relation='parent'))\n []\n >>> list(container.findTargets(source=app['noah'], relation='parent'))\n []\n >>> list(container.findSources(target=app['evelyn']))\n [, ]\n >>> list(container.findSources(target=app['evelyn'], relation='parent'))\n []\n >>> list(container.findSources(target=app['evelyn'], relation='intimate'))\n []\n\n\nTransitivity\n------------\n\nWe can also generate a list of relationships, and even look\ntransitively at chains of relationships by specifying a maxDepth (and\noptionally a minDepth) for any of the queries. In particular the\nfindRelationships method will seek out chains of relationship matching\nthe specified parameters. Let's look at the ways that ``hollis`` and\n``evelyn`` are connected:\n\n >>> list(container.findRelationships(source=app['hollis'],\n ... target=app['evelyn'], maxDepth=2))\n [(,) to (,)>,), (,) to (,)>, ,) to (,)>)]\n\n``Hollis`` is ``evelyn's`` husband, and also her father's associate.\n\n\nModifying Relationships\n-----------------------\n\nThe above method also allows us to access existing relationships\ndirectly, which is especially helpful when we want to alter them. In\nthis case ``hollis`` has been _murdered_; so ``evelyn`` is now his\nwidow. We express this with a state change on the relationship, note that\nwe have to reindex the relationship after applying the state directly\nto it, if we had used an adapter to provide the state, then it should\nhave taken care of this for us when the attribute was set.:\n\n >>> relations = container.findRelationships(target=app['evelyn'], relation='intimate')\n >>> relations = list(relations)\n >>> relations\n [(,) to (,)>,)]\n >>> marriage = relations[0][0]\n >>> marriage.state = 'widowed'\n >>> container.reindex(marriage) # an adapter could handle this, as\n ... # we'll see later with context\n\nWe have changed the state of the marriage, let's ensure we can still\nfind it the same way we did before, but also using out new state:\n\n >>> list(container.findTargets(source=app['hollis'], relation='intimate'))\n []\n >>> list(container.findTargets(source=app['hollis'], relation='intimate', state='widowed'))\n []\n >>> list(container.findTargets(source=app['hollis'], relation='intimate', state='happy'))\n []\n\nNow let's add some more relationships, including one with an unknown\n``relation``. Here is the new relation tree::\n\n noah <----(business-partner)---\n | (parent) |\n v |\n evelyn <-(intimate:widowed)- hollis\n /\\\n (client)/ \\ (??)\n v v\n jake katherine\n\nand the associated code:\n\n >>> rel4 = Relationship((app['evelyn'],), (app['jake'],), relation='client')\n >>> rel5 = Relationship((app['evelyn'],), (app['katherine'],))\n >>> container.add(rel4)\n >>> container.add(rel5)\n\n\n >>> sorted([repr(r) for r in container.findTargets(source=app['evelyn'])])\n ['', '']\n >>> list(container.findTargets(source=app['evelyn'], relation=None))\n []\n >>> list(container.findTargets(source=app['noah'], relation=None))\n []\n\nNote that we can find entries with empty parameters using None as the\nquery argument.\n\n\nFinding if Objects Are Related\n-------------------------------\n\nWe can use maxDepth, like we did with the ``findRelationship``\nqueries, for any other query methods. A particularly useful one is\n``isLinked``, which determines if any matching relationship chains\nexist for a given query:\n\n >>> sorted([repr(r) for r in container.findTargets(source=app['noah'],\n ... maxDepth=2)])\n ['', '', '']\n >>> container.isLinked(source=app['noah'], target=app['jake'])\n False\n >>> container.isLinked(source=app['noah'], target=app['jake'], maxDepth=2)\n True\n >>> container.isLinked(source=app['noah'], target=app['katherine'],\n ... relation='parent', maxDepth=2)\n False\n\nSo, as far as we know, ``noah`` and ``katherine`` are not linked via\nparental relationships.\n\n\nContext\n--------\n\nNow we'll apply a context to an existing relationship using a simple\nadapter, in the real world this extra data would probably be stored\nusing an annotation on the relationship, but here we store it directly:\n\n >>> class ContextAdapter(object):\n ... def __init__(self, relationship):\n ... self.relationship = relationship\n ... def getContext(self):\n ... return getattr(self.relationship, '_context', None)\n ... def setContext(self, context):\n ... self.relationship._context = context\n ... #reindex ourself in the container\n ... if self.relationship.__parent__ is not None:\n ... self.relationship.__parent__.reindex(self.relationship)\n >>> from zope.component import provideAdapter\n >>> provideAdapter(ContextAdapter, (interfaces.IRelationship,), interfaces.IContextAwareRelationship)\n\nRight now the ``client`` relationship between ``evelyn`` and ``jake``\ndoesn't tell us much because there are potentially many different\ncontexts for a client relationship. In this case ``jake`` is a\nprivate investigator and the context is the ``investigation`` of\n``hollis'`` murder. This ``investigation`` object could consist of\nnotes pertaining to the investigation or other relevant data. We\napply it to the relationship as a context:\n\n >>> list(container.findSources(target=app['jake'], relation='client',\n ... context=app['investigation']))\n []\n >>> relationships = list(container.findRelationships(source=app['evelyn'],\n ... target=app['jake']))\n >>> relationships\n [(,) to (,)>,)]\n >>> evelyn_jake = relationships[0][0]\n >>> interfaces.IContextAwareRelationship(evelyn_jake).setContext(\n ... app['investigation'])\n >>> list(container.findSources(target=app['jake'], relation='client',\n ... context=app['investigation']))\n []\n >>> list(container.findSources(target=app['jake'], context=None))\n []\n >>> list(container.findSources(target=app['katherine'], context=None))\n []\n\n\nIn time some additional relationships develop. ``Jake`` and ``katherine``\nhave a fling during the investigation. Also, ``jake`` becomes suspicious\nof ``hollis'`` business partner and father-in-law ``noah``:\n\n >>> rel6 = Relationship((app['jake'],), (app['evelyn'],), 'intimate')\n >>> rel6.state = 'fling'\n >>> interfaces.IContextAwareRelationship(rel6).setContext(app['investigation'])\n >>> rel7 = Relationship((app['jake'],), (app['noah'],), 'nemesis')\n >>> interfaces.IContextAwareRelationship(rel7).setContext(app['investigation'])\n >>> container.add(rel6)\n >>> container.add(rel7)\n\n\nMultiple Relationship Chains and Cycles\n---------------------------------------\n\nWe've got a fairly complex graph, but an existing relationship becomes\na little clearer, when we learn katherine is evelyn's sister:\n\n >>> murky = list(container.findRelationships(source=app['evelyn'],\n ... target=app['katherine']))\n >>> evelyn_katherine = murky[0][0]\n >>> interfaces.IComplexRelationship(evelyn_katherine).relation = 'sibling'\n\nHere's the current relationship tree in ASCII form::\n\n (nemesis)---->noah <-----(business-partner)--\n [investigation]| | (parent) |\n | v |\n (intimate:fling)|--> evelyn <-(intimate:widowed)- hollis\n [investigation] | /\\\n |(client)\\\n [investigation]\\ (sibling)\n | / \\\n | v v\n jake katherine\n\nThis complexity will allow us to explore how the relationship query\nmechanisms resolve multiple relationship paths:\n\n >>> list(container.findTargets(source=app['jake'], context=app['investigation']))\n [, ]\n >>> list(container.findRelationships(context=app['investigation']))\n [(,) to (,)>,), (,) to (,)>,), (,) to (,)>,)]\n\nThe first findTargets example above shows all the people that are\n``jake's`` targets in the context of the investigation. Then we have\na map of all the relationships that apply in the context of the\ninvestigation.\n\nIn the end of the film we discover some rather sinister connections\nbetween these characters. ``Noah`` was ``hollis'`` murderer, and also\nhad an inappropriate intimate relationship with his daughter\n``evelyn`` which resulted in their daughter ``katherine``. We add\nthose relationships below (note how one can use multiple sources or\ntargets for a single relationship with ``noah`` and ``evelyn`` the\nsources for their parental relationship with ``katherine``)::\n\n noah-(intimate[the past])->evelyn\n |\\ /\n | \\ /\n | \\ /\n | \\ (parents) /\n | -->katherine<--\n (murderer)\n |\n hollis\n\nand the code:\n\n >>> rel8 = Relationship((app['noah'],), (app['evelyn'],), 'intimate')\n >>> interfaces.IContextAwareRelationship(rel8).setContext(app['the past'])\n >>> container.add(rel8)\n\n >>> rel9 = Relationship((app['noah'],), (app['hollis'],), 'murderer')\n >>> container.add(rel9)\n\n >>> rel10 = Relationship((app['evelyn'], app['noah']), (app['katherine'],),\n ... 'parent')\n >>> container.add(rel10)\n\nAt this point the relationship tree is far too complex and full of\nloops to draw understandably using ascii art. However, it's no trouble\nfor our relationship container to inspect it:\n\n >>> list(container.findSources(target=app['katherine'], relation='parent', maxDepth=None))\n [, ]\n >>> list(container.findRelationships(source=app['noah'],\n ... target=app['katherine'],\n ... relation='parent', maxDepth=None))\n [(, ) to (,)>,), (,) to (,)>, , ) to (,)>)]\n\nThis is the same query we tried earlier when we were unclear of\nrelation between ``katherine`` and ``noah``. Now we can see that\n``noah`` is both her father and grandfather (ick!).\n\nExploring the relationships pointing to ``katherine`` from ``evelyn``\nyields a pretty crazy picture, even when we restrict ourselves to\npaths of at most 2 relationships (we need to play some tricks to\nensure that the results are returned in a repeatable order, so that\nthis test passes):\n\n >>> relations = container.findRelationships(target=app['katherine'],\n ... maxDepth=2)\n >>> res = [repr(r) for r in relations]\n >>> res.sort(key=lambda x:(len(x), x)) # sort by length\n >>> print '\\n'.join(res)\n (,) to (,)>,)\n (, ) to (,)>,)\n (,) to (,)>, ,) to (,)>)\n (,) to (,)>, ,) to (,)>)\n (,) to (,)>, ,) to (,)>)\n (,) to (,)>, ,) to (,)>)\n (,) to (,)>, , ) to (,)>)\n (,) to (,)>, , ) to (,)>)\n (,) to (,)>, , ) to (,)>)\n (,) to (,)>, , ) to (,)>)\n (,) to (,)>, , ) to (,)>)\n (,) to (,)>, , ) to (,)>)\n\n\nThe relationships are as follows:\n\n evelyn \\|-(sibling)-> katherine\n evelyn+noah \\|-(parent)-> katherine\n noah \\|-(parent)-> evelyn \\|-(sibling)-> katherine\n jake \\|-(intimate)-> evelyn \\|-(sibling)-> katherine\n noah \\|-(intimate)-> evelyn \\|-(sibling)-> katherine\n hollis \\|-(intimate)-> evelyn \\|-(sibling)-> katherine\n jake \\|-(nemesis)-> noah \\|-(parent)-> katherine\n noah \\|-(parent)-> evelyn \\|-(parent)-> katherine\n jake \\|-(intimate)-> evelyn \\|-(parent)-> katherine\n noah \\|-(intimate)-> evelyn \\|-(parent)-> katherine\n hollis \\|-(intimate)-> evelyn \\|-(parent)-> katherine\n hollis \\|-(business-partner)-> noah \\|-(parent)-> katherine\n\n\nIt's important to note that nothing explodes when a cycle is found.\nThe result in such a case is just a special tuple that implements\nICircularRelationshipPath. We can see this by looking at the simplest\ncycles between ``evelyn`` and herself:\n\n >>> list(container.findRelationships(source=app['evelyn'],\n ... target=app['evelyn'], maxDepth=2))\n [cycle(,) to (,)>, ,) to (,)>)]\n\n\nAcquisition Nonsense\n--------------------\n\nZope 2 requires almost every object to support acquisition in order to\nfunction (it is required for security and traversal). Below we will perform\nsome sanity checks to ensure that the objects involved are wrapped in ways\nthat meet Zope 2's expectations:\n\n >>> list(container.findSources(target=app['katherine']))[0].aq_chain\n [, , ]\n >>> list(container.findTargets(source=app['hollis'],\n ... relation='business-partner'))[0].aq_chain\n [, , ]\n >>> list(list(container.findRelationships(source=app['evelyn'],\n ... target=app['katherine']))[0][0].targets)[0].aq_chain\n [, , ]\n >>> list(list(container.findRelationships(source=app['evelyn'],\n ... target=app['katherine']))[0][0].sources)[0].aq_chain\n [, , ]\n\nAs you can see, even as returned from the search the targets and\nsources have their original wrapping. Relationships are not wrapped,\nthough they can be explicitly wrapped with some available context when\nsecurity checks are needed. The ``sources`` and ``targets``\nattributes of a returned relationship will to have their original\nwrapping as well, even after ghosting:\n\n >>> evelyn = list(container.findSources(target=app['katherine']))[0]\n >>> noah = list(container.findTargets(source=app['hollis'],\n ... relation='business-partner'))[0]\n >>> rel = list(container.findRelationships(source=app['evelyn'],\n ... target=app['katherine']))[0][0]\n >>> sp = transaction.savepoint()\n >>> evelyn._p_deactivate()\n >>> noah._p_deactivate()\n >>> for _rel in container.values():\n ... _rel._p_deactivate()\n ... _rel.targets._p_deactivate()\n ... _rel.sources._p_deactivate()\n >>> container._p_deactivate()\n >>> list(rel.targets)[0].aq_chain\n [, , ]\n >>> list(container.findSources(target=app['katherine']))[0].aq_chain\n [, , ]\n >>> list(container.findTargets(source=app['hollis'],\n ... relation='business-partner'))[0].aq_chain\n [, , ]\n >>> list(list(container.findRelationships(source=app['evelyn'],\n ... target=app['katherine']))[0][0].targets)[0].aq_chain\n [, , ]\n\nAll of the wrappers are preserved except those on the ``sources`` and\n``targets``, which for this reason mostly shouldn't be directly\ndepended on (at least not from code that requires security checks or\nacquisition).\n\nWhat happens when we create a relationship to an explicitly rewrapped object:\n\n >>> rel = Relationship((app['katherine'],),(app['jake'].__of__(container),))\n >>> container.add(rel)\n >>> list(rel.targets)[0].aq_chain\n [, , ]\n >>> list(container.findTargets(source=app['katherine'],\n ... relation=None))[0].aq_chain\n [, , ]\n\nThe retrieval via search returns the object only wrapped by its\noriginal containment, regardless of how it was wrapped when used in\nthe relationship. When we retrieve the relationship, the original wrapping\nof ``sources`` and ``targets`` will be restored.\n\n >>> sp = transaction.savepoint()\n >>> rel._p_deactivate()\n >>> rel.sources._p_deactivate()\n >>> rel.targets._p_deactivate()\n >>> list(list(container.findRelationships(source=app['katherine'],\n ... relation=None))[0][0].targets)[0].aq_chain\n [, , ]\n\n\n >>> tests.tearDown()", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://pypi.python.org/pypi/plone.relations", "keywords": "references relationships plone", "license": "GPL with container.txt covered by ZPL and owned by Zope Corp.", "maintainer": null, "maintainer_email": null, "name": "plone.relations", "package_url": "https://pypi.org/project/plone.relations/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/plone.relations/", "project_urls": { "Download": "UNKNOWN", "Homepage": "http://pypi.python.org/pypi/plone.relations" }, "release_url": "https://pypi.org/project/plone.relations/2.0/", "requires_dist": null, "requires_python": null, "summary": "Tools for defining and querying complex relationships between objects", "version": "2.0" }, "last_serial": 661249, "releases": { "1.0b1": [ { "comment_text": "", "digests": { "md5": "b01339917a5a617b6bf82c6175a9af49", "sha256": "3287cd47fccf4de5fa47d164e4ed430912d456d796f7f5310da64ee0b48d6de0" }, "downloads": -1, "filename": "plone.relations-1.0b1-py2.4.egg", "has_sig": false, "md5_digest": "b01339917a5a617b6bf82c6175a9af49", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 95986, "upload_time": "2007-10-04T03:56:09", "url": "https://files.pythonhosted.org/packages/fb/e3/b66b40829839c808c40e340a71cf4550e04387df06c9c94e85f2813f205e/plone.relations-1.0b1-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "c7e66f828ad54c39b975e1e4046a5a22", "sha256": "b16b8b6799eb7a63e0293dcf8f5e1a697db7bfe289a9a395e22095595c9c95c1" }, "downloads": -1, "filename": "plone.relations-1.0b1.tar.gz", "has_sig": false, "md5_digest": "c7e66f828ad54c39b975e1e4046a5a22", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 57769, "upload_time": "2007-10-04T03:56:06", "url": "https://files.pythonhosted.org/packages/c2/e8/8b67b9446ccd61641e2a65a8a842c9a09cd0a27991a2370d42ed4c15b517/plone.relations-1.0b1.tar.gz" } ], "1.0b2": [ { "comment_text": "", "digests": { "md5": "6a3e20c6cf2c08a530aaa66087d9d677", "sha256": "fc6c354af490377719a49116b1dee543fb4270f95b98e99ac1e01879aa474f3c" }, "downloads": -1, "filename": "plone.relations-1.0b2-py2.4.egg", "has_sig": false, "md5_digest": "6a3e20c6cf2c08a530aaa66087d9d677", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 96001, "upload_time": "2007-10-05T00:42:47", "url": "https://files.pythonhosted.org/packages/ea/c5/7ea0c5c26df2ee5d8644d3e28f6ad34b4e1e73d7b58b6712f9ebe21d1575/plone.relations-1.0b2-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "17a6c34e9d08b23918e365421e7dcdcb", "sha256": "cabf0124a1673b2b25c70bc8207c912ddce1b5a94844d78b5435ca3f0c9dcdb1" }, "downloads": -1, "filename": "plone.relations-1.0b2.tar.gz", "has_sig": false, "md5_digest": "17a6c34e9d08b23918e365421e7dcdcb", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 57788, "upload_time": "2007-10-05T00:42:44", "url": "https://files.pythonhosted.org/packages/96/cc/e2d77caffac4a7a4919ef6e69ae572d146727ce69e7738202d6ec06be017/plone.relations-1.0b2.tar.gz" } ], "1.0b3": [ { "comment_text": "", "digests": { "md5": "ec46805ea99b1b424e90e0abdee55241", "sha256": "6af4ebf887c364e9f75745249710281705b022a58f8cbbc6b0fee2f89316d892" }, "downloads": -1, "filename": "plone.relations-1.0b3-py2.4.egg", "has_sig": false, "md5_digest": "ec46805ea99b1b424e90e0abdee55241", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 97839, "upload_time": "2007-10-14T12:10:15", "url": "https://files.pythonhosted.org/packages/8a/a0/5bd3fb042c3b30504ee8d5493cc0dd1ee4b2bff67609bf5999e8c1c4dba5/plone.relations-1.0b3-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "7ced6f5a2d95546b373ccf8bb8a45c65", "sha256": "be6c91292680cb11702fefad2e104fcf28c5214721bd035374ea9713b8f1afdc" }, "downloads": -1, "filename": "plone.relations-1.0b3.tar.gz", "has_sig": false, "md5_digest": "7ced6f5a2d95546b373ccf8bb8a45c65", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41755, "upload_time": "2007-10-14T12:10:14", "url": "https://files.pythonhosted.org/packages/4f/05/7f8c9fdda4664857abcedb682871f212893de21220231b7a77f1be2f824c/plone.relations-1.0b3.tar.gz" } ], "1.0b4": [ { "comment_text": "", "digests": { "md5": "a941555ed908f6b9b4de749eb74748ce", "sha256": "f3dbe26cb698649c0bc9b8fbd88935a3466b96696eb295863e0bd76e6e52f5af" }, "downloads": -1, "filename": "plone.relations-1.0b4-py2.4.egg", "has_sig": false, "md5_digest": "a941555ed908f6b9b4de749eb74748ce", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 97839, "upload_time": "2007-10-14T14:40:46", "url": "https://files.pythonhosted.org/packages/1a/e4/06f90d66981ef9034db4139feafd09501edc41a5a123344d3990b2cc8010/plone.relations-1.0b4-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "e91e9241c0f3b463acd6338adeef9115", "sha256": "e37b682411de5fea3f9d828bcbafc1dbf217e0ef688c2cecd41217dbdfe75926" }, "downloads": -1, "filename": "plone.relations-1.0b4.tar.gz", "has_sig": false, "md5_digest": "e91e9241c0f3b463acd6338adeef9115", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41756, "upload_time": "2007-10-14T14:40:46", "url": "https://files.pythonhosted.org/packages/47/82/f883240e409ff118acab952b6f7bc2d77c7a2c1bef2760d52b9d2f9a0421/plone.relations-1.0b4.tar.gz" } ], "1.0b5": [ { "comment_text": "", "digests": { "md5": "8886b6f9972816282a6dbde8068897d2", "sha256": "af7da9f810ec242f3981cfb8effdbbe23a279b3166f76260bb49b7fef9a07f74" }, "downloads": -1, "filename": "plone.relations-1.0b5-py2.4.egg", "has_sig": false, "md5_digest": "8886b6f9972816282a6dbde8068897d2", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 46426, "upload_time": "2008-05-10T18:45:57", "url": "https://files.pythonhosted.org/packages/00/51/f21aa3c866303c0eaefab6c352119aa6c448c9b76d1947a5ed78f88f5dce/plone.relations-1.0b5-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "5b8c3cc1556da41c16b28cfbbaa4885c", "sha256": "44a18dae268fdef409f2fe1420a4cb7eb50200c5cbcc5e47f27d47173bf694a0" }, "downloads": -1, "filename": "plone.relations-1.0b5.tar.gz", "has_sig": false, "md5_digest": "5b8c3cc1556da41c16b28cfbbaa4885c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 41574, "upload_time": "2008-05-10T18:45:56", "url": "https://files.pythonhosted.org/packages/a4/26/2fb4961a081befa2cbe6c93ef08e09cb273d802db0f6820407d3b313c9b6/plone.relations-1.0b5.tar.gz" } ], "1.0rc1": [ { "comment_text": "", "digests": { "md5": "be7b2d5d98e56303d90527f1f2c0183f", "sha256": "a06e65e7f717506a6bae7b7e6c9f6384bbe06a7e867ca21bd76bb373b6357da1" }, "downloads": -1, "filename": "plone.relations-1.0rc1.zip", "has_sig": true, "md5_digest": "be7b2d5d98e56303d90527f1f2c0183f", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 49817, "upload_time": "2008-11-26T14:23:39", "url": "https://files.pythonhosted.org/packages/5a/68/f5b6e058289d2452ecd62f4bd65768ac2a5514d861de409f362fb210ae54/plone.relations-1.0rc1.zip" } ], "1.0rc3": [ { "comment_text": "", "digests": { "md5": "05d4dc37c9d09a2df4dad120f72b838a", "sha256": "d00291173ba99b9142a26ab65d211fdb929df0a2395cd09480a977f1ac466d21" }, "downloads": -1, "filename": "plone.relations-1.0rc3-py2.4.egg", "has_sig": false, "md5_digest": "05d4dc37c9d09a2df4dad120f72b838a", "packagetype": "bdist_egg", "python_version": "2.4", "requires_python": null, "size": 46597, "upload_time": "2008-12-06T02:47:21", "url": "https://files.pythonhosted.org/packages/d8/ef/4886613ff5762378f92fbbe4051a9ac8fe8955391470b0285e17fff248f4/plone.relations-1.0rc3-py2.4.egg" }, { "comment_text": "", "digests": { "md5": "b4b0238a2cedf23bf6a7f829910e13ac", "sha256": "9ce6b292c3a107c9286861e37eecaac0f12e66090a0467a3f8d8af444a70c20b" }, "downloads": -1, "filename": "plone.relations-1.0rc3.tar.gz", "has_sig": false, "md5_digest": "b4b0238a2cedf23bf6a7f829910e13ac", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 42431, "upload_time": "2008-12-06T02:47:19", "url": "https://files.pythonhosted.org/packages/fb/e7/6c5488d2430cc262e22c2c329894c87a09f96a668c1b16cff8c8926c6f87/plone.relations-1.0rc3.tar.gz" } ], "2.0": [ { "comment_text": "", "digests": { "md5": "0a422b90480d687f94d08f7ecbe78fe3", "sha256": "4115329bbca96411cd149a11b00820ad6477d8e39b7e9e52a4421a3566bba340" }, "downloads": -1, "filename": "plone.relations-2.0.zip", "has_sig": false, "md5_digest": "0a422b90480d687f94d08f7ecbe78fe3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50066, "upload_time": "2011-10-06T16:10:47", "url": "https://files.pythonhosted.org/packages/74/3d/68fdee0c2775cb360b01ca1753ebbae70a138f67edc9705ef22a93cb0f30/plone.relations-2.0.zip" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "0a422b90480d687f94d08f7ecbe78fe3", "sha256": "4115329bbca96411cd149a11b00820ad6477d8e39b7e9e52a4421a3566bba340" }, "downloads": -1, "filename": "plone.relations-2.0.zip", "has_sig": false, "md5_digest": "0a422b90480d687f94d08f7ecbe78fe3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 50066, "upload_time": "2011-10-06T16:10:47", "url": "https://files.pythonhosted.org/packages/74/3d/68fdee0c2775cb360b01ca1753ebbae70a138f67edc9705ef22a93cb0f30/plone.relations-2.0.zip" } ] }