Working Copy#

Note

This feature is available only on Plone 5 or greater.

Plone has a working copy feature provided by the core package plone.app.iterate. It allows the users to create a working copy of a published or live content object, and work with it until it is ready to be published without having to edit the original object.

This process has several steps in its life cycle.

Create working copy (a.k.a., check-out)#

The user initiates the process and creates a working copy by checking out the content:

http

POST /plone/document/@workingcopy HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X POST http://nohost/plone/document/@workingcopy -H "Accept: application/json" --user admin:secret

httpie

http POST http://nohost/plone/document/@workingcopy Accept:application/json -a admin:secret

python-requests

requests.post('http://nohost/plone/document/@workingcopy', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))

…and receives the response:

HTTP/1.1 201 Created
Content-Type: application/json
Location: http://localhost:55001/plone/document

{
    "@id": "http://localhost:55001/plone/copy_of_document"
}

Get the working copy#

A working copy has been created and can be accessed querying the content:

http

GET /plone/document/@workingcopy HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X GET http://nohost/plone/document/@workingcopy -H "Accept: application/json" --user admin:secret

httpie

http http://nohost/plone/document/@workingcopy Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/document/@workingcopy', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))

…and receives the response:

HTTP/1.1 200 OK
Content-Type: application/json

{
    "working_copy": {
        "@id": "http://localhost:55001/plone/copy_of_document",
        "created": "1995-07-31T13:45:00+00:00",
        "creator_name": "admin",
        "creator_url": "http://localhost:55001/plone/author/admin",
        "title": "Test document"
    },
    "working_copy_of": null
}

The GET content of any object also states the location of the working copy, if any, as working_copy:

http

GET /plone/document HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X GET http://nohost/plone/document -H "Accept: application/json" --user admin:secret

httpie

http http://nohost/plone/document Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/document', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))
HTTP/1.1 200 OK
Content-Type: application/json

{
    "@components": {
        "actions": {
            "@id": "http://localhost:55001/plone/document/@actions"
        },
        "aliases": {
            "@id": "http://localhost:55001/plone/document/@aliases"
        },
        "breadcrumbs": {
            "@id": "http://localhost:55001/plone/document/@breadcrumbs"
        },
        "contextnavigation": {
            "@id": "http://localhost:55001/plone/document/@contextnavigation"
        },
        "navigation": {
            "@id": "http://localhost:55001/plone/document/@navigation"
        },
        "navroot": {
            "@id": "http://localhost:55001/plone/document/@navroot"
        },
        "types": {
            "@id": "http://localhost:55001/plone/document/@types"
        },
        "workflow": {
            "@id": "http://localhost:55001/plone/document/@workflow"
        }
    },
    "@id": "http://localhost:55001/plone/document",
    "@type": "Document",
    "UID": "SomeUUID000000000000000000000001",
    "allow_discussion": false,
    "contributors": [],
    "created": "1995-07-31T13:45:00+00:00",
    "creators": [
        "test_user_1_"
    ],
    "description": "",
    "effective": null,
    "exclude_from_nav": false,
    "expires": null,
    "id": "document",
    "is_folderish": false,
    "language": "",
    "layout": "document_view",
    "lock": {
        "created": "1995-07-31T17:30:00+00:00",
        "creator": "admin",
        "creator_name": "admin",
        "creator_url": "http://localhost:55001/plone/author/admin",
        "locked": true,
        "name": "iterate.lock",
        "stealable": false,
        "time": 807211800.0,
        "timeout": 4294967280,
        "token": "0.12345678901234567-0.98765432109876543-00105A989226:1630609830.249"
    },
    "modified": "1995-07-31T17:30:00+00:00",
    "next_item": {
        "@id": "http://localhost:55001/plone/copy_of_document",
        "@type": "Document",
        "description": "",
        "title": "Test document",
        "type_title": "Page"
    },
    "parent": {
        "@id": "http://localhost:55001/plone",
        "@type": "Plone Site",
        "description": "",
        "title": "Plone site",
        "type_title": "Plone Site"
    },
    "previous_item": {},
    "relatedItems": [],
    "review_state": "private",
    "rights": "",
    "subjects": [],
    "table_of_contents": null,
    "text": null,
    "title": "Test document",
    "type_title": "Page",
    "version": "current",
    "working_copy": {
        "@id": "http://localhost:55001/plone/copy_of_document",
        "created": "1995-07-31T13:45:00+00:00",
        "creator_name": "admin",
        "creator_url": "http://localhost:55001/plone/author/admin",
        "title": "Test document"
    },
    "working_copy_of": null
}

The GET content of any a working copy also returns the original as working_copy_of:

http

GET /plone/copy_of_document HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X GET http://nohost/plone/copy_of_document -H "Accept: application/json" --user admin:secret

httpie

http http://nohost/plone/copy_of_document Accept:application/json -a admin:secret

python-requests

requests.get('http://nohost/plone/copy_of_document', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))
HTTP/1.1 200 OK
Content-Type: application/json

{
    "@components": {
        "actions": {
            "@id": "http://localhost:55001/plone/copy_of_document/@actions"
        },
        "aliases": {
            "@id": "http://localhost:55001/plone/copy_of_document/@aliases"
        },
        "breadcrumbs": {
            "@id": "http://localhost:55001/plone/copy_of_document/@breadcrumbs"
        },
        "contextnavigation": {
            "@id": "http://localhost:55001/plone/copy_of_document/@contextnavigation"
        },
        "navigation": {
            "@id": "http://localhost:55001/plone/copy_of_document/@navigation"
        },
        "navroot": {
            "@id": "http://localhost:55001/plone/copy_of_document/@navroot"
        },
        "types": {
            "@id": "http://localhost:55001/plone/copy_of_document/@types"
        },
        "workflow": {
            "@id": "http://localhost:55001/plone/copy_of_document/@workflow"
        }
    },
    "@id": "http://localhost:55001/plone/copy_of_document",
    "@type": "Document",
    "UID": "SomeUUID000000000000000000000002",
    "allow_discussion": false,
    "contributors": [],
    "created": "1995-07-31T13:45:00+00:00",
    "creators": [
        "test_user_1_"
    ],
    "description": "",
    "effective": null,
    "exclude_from_nav": false,
    "expires": null,
    "id": "copy_of_document",
    "is_folderish": false,
    "language": "",
    "layout": "document_view",
    "lock": {
        "locked": false,
        "stealable": true
    },
    "modified": "1995-07-31T17:30:00+00:00",
    "next_item": {},
    "parent": {
        "@id": "http://localhost:55001/plone",
        "@type": "Plone Site",
        "description": "",
        "title": "Plone site",
        "type_title": "Plone Site"
    },
    "previous_item": {
        "@id": "http://localhost:55001/plone/document",
        "@type": "Document",
        "description": "",
        "title": "Test document",
        "type_title": "Page"
    },
    "relatedItems": [],
    "review_state": "private",
    "rights": "",
    "subjects": [],
    "table_of_contents": null,
    "text": null,
    "title": "Test document",
    "type_title": "Page",
    "version": "current",
    "working_copy": {
        "@id": "http://localhost:55001/plone/copy_of_document",
        "created": "1995-07-31T13:45:00+00:00",
        "creator_name": "admin",
        "creator_url": "http://localhost:55001/plone/author/admin",
        "title": "Test document"
    },
    "working_copy_of": {
        "@id": "http://localhost:55001/plone/document",
        "title": "Test document"
    }
}

Check-in#

Once the user has finished editing the working copy and wants to update the original with the changes, they would check in the working copy:

http

PATCH /plone/copy_of_document/@workingcopy HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X PATCH http://nohost/plone/copy_of_document/@workingcopy -H "Accept: application/json" --user admin:secret

httpie

http PATCH http://nohost/plone/copy_of_document/@workingcopy Accept:application/json -a admin:secret

python-requests

requests.patch('http://nohost/plone/copy_of_document/@workingcopy', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))

…and receives the response:

HTTP/1.1 204 No Content

The working copy is deleted afterwards as a result of this process. The PATCH can also be issued in the original (baseline) object.

Delete the working copy (cancel check-out)#

If you want to cancel the check-out and delete the working copy (in both the original and the working copy):

http

DELETE /plone/copy_of_document/@workingcopy HTTP/1.1
Accept: application/json
Authorization: Basic YWRtaW46c2VjcmV0

curl

curl -i -X DELETE http://nohost/plone/copy_of_document/@workingcopy -H "Accept: application/json" --user admin:secret

httpie

http DELETE http://nohost/plone/copy_of_document/@workingcopy Accept:application/json -a admin:secret

python-requests

requests.delete('http://nohost/plone/copy_of_document/@workingcopy', headers={'Accept': 'application/json'}, auth=('admin', 'secret'))

and receives the response:

HTTP/1.1 204 No Content

When a working copy is deleted using the normal DELETE action, it also deletes the relation and cancels the check-out. That is handled by plone.app.iterate internals.