Overview
========

    >>> import datetime

Datasets provide a versioning system based on a version ID to group certain
objects and sort them based on the effectiveDate (and creationDate, if first
is None) to determine their version number.

Lets use the sandbox.

    >>> portal = layer['portal']
    >>> _ = portal.invokeFactory('Folder', 'sandbox')
    >>> sandbox = portal._getOb('sandbox')

    >>> from plone.app.testing import TEST_USER_ID
    >>> from plone.app.testing import setRoles
    >>> setRoles(portal, TEST_USER_ID, ['Manager'])

Lets add a Sample Data.

    >>> _ = sandbox.invokeFactory('Sample Data', 'data1')
    >>> data1 = sandbox._getOb('data1')
    >>> form = {
    ...   'title': 'Dataset',
    ...   'description': 'Organisation description 1',
    ...	  'somedata':'Some Data 1',
    ... }
    >>> data1.setEffectiveDate(datetime.datetime.now())
    >>> _ = data1.invokeFactory("Document", 'c1')
    >>> data1['c1'].setTitle("Child")
    >>> print list(data1.objectIds())
    ['c1']

We'll also test relatedItems relationships, so let's add a helper content
object.

    >>> _ = sandbox.invokeFactory("Document", 'r1')
    >>> data1.setRelatedItems([sandbox['r1']])
    >>> print data1.getRelatedItems()
    [<ATDocument at /plone/sandbox/r1>]


How to get the versionId
------------------------
All versionable (IVersionEnhanced) objects have a versionId by default. This is
stored in an annotation, and by default is a random string of 10 characters.

    >>> from eea.versions.versions import VERSION_ID
    >>> vid = data1.__annotations__[VERSION_ID]
    >>> len(vid) == 10
    True

The recommended way to get the versionId is using the IGetVersions adapter:

    >>> from eea.versions.interfaces import IGetVersions
    >>> IGetVersions(data1).versionId == vid
    True

There's also a view that can be used:

    >>> view = data1.unrestrictedTraverse('@@getVersions')
    >>> view.versionId == vid
    True

You can manually assign a new version using IVersionControl:

    >>> from eea.versions.interfaces import IVersionControl
    >>> IVersionControl(data1).setVersionId("1234567890")
    >>> print IGetVersions(data1).versionId
    1234567890


Versions keep the original data
-------------------------------
    >>> data1.processForm(values=form, data=1, metadata=1)

Now lets create a new version of the above dataset.

    >>> createVersionView = data1.unrestrictedTraverse('@@createVersion')
    >>> vertmp = createVersionView()
    >>> id = vertmp[vertmp.rfind('/')+1:]
    >>> dataVer = sandbox._getOb(id)

The id of the dataVer is based on the old id, but with a number suffix:

    >>> dataVer.id == 'data1-1'
    True

Verify if properties were copied on the new version.

    >>> dataVer.Title() == data1.Title()
    True
    >>> dataVer.getSomedata() == data1.getSomedata()
    True

Effective date of the new version should be set to None, while the original
keeps its effective date:

    >>> dataVer.getEffectiveDate() == None
    True
    >>> data1.getEffectiveDate() != None
    True

Both objects should have the same version ID.

    >>> from eea.versions.interfaces import IVersionControl
    >>> IVersionControl(dataVer).getVersionId() == IVersionControl(data1).getVersionId()
    True

Children objects which where present in the original are also copied:

    >>> print dataVer['c1'].absolute_url()
    http://nohost/plone/sandbox/data1-1/c1

Relationships are also copied:

    >>> print dataVer.getRelatedItems()
    [<ATDocument at /plone/sandbox/r1>]


The IGetVersions API
--------------------
    >>> from eea.versions.interfaces import IGetVersions
    >>> from eea.versions.versions import create_version
    >>> dataVer1 = create_version(dataVer)
    >>> adapter = IGetVersions(dataVer)

Calling the adapter returns the enumerated versions, to mimic the old API:

    >>> print adapter()
    {1: <SampleData at /plone/sandbox/data1>, 2: <SampleData at /plone/sandbox/data1-1>, 3: <SampleData at /plone/sandbox/data1-2>}
    >>> print adapter.enumerate_versions()
    {1: <SampleData at /plone/sandbox/data1>, 2: <SampleData at /plone/sandbox/data1-1>, 3: <SampleData at /plone/sandbox/data1-2>}

The adapter is bound to the middle of the three versions:

    >>> print adapter.earlier_versions()
    [{'url': 'http://nohost/plone/sandbox/data1', 'date': DateTime('...'), 'review_state': 'visible', 'title_state': 'Public draft', 'title': 'Dataset'}]
    >>> print adapter.later_versions()
    [{'url': 'http://nohost/plone/sandbox/data1-2', 'date': DateTime('...'), 'review_state': 'visible', 'title_state': 'Public draft', 'title': 'Dataset'}]
    >>> print adapter.isLatest()
    False
    >>> print adapter.first_version()
    <SampleData at data1>
    >>> print adapter.latest_version()
    <SampleData at data1-2>
    >>> print adapter.version_number()
    2
    >>> print adapter.versions()
    [<SampleData at /plone/sandbox/data1>, <SampleData at /plone/sandbox/data1-1>, <SampleData at /plone/sandbox/data1-2>]
    >>> print adapter.getLatestVersionUrl()
    http://nohost/plone/sandbox/data1-2

The last created version is indeed the latest version:

    >>> adapter = IGetVersions(dataVer1)
    >>> adapter.isLatest()
    True


Anonymous users and the IGetVersions API
----------------------------------------
Anonymous users will only see as versions the objects that are published:

    >>> wftool = adapter.wftool()
    >>> wftool.doActionFor(dataVer, 'publish')
    >>> from plone.app.testing import logout
    >>> logout()

Since the original object is not published dataVer will be marked as the first
version number:

    >>> adapter = IGetVersions(dataVer)
    >>> print adapter.version_number()
    1

And calling earlier_versions will return no results:

    >>> print adapter.earlier_versions()
    []

As such the first version will return the SampleData object that is referenced
by our adapter:

    >>> print adapter.first_version()
    <SampleData at data1-1>

And if dataVar1 is published it will be the latest version:

    >>> from plone.app.testing import login
    >>> from plone.app.testing import TEST_USER_NAME
    >>> login(portal, TEST_USER_NAME)
    >>> wftool.doActionFor(dataVer1, 'publish')
    >>> adapter = IGetVersions(dataVer)
    >>> print adapter.latest_version() == dataVer1
    True


How new ids are calculated
--------------------------
We use a custom naming scheme to calculate new ids when versioning:

    >>> from eea.versions.versions import generateNewId
    >>> print sorted(sandbox.objectIds())
    ['data1', 'data1-1', 'data1-2', 'r1']

Names will be generated in numeric sequences, taking into consideration
what names are available in the given location:

    >>> print generateNewId(sandbox, 'data1')
    data1-3
    >>> print generateNewId(sandbox, 'data2')
    data2
    >>> del sandbox['data1-2']
    >>> print generateNewId(sandbox, 'data1')
    data1-2


Talkbacks
---------
When versioning, the discussion items should be removed and unindexed:

    >>> from Products.CMFCore.utils import getToolByName
    >>> hasNewDiscussion = True
    >>> try:
    ...     from plone.app.discussion.interfaces import IConversation
    ...     from zope.component import createObject
    ... except:
    ...     hasNewDiscussion = False

Latest versions of CMFCore queue and process indexing operations (index, reindex, unindex) at transaction boundaries.
But this isn't well covered within tests, thus we have to manually call processQueue.
See https://community.plone.org/t/strange-catalog-behavior-on-plone-5-1/4582/3

    >>> try:
    ...     from Products.CMFCore.indexing import processQueue
    ... except ImportError:
    ...     processQueue = lambda: 1

    >>> catalog = getToolByName(data1, "portal_catalog")
    >>> dtool = getToolByName(data1, "portal_discussion")
    >>> data1.allow_discussion = True
    >>> if hasNewDiscussion:
    ...     conversation = IConversation(data1)
    ...     comment = createObject('plone.Comment')
    ...     comment.title = 'title'
    ...     comment.text = 'text'
    ...     comment_id = conversation.addComment(comment)
    ...     _ = processQueue()
    ...     print len(catalog.searchResults(path="/".join(data1.getPhysicalPath()) + "/++conversation++default/"))
    ... else:
    ...     tb = dtool.getDiscussionFor(data1)
    ...     id = tb.createReply('title', 'text')
    ...     _ = processQueue()
    ...     print len(catalog.searchResults(path="/".join(data1.getPhysicalPath()) + "/talkback/"))
    1
    >>> createVersionView = data1.unrestrictedTraverse('@@createVersion')
    >>> vertmp = createVersionView()
    >>> id = vertmp[vertmp.rfind('/')+1:]
    >>> dataVer = sandbox._getOb(id)
    >>> if hasNewDiscussion:
    ...     conversationVer = IConversation(dataVer)
    ...     print conversationVer.total_comments
    ... else:
    ...     tb = dtool.getDiscussionFor(dataVer)
    ...     print tb.replyCount(dataVer)
    0
    >>> if hasNewDiscussion:
    ...     print len(dataVer.portal_catalog.searchResults(path="/".join(dataVer.getPhysicalPath()) + "/++conversation++default/"))
    ... else:
    ...     print len(dataVer.portal_catalog.searchResults(path="/".join(dataVer.getPhysicalPath()) + "/talkback/"))
    0
