4

I want to programmatically add a header and a footer as I use atlas with multipages for one feature but I can't predict the number of pages for the table depending on the feature. In order to add the header and footer, I want to copy a QgsLayoutItemGroup and all the associated items to position them on other pages. I can easily through the GUI, copy and paste the QgsLayoutItemGroup but I don't see how I can do it with PyQGIS and make change automatically. Pointers welcome!

Edit

I've changed the original function code to only deal with group to remove references between objects and rename them as the answer provided was keeping some unwanted references between group. It's clearly not enough generic but it fits my use case: a group with items but no other group within it.

from qgis.PyQt.QtCore import QUuid
from qgis.PyQt.QtXml import QDomDocument
from qgis.core import QgsReadWriteContext

def duplicate_item_group(item, layout, position=None):
    doc = QDomDocument()
    element = doc.createElement('Copied items')
    context = QgsReadWriteContext()

    group_uuid = QUuid.createUuid().toString()
    item.writeXml(element, doc, context)
    if type(item) == QgsLayoutItemGroup:
        print(len(item.items()))
        for subitem in item.items():
            subitem.writeXml(element, doc, context)
    doc.appendChild(element)
    layout_items = doc.elementsByTagName('LayoutItem')
    uuids = []
    for i in range(layout_items.count()):
        item_node = layout_items.at(i + 1)
        if item_node.isElement():
            item_node.toElement().removeAttribute('uuid')
            uuid = QUuid.createUuid().toString()
            uuids.append(uuid)
            item_node.toElement().setAttribute('groupUuid', group_uuid)
            item_node.toElement().setAttribute('templateUuid', uuid)

    group_elements = doc.elementsByTagName('ComposerItemGroupElement')
    for i in range(group_elements.count()):
        item_node = group_elements.at(i)
        if item_node.isElement():
            item_node.toElement().setAttribute('uuid', uuids[i])
    # print(doc.toString()) # To debug the XML content
    if position is None:
        return layout.addItemsFromXml(element, doc, context)
    else:
        return layout.addItemsFromXml(element, doc, context, QPoint(*position))
5
+100

The copy/paste mechanism for layout items is implemented in the QgsLayoutView class (copyItems, pasteItems) .

I'm not sure if there's a way to use these functions without opening and accessing the Layout window, but we can write a Python function that works directly with the layout object:

def duplicate_item(item, layout, position=None):
    doc = QDomDocument('Clipboard')
    element = doc.createElement('Copied items')
    context = QgsReadWriteContext()

    item.writeXml(element, doc, context)
    if type(item) == QgsLayoutItemGroup:
        for subitem in item.items():
            subitem.writeXml(element, doc, context)
    doc.appendChild(element)
    layout_items = doc.elementsByTagName('LayoutItem')
    for i in range(layout_items.count()):
        item_node = layout_items.at(i)
        if item_node.isElement():
            item_node.toElement().removeAttribute('uuid')
            item_node.toElement().removeAttribute('groupUuid')

    if position is None:
        return layout.addItemsFromXml(element, doc, context)
    else:
        return layout.addItemsFromXml(element, doc, context, QPoint(*position))

For instance, if you want to copy the first group on the first layout to a new location, you can call the code as follows from the Python console in QGIS:

layout = QgsProject.instance().layoutManager().layouts()[0]
group = [item for item in layout.items() if type(item)==QgsLayoutItemGroup][0]
duplicate_item(group, layout, [50, 80])
5
  • Tried again and buggy not in the sense it does not work at all but because execute multiple times duplicate_item(group, layout, [50, 80]) in the PyQGIS console, then try to move and select each group in the GUI to see they are grouped with the others. Conclusion: the code does not copy the group correctly and some reference exists between groups... – ThomasG77 Apr 19 '20 at 20:08
  • @ThomasG77: You're right, one of the resulting groups contains references to the elements in the other group. I'll take a look at how to fix this later today – Jake Apr 20 '20 at 6:34
  • @ThomasG77: Actually, it seems like the same thing happens when I copy and paste a group using the GUI (Ctrl+C, Ctrl+V). Could you verify whether this is also the case for you? Also, could you edit your question to include an example of the types of objects you have in your group? – Jake Apr 20 '20 at 6:58
  • Same issue using GUI. You can see in my edit how I've solved the issue reusing your code without the side effect we've seen. If you find a better/more generic solution, feedback welcome! – ThomasG77 Apr 20 '20 at 9:30
  • @ThomasG77: Your approach of removing the groupUuid in addition to the uuid is the answer. It's not necessary to manually generate new uuids and groupUuids, that's handled correctly when restoring the objects from the XML. I've edited my code accordingly. – Jake Apr 20 '20 at 19:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.