Batching folder contents based on content type (Plone 6 Classic)

Lets say we have a folder with many items, 30 Documents, 10 Images, 23 News Items, etc. etc.

If I want to make a folder view that shows the first 10 items of each, but also with a 'batch' version (so an (next button) below Documents.

How can this 'best' be done (fastest).
PS: Should work withouth knowing what portal types are in the folder (is there a way to know which content types a folder contains without 'getting everything )? )

You could first get the portal types from the catalog's index

catalog = getToolByName(context, 'portal_catalog')
index = catalog._catalog.indexes['portal_type']

then loop through the index.uniqueValues() and calling catalog.searchResults with the portal_type and sort_limit parameters on the folder_path.

I think that returns 'all content types', I need to have just those for the current folder, probably I could just 'find all' and use only unique values
Maybe:

def get_types(self):
    folder = self.context  # Assuming the script is created inside the folder
    portal_types = [item.portal_type for item in folder.objectValues()]

    return set(portal_types)

I wonder if there is a way to get 'for free' the 'batching buttons etc'. Since there will be a 'next page' for some content types and not one for others it would be super if there was a fill slot or something similar I could use (for each 'batch')

Keep in mind, that when you do folder.objectValues() you wake up every object inside this folder.

I'd rather use @@contentlisting view from plone.app.contentlisting like this:

view = folder.restrictedTraverse("@@contentlisting")
ptypes = set()
for it in view(batch=False):
    ptypes.add(it.portal_type)

ptypes will be a distinct list of the item types because of set()

you can then further use view for getting the type constrained list like

folders = view(batch=True, portal_type="Folder")

EDIT: for folders use @@folderListing the unified browser view for folders and collections is @@contentlisting ... sorry.

For multiple batches on the same site you might look at the b_start_str parameter from plone.base.batch.Batch ... with that parameter you can trigger for which batch you want to create the paging links:

Something like this...

from plone.base.batch import Batch

class MultiTypeListing(BrowserView):

    b_size = 20

    def batch(self, ptype):
        b_start_str = f"b_start_{ptype}"
        b_start = self.request.get(b_start_str, 0)
        listing = self.context.restrictedTraverse("@@folderListing")
        items = listing(batch=False, portal_type=ptype)
        return Batch(
            items,
            self.b_size,
            b_start,
            b_start_str=b_start_str,
        )

and use it in the template like:

<div tal:define="batch python:view.batch('Folder')">
    <!-- show your batch -->
    <div metal:use-macro="context/batch_macros/macros/navigation"></div>
</div>

<div tal:define="batch python:view.batch('Event')">
    <!-- show your batch -->
    <div metal:use-macro="context/batch_macros/macros/navigation"></div>
</div>

untested :wink:

Thanks.
This works (great)

view.py


class DocumentsFolderView(BrowserView):
    # template = ViewPageTemplateFile('documents_folder_view.pt')

    b_size = 25
    
    def __call__(self):
        return self.index()
    
    def get_types(self):
          #view = self.context.restrictedTraverse("@@folderListing")
        #See comments below
        view = self.context.restrictedTraverse("@@folderListing")@@contentlisting`
        ptypes = set()
        for it in view(batch=False):
            ptypes.add(it.portal_type)
        return sorted(ptypes)

    def batch(self, ptype):
        b_start_str = f"b_start_{ptype}"
        b_start = self.request.get(b_start_str, 0)
        #See comments below
        listing = self.context.restrictedTraverse("@@folderListing")@@contentlisting
        
        items = listing(batch=False, portal_type=ptype)
        return Batch(
            items,
            self.b_size,
            b_start,
            b_start_str=b_start_str,
        )

template.pt


<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:metal="http://xml.zope.org/namespaces/metal"
  xmlns:tal="http://xml.zope.org/namespaces/tal"
  xmlns:i18n="http://xml.zope.org/namespaces/i18n"
  i18n:domain="My.Addon"
  metal:use-macro="context/main_template/macros/master">
  <body>
    <metal:content-core fill-slot="content-core">
      <metal:block define-macro="content-core">

        <div class="white-background">
          <!-- first tab -->

          <div class="pat-autotoc autotabs"
            data-pat-autotoc="section:fieldset;levels:legend;">

            <!-- Loop through each portal type in the grouped_items dictionary -->
            <fieldset  tal:repeat="contentype view/get_types">
              <legend>${contentype}</legend> 
              <div tal:define="batch python:view.batch(contentype)">

            
              <div tal:repeat="item batch">
                  <!-- <tal:obj tal:define="obj item/getObject"> -->
                        <h5><a href="${item/getURL}">${item/Title}</a></h5>
                        <p>${item/Description}</p>
                  <!-- </tal:obj> -->
              </div>
              <div metal:use-macro="here/batch_macros/macros/navigation" />
              </div>
            </fieldset>
        </div>
    </div>

  
  </metal:block>
</metal:content-core>
</body>
</html>

NOTE: Updated code, see notes below

Perfect... on behalf of speed: use item/getObject only if you know what you do :wink:

Since you get a list of IContentListingObject back from @@contentlisting you can look here what is callable: plone.app.contentlisting/plone/app/contentlisting/interfaces.py at master · plone/plone.app.contentlisting · GitHub

Yepp. That is why I commented out, it might be that one content type should show something that I can not get (directly) from the catalog, most likely 'what other item it is related to (I think I will need 'obj' to use 'to_obj' (?), will check if I need it (customer meeting later today).

Thanks, made me look at it again. I did not know (remember?) that there is CroppedDescription. Probably better for this usecase.

If Plone 6, you can use https://6.docs.plone.org/plone.api/relation.html

Yes but for plone.api.relation you also need the object again. On a really large system we made our own KeywordIndex with the related uuids as catalog metadata. would ne interesting to have some benchmarks how this "related item lookup" behaves with cataloged metadata or plone.api lookup

It would be nice to also show the icons for each content types in the title of each 'batch'.

If the content type is 'Folder', how do I get/render the icon that is defined for Folder. Should I read the registry ( <record name="plone.icon.Folder (or .folder) ) or is there another way to do this.

Note: This is just once for each 'tab/title', I dont have 'item' here

UPDATE, this can be done with:

 <tal:icon tal:replace="structure python:icons.tag(contentype)" />

consider using @@contentlisting instead of @@folderListing. (See README.rst).

This documentation might be helpful as well: integration.rst.

@@contentlisting is for Collection context ... @@folderListing for Folder wrong -> @@contentlisting is registered for folders and collections and should be used.

Oh. I thought that @@contentlisting was a unified interface for Folders and Collections and that @@folderListing was deprecated:

See https://github.com/plone/plone.app.contentlisting/blob/master/CHANGES.rst#12-2015-07-18:

Introduce @@contentlisting view, which is also supports Collections from plone.app.contenttypes including filtering of results. This gives us a unified interface for listing content from Folders or Collections. Deprecate @@folderListing, which is kept for BBB compatibility. [thet]

Ah, you're right ... I've overseen the second registration for @@contentlisting for folderish types ... sorry for the noise. (I've edited my comment above)

I thought getting object and then use to_object was faster than using plone.api. Is that correct?

Note: I think obj.to_path does not wake up 'the other object', no idea if there is any difference by using plone.api.relation.get 'first'