Plone Rest API - no way to set a default view/page

It seems that there is no way to set a default page on a folder using the Plone REST API.
See issue here: Provide default_page and layout properties in content GET · Issue #411 · plone/plone.restapi · GitHub

Is there an existing solution that provides a way to set the default page via the Plone REST API? Maybe an add-on that provides the endpoint to set the default page? I want to be sure before I go and register a new api endpoint for my project to support setting the default page via a REST API call.

Background
I'm using the REST API to automate maintenance tasks on a Plone classic site. My mode of operation has been to make the changes without being "invasive" (generally, not creating or installing additional add-ons to achieve things).

There may be a better, more elegant approach. I just want to rule out other options before I create or modify an add-on to get the endpoint for managing the default view.

There is not a direct way to do this with the RESTAPI.

But (as a workaround within the RESTAPI) you could rename the id of the default page to index_html (if this is possible in your use case).

import requests

SITE_URL = "https://6-classic.demo.plone.org"
auth = ("manager", "plonemanager")
headers = {"Accept": "application/json", "Content-Type": "application/json"}

# create a folder
response_folder = requests.post(SITE_URL + '/en', 
                  json={'@type': 'Folder', 'title': 'MyFolder'},
                  headers=headers, auth=auth)

# get the @id of the created folder
folder_at_id = response_folder.json()['@id']

# create a few pages in the folder
for i in range(1, 4):
    requests.post(folder_at_id,
                  json={'@type': 'Document', 'title': f'MyDocument_{i}'},
                  headers=headers, auth=auth)

# set document 1 as default page
requests.patch(folder_at_id + '/mydocument_1',
               json={'id': 'index_html'}, headers=headers, auth=auth)

# unset document 1 as default page
requests.patch(folder_at_id + '/index_html',
               json={'id': 'mydocument_1'}, headers=headers, auth=auth)

# set document 2 as default page
requests.patch(folder_at_id + '/mydocument_2',
               json={'id': 'index_html'}, headers=headers, auth=auth)

Thanks @mekell, interesting approach. I'm aware that setting the shortname as index_html causes Plone to treat an item as the default view. That approach will likely work for newly created folders, the problem is that the folders I'm working with already have a default view set. I'm not sure that document named index_html will resolve that.

plone.restapi is an open source product driven by the Plone community. You are a part of the Plone community, your use case is as valid as any other, so, if you find plone.restapi is missing something, try to see if you can make a contribution and get that gap filled.

Default pages in Plone are really very tricky. This is because a "default page" is not always a "default page": A "default page" can actually be very different things: For instance index_html pages are treated as default pages but they are not stored in the default_page property of the parent folder. "Real" default pages are stored in the default_page property (see get_default_page()).

# Docstring from plone.base.defaultpage.get_default_page():

Given a folderish item, find out if it has a default-page using the following lookup rules:

  1. A content object called 'index_html' wins
  2. Else check for IBrowserDefault, either if the container implements it or if an adapter exists. In both cases fetch its FTI and either take it if it implements IDynamicViewTypeInformation or adapt it to IDynamicViewTypeInformation. call getDefaultPage on the implementer and take value if given.
  3. Else, look up the attribute default_page on the object, without acquisition in place
    3.1 look for a content in the container with the id, no acquisition!
    3.2 look for a content at portal, with acquisition
  4. Else, look up the property default_page in site_properties for magic ids and test these

The id of the first matching item is then used to lookup a translation and if found, its id is returned. If no default page is set, None is returned. If a non-folderish item is passed in, return None always.

Nonetheless, depending on your use case, there are some ways (even if not complete) to get the information about default pages via RESTAPI. One of them is the mentioned above (setting the id to index_html). Another one is querying additional metadata via RESTAPI with the parameter ?metadata_fields=default_page. Remember that default_page is a property of the container folder not of the page. This method will give you the "real" default page but not the index_html page!

response = requests.get(folder_at_id, headers=headers, auth=auth)
folder_at_id = response.json()['@id']
parent_at_id = response.json()['parent']['@id']
response = requests.get(parent_at_id + '?metadata_fields=default_page',
                        headers=headers, auth=auth)
for item in response.json()['items']:
    if item['@id'] == folder_at_id and item['default_page']:
        print(f"{item['@id']}/{item['default_page']}")

With both methods (index_html and default_page) you could proceed as follows:

  1. get the default_page of the folder if it is set
  2. set the id of your page via index_html

But remember that this way you'll actually have two "default pages"! Since a content object called index_html wins (see docstring above), this might solve (parts) of your problem.

PS: One caveat of having two "default pages" can be found with the navigation portlet, which won't show the "real" default page even if there is a index_html.

If I were asked, I would suggest that a RESTAPI entry point for setting/adding/changing/deleting properties be implemented.

The method setDefaultPage basically does set (or add/modify/delete) the property default_page and reindexes the object.

An entry point for setting properties would be really useful not only for the property default_page but generally for any property.

UPDATE:

To clarify: I use here the term "property" as defined in PropertyManager.

it is like implementing a remote prompt. API should adhere to an interface, to a set of operations. Setting every property, calling every function is not an API.

Setting the default page is a Plone API?

Well. There could be as many opinions as people you ask.

At this moment the RESTAPI offers the metadata_fields parameter. This is in my opinion a properties "getter". So why not extend the RESTAPI with the corresponging "setters"?

The content type plone.app.contentypes.Folder is actually a subclass of OFS.PropertyManager.PropertyManager [see fn 1]. PropertyManagers main purpose was/is to offer propertie's management via the web. That's what an API is for. Isn't it? IMHO this qualifies it as an interface.

But despite of the appropriateness of such an interface I think that setting the default page is either a function of Plone and thus it should be accessible via API (which I'd prefer) or it is not a function of Plone and thus it can be deprecated. There are lots of use cases where the default page is useful. BTW: Volto (as depending on the RESTAPI) has no way to set the default page.

On the other side the RESTAPI does offer the @registry endpoint. This could of course be viewed as an interface to the registry. Even if this is very useful it also implies an extended concept of interface.
BTW: The registry has two records for storing the default page ids (plone.default_page from ISiteSchema) and the types which can be set as a default page (plone.default_page_types from ITypesSchema). Those records are also accessible via RESTAPI.

PS: While writing this I realize that the word "property" is ambiguous. To clarify: I use it here in the meaning of property as defined in PropertyManager. I do not mean the python builtin class property nor any other concepts of this word.

[1] plone.app.contentypes.Folder > plone.app.contentypes.Container > plone.folder.CMFOrderedBTreeFolderBase > Products.CMFCore.PortalFolderBase > OFS.Folder.Folder > OFS.PropertyManager.PropertyManager

The concept of a default_page has always been one of the most challenging tasks (together with collections and portlets) for editors. We dropped default_pages in Plone 6 (Volto) and the Folder content type to simplify the UI. This idea goes back to Alex Limi and others who wanted to simplify Plone in that regard, I think since Plone 2.5 or 3. With Plone 6 we were finally able to implement this simplification.

That being said, setting and getting the default_page is a valid use case for Plone Classic and older versions of Plone. Therefore having it in plone.restapi can be rationalized even if Plone 6 Volto does not use it. plone.restapi is a REST API for Plone, Volto is just the main consumer and mainly drives its development since the beginning.

Though, the concept of a default page is clearly something we deprecated in Plone 6. Therefore at some point in the future, we most likely would rip it out again. There is a plone.restapi 8 branch that is still maintained for old Plone versions that might be an option. Though, if there are no people who push for this, nothing will happen.

plone.restapi and Volto are open source, anybody is welcome to add any feature that they want to see in there. As release manager responsible for plone.restapi, I am happy to review and release any PRs that enhance the REST API.

@tisto:I do not understand the logic of this argumentation.

If it is a valid use case for Plone Classic (i.e. Plone 6 Classic UI), how can it be concluded that "therefore having it in plone.restapi can be rationalized"?

Would you please explain who is "we" and where is it documented that "the concept of a default page is clearly something we deprecated in Plone 6"?

Just curious. Does the wording "old Plone versions" include "Plone 6 Classic UI"?

Nope. Please read Overview — Plone Documentation v6.0

plone.restapi is not required for plone 6 ClassicUI to function. But the latest and newest release is also included in a ClassicUI setup because it is the same backend server. Thatm/ probably why the endpoint to only set a default_page wasn’t really missed until now.
Missing the option to set a default_page is an easy fix.

Collective.exportimport for example does use the (de)serializers but the default pages are an extra export and import step. That’s likely to avoid setting an item that is not there yet during the content-import step.

Th default page is It’s just an attribute of a content item, like the layout is another.

It does not hurt if we add this to Plone restapi. ClassicUI will live for a long time and so I see a value in having it. Even if thrown out in core at some future version the concept will be moved to a classicui package and can be provided by restapi conditionally, as it already does with some features.

That said, someone has to write it and do a PR, that is how it works.
If it comes to restapi this needs docs first, then - if the API design is checked - the tests and implementation is next.

3 Likes

Reporting back:
Renaming my document to 'index_html' was good enough for my use case.
Thanks very much @mekell for the suggestion!

Does this mean that it is possible to set the id of a document, but not (the property) default_page ? What happens if one tries to change 'default_page' the same way as 'id' ? (or can this not be done unless 'default_page' is already set?

Unfortunately this is not possible using the RESTAPI. See my comments before.