diff --git a/soitool/inline_editable_soi_view.py b/soitool/inline_editable_soi_view.py
index e8bd1f9d4924aab3b2f316b4744b563df87718d1..bacf971887507dd2d0ae187599eb9e261b0edcdf 100644
--- a/soitool/inline_editable_soi_view.py
+++ b/soitool/inline_editable_soi_view.py
@@ -1,4 +1,5 @@
 """Includes functionality for inline editing of SOI."""
+import string
 from datetime import datetime
 from PySide2.QtCore import Qt, QRectF, QTimer, QPoint, QMarginsF
 from PySide2.QtWidgets import (
@@ -22,6 +23,11 @@ from soitool.soi import ModuleLargerThanBinError
 from soitool.dialog_wrappers import exec_warning_dialog
 from soitool.serialize_export_import_soi import generate_soi_filename
 
+# How attachment pages should be numbered. The first page should be numbered
+# by the value of ATTACHMENT_NUMBERING_SCHEME[0], the second page
+# ATTACHMENT_NUMBERING_SCHEME[1], and so on.
+ATTACHMENT_NUMBERING_SCHEME = list(string.ascii_uppercase)
+
 
 class ProxyLabelWithCustomQPrintText(QGraphicsProxyWidget):
     """QGraphicsItem that prints a custom text when printed onto QPrint.
@@ -180,7 +186,7 @@ class InlineEditableSOIView(QScrollArea):
 
     def ensure_proxies(self):
         """Make sure all modules of the SOI have a proxy inside the scene."""
-        for module in self.soi.modules:
+        for module in self.soi.modules + self.soi.attachments:
             if not self.is_widget_in_scene(module["widget"]):
                 proxy = self.scene.addWidget(module["widget"])
                 self.proxies.add(proxy)
@@ -213,7 +219,11 @@ class InlineEditableSOIView(QScrollArea):
 
         self.soi = soi
 
-        self.number_of_pages = 1
+        # The following variables are updated by a call to `update_pages` later
+        # in this `__init__`. Therefore the values given here are in practice
+        # never used
+        self.number_of_pages_total = 1
+        self.number_of_non_attachment_pages = 1
         self.proxies = set()
 
         # NOTE: These variables are only included to support PDF output of the
@@ -310,7 +320,7 @@ class InlineEditableSOIView(QScrollArea):
                 self.draw_pages()
 
                 # Render each page to own PDF page
-                for j in range(self.number_of_pages):
+                for j in range(self.number_of_pages_total):
 
                     x = 0
                     y = self.soi.HEIGHT * j + self.soi.PADDING * j
@@ -321,7 +331,7 @@ class InlineEditableSOIView(QScrollArea):
                     )
 
                     # If there are more pages, newPage
-                    if j + 1 < self.number_of_pages:
+                    if j + 1 < self.number_of_pages_total:
                         printer.newPage()
                 # If there are more copies, newPage
                 if i + 1 < self.copy_total:
@@ -334,15 +344,18 @@ class InlineEditableSOIView(QScrollArea):
 
         The minimum page count is 1.
         """
-        required_pages = 1
-        for module in self.soi.modules:
-            if module["meta"]["page"] > required_pages:
-                required_pages += 1
-        self.number_of_pages = required_pages
+        self.number_of_non_attachment_pages = (
+            self.soi.get_number_of_non_attachment_pages()
+        )
+        # Each attachment module requires it's own page
+        required_pages = self.number_of_non_attachment_pages + len(
+            self.soi.attachments
+        )
+        self.number_of_pages_total = required_pages
 
     def draw_pages(self):
-        """Draw self.number_of_pages pages."""
-        for i in range(self.number_of_pages):
+        """Draw self.number_of_pages_total pages."""
+        for i in range(self.number_of_pages_total):
 
             x = 0
             y = self.soi.HEIGHT * i + self.soi.PADDING * i
@@ -354,7 +367,20 @@ class InlineEditableSOIView(QScrollArea):
             )
 
             self.draw_page(x, y)
-            self.draw_header(x + self.soi.PADDING, y + self.soi.PADDING, i + 1)
+            page_number = i + 1
+            header_x = x + self.soi.PADDING
+            header_y = y + self.soi.PADDING
+            # If not an attachment page: draw as normal
+            # If attachment page: draw with page number starting from 1 again
+            if page_number <= self.number_of_non_attachment_pages:
+                self.draw_header(header_x, header_y, page_number, False)
+            else:
+                self.draw_header(
+                    header_x,
+                    header_y,
+                    page_number - self.number_of_non_attachment_pages,
+                    True,
+                )
 
         for proxy in self.proxies:
             # Redraw of pages requires modules to be moved to front again
@@ -369,7 +395,7 @@ class InlineEditableSOIView(QScrollArea):
     # "Too many statements" for this function because it's doing GUI layout
     # work, which in nature is tedious and repetitive.
     # pylint: disable=R0914,R0915
-    def draw_header(self, x, y, page_number):
+    def draw_header(self, x, y, page_number, is_attachment_page):
         """Draw header staring at given position.
 
         Source for rotation approach:
@@ -379,6 +405,15 @@ class InlineEditableSOIView(QScrollArea):
         ----------
         x : int
         y : int
+        page_number : int
+            Page number of page to draw. 'is_attachment_page' affects how the
+            page number is drawn.
+        is_attachment_page : bool
+            If True the page will indicate that it is an attachment, and the
+            page numbering will follow a convention for page numbering defined
+            in 'ATTACHMENT_NUMBERING_SCHEME'. If False the page will indicate
+            that the page is part of the main page, and the page numbering will
+            use 1,2,3,....
         """
         # Title
         label_title = QLabel(self.soi.title)
@@ -449,7 +484,7 @@ class InlineEditableSOIView(QScrollArea):
         # NOTE that this position is only correct for the default text. During
         # PDF printing the ProxyLabelWithCustomQPrintText class will paint
         # itself with a different text, and thus not be perfectly centered.
-        proxy.setPos(x + 18, copy_number_y_pos + label_width / 2)
+        proxy.setPos(x + 15, copy_number_y_pos + label_width / 2)
         proxy.setRotation(-90)
         self.scene.addItem(proxy)
 
@@ -471,9 +506,18 @@ class InlineEditableSOIView(QScrollArea):
         proxy.setRotation(-90)
 
         # Page numbering
-        page_number = QLabel(
-            "Side {} av {}".format(page_number, self.number_of_pages)
-        )
+        if is_attachment_page:
+            page_number = QLabel(
+                "Vedlegg {}".format(
+                    ATTACHMENT_NUMBERING_SCHEME[page_number - 1]
+                )
+            )
+        else:
+            page_number = QLabel(
+                "Side {} av {}".format(
+                    page_number, self.number_of_non_attachment_pages
+                )
+            )
         page_number.setStyleSheet("background-color: rgba(0,0,0,0%)")
         page_number.setFont(QFont("Times New Roman", 15))
         # Source: https://stackoverflow.com/a/8638114/3545896
@@ -481,7 +525,7 @@ class InlineEditableSOIView(QScrollArea):
         label_width = (
             page_number.fontMetrics().boundingRect(page_number.text()).width()
         )
-        page_number.move(x + 85, copy_number_y_pos + label_width / 2)
+        page_number.move(x + 80, copy_number_y_pos + label_width / 2)
         proxy = self.scene.addWidget(page_number)
         proxy.setRotation(-90)
 
diff --git a/soitool/main_window.py b/soitool/main_window.py
index c1aa8d6273560291987a2937efc81f22a1820c12..ddaf71c3771215eb7eba64bd9b41efa47d8ca2b4 100644
--- a/soitool/main_window.py
+++ b/soitool/main_window.py
@@ -225,9 +225,10 @@ class MainWindow(QMainWindow):
             self.tabs.setCurrentWidget(tab)
 
     def open_soi_workspace_tab(self):
-        """Open tab containing a SOIWorkspaceWidget."""
+        """Open and select tab containing a SOIWorkspaceWidget."""
         tab = SOIWorkspaceWidget()
         self.tabs.addTab(tab, tab.soi.title)
+        self.tabs.setCurrentWidget(tab)
 
     def close_tab(self, index):
         """Close tab at given index.
diff --git a/soitool/media/authenticationboardmodule.PNG b/soitool/media/authenticationboardmodule.PNG
index 9df0e912d0c92a22c40cbe72641897146749ab5f..d725e2615e1c1c37c0507da0da51d8cb6635cc1c 100644
Binary files a/soitool/media/authenticationboardmodule.PNG and b/soitool/media/authenticationboardmodule.PNG differ
diff --git a/soitool/media/subtractorcodesmodule.PNG b/soitool/media/subtractorcodesmodule.PNG
index a74e8c1a2b9bcec0b1a55e6205c137c53ca8aabc..7a3f581f62d58d2c88dbc909fdd06e27d5b8d404 100644
Binary files a/soitool/media/subtractorcodesmodule.PNG and b/soitool/media/subtractorcodesmodule.PNG differ
diff --git a/soitool/module_list.py b/soitool/module_list.py
index 64c62e84200bf56fad6bb145357433b1bf463fff..c30fbcd636d733869a9c0b943ce7807c9c8fde4b 100644
--- a/soitool/module_list.py
+++ b/soitool/module_list.py
@@ -15,7 +15,7 @@ class ModuleList(QListWidget):
 
     List elements are names of modules or attachment-modules from soitool.SOI.
     List elements are editable, drag-and-droppable and unique (no duplicates).
-    Makes changes in soitool.SOI lists through its parent-variable.
+    Makes changes in soitool.SOI lists through its soi property.
 
     Parameters
     ----------
@@ -32,9 +32,9 @@ class ModuleList(QListWidget):
 
         if not isinstance(soi, SOI):
             raise RuntimeError(
-                "Only soitool.SOIWorkspaceWidget is "
-                "acceptable type for parent-variable "
-                "in class Module_list."
+                "Only soitool.SOI is "
+                "acceptable type for the soi property "
+                "in class ModuleList."
             )
         self.type = module_type
         self.soi = soi
@@ -142,7 +142,8 @@ class ModuleList(QListWidget):
         """Notify parent when an element is drag-and-dropped.
 
         Note that if the SOI is not prepared for manual priorization an error
-        message will be displayed, and nothing else will be done.
+        message will be displayed, and nothing else will be done. This only
+        applies to uses of this class with non-attachment modules.
 
         https://doc.qt.io/qt-5/qabstractitemview.html#dropEvent.
 
@@ -151,7 +152,10 @@ class ModuleList(QListWidget):
         event : QDropEvent
             Is sent to super().
         """
-        if self.soi.algorithm_sort != "none":
+        if (
+            self.soi.algorithm_sort != "none"
+            and ModuleType(self.type) == ModuleType.MAIN_MODULE
+        ):
             exec_warning_dialog(
                 text="SOI er ikke innstilt for automatisk plassering av "
                 "moduler!",
diff --git a/soitool/modules/code_table_base.py b/soitool/modules/code_table_base.py
index 4516e2c2c673a7bc2f4bd1deb8e0030e4e97839a..47cb88e32ea4dd39f0527f7c0b2e78f49f00c1ab 100644
--- a/soitool/modules/code_table_base.py
+++ b/soitool/modules/code_table_base.py
@@ -114,8 +114,8 @@ class CodeTableBase(ModuleBase, QTableWidget, metaclass=Meta):
         for i in range(self.start_no_of_codes):
             # Insert non-editable code in third column
             item_third = QTableWidgetItem(codes[i])
-            if self.type == SUBTRACTORCODES_MODULE:
-                item_third.setTextAlignment(Qt.AlignCenter)
+            item_third.setTextAlignment(Qt.AlignCenter)
+            item_third.setFont(self.code_font)
             item_third.setFlags(item_third.flags() ^ Qt.ItemIsEditable)
             self.setItem(i, 2, item_third)
 
diff --git a/soitool/modules/module_authentication_board.py b/soitool/modules/module_authentication_board.py
index 6c6e76cb694b5d66df874443c751ff84789089c5..5399ddd044a96a382011def70db29adad7acf48a 100644
--- a/soitool/modules/module_authentication_board.py
+++ b/soitool/modules/module_authentication_board.py
@@ -12,14 +12,17 @@ START_NO_OF_CODES = 10
 CODE_LENGTH = 25
 
 # Has to be 'ascii', 'digits' or 'combo'
-# Codes will consist of A-Z if 'ascii', 0-9 if 'digits' and A-Z+0-9 if 'combo'.
+# Codes will consist of A-Z if 'ascii', 0-9 if 'digits' and A-Z+0-9 if 'combo'
 CODE_CHARACTER_TYPE = "ascii"
 
+# Font for authentication codes, should be a monospaced font
+CODE_FONT = QtGui.QFont("Consolas", 10, QtGui.QFont.SansSerif)
+
 # Characters for first column,
 # it's length determines maximum number of codes (rows).
 ROW_IDENTIFIERS = string.ascii_uppercase
 
-# Adds space between sets of characters, 0 => no spaces.
+# Adds space between sets of characters, 0 => no spaces
 # If code is 123456 and interval is 2, code will be 12 34 56
 SPACE_INTERVAL = 5
 SPACE_AMOUNT = 2
@@ -38,6 +41,9 @@ class AuthenticationBoardModule(CodeTableBase):
     CODE_LENGTH, spaced out for readability if SPACE_INTERVAL and SPACE_AMOUNT
     is larger than 0.
 
+    The authentication codes have their own monospaced font for increased
+    readability, meaning each character has the same width.
+
     If parameters are given, the widget initializes accordingly:
     'size' is a dict: {"width": int, "height": int},
     'data' is a dict with keys "cells", "code_length", "space_interval",
@@ -47,9 +53,6 @@ class AuthenticationBoardModule(CodeTableBase):
 
     The widget does not use more room than needed, and resizes dynamically.
     It has shortcuts for adding and removing rows.
-
-    Codes are not horizontally centered for readability concerns because 'BGD'
-    is wider than 'III' (example) in certain fonts.
     """
 
     def __init__(self, size=None, data=None):
@@ -66,6 +69,8 @@ class AuthenticationBoardModule(CodeTableBase):
                 "Invalid value for CONSTANT 'CODE_CHARACTER_TYPE': "
                 "'{}'".format(CODE_CHARACTER_TYPE)
             )
+        self.code_font = CODE_FONT
+
         # Set default values for table to be generated
         if size is None and data is None:
             self.start_no_of_codes = START_NO_OF_CODES
@@ -131,12 +136,10 @@ class AuthenticationBoardModule(CodeTableBase):
 
             # Insert authentication-code in third column
             item_third = QTableWidgetItem(code)
+            item_third.setFont(self.code_font)
             item_third.setFlags(item_third.flags() ^ Qt.ItemIsEditable)
             self.setItem(selected_row_index + 1, 2, item_third)
 
-            # Resize code-column in case it got wider
-            # Example: 'BGD' is wider than 'III' (depending on font)
-            self.resizeColumnToContents(2)
             self.resizeRowToContents(selected_row_index + 1)
 
             resize_table(self, columns=False, rows=False, has_headline=True)
diff --git a/soitool/modules/module_subtractorcodes.py b/soitool/modules/module_subtractorcodes.py
index 5ef03ddbde0ef4e611a1356f0faeb497ae5ae8ac..8622f14b415973186a6e8a1e76eef606a0503b05 100644
--- a/soitool/modules/module_subtractorcodes.py
+++ b/soitool/modules/module_subtractorcodes.py
@@ -10,13 +10,16 @@ from soitool.modules.code_table_base import CodeTableBase
 START_NO_OF_CODES = 7
 CODE_LENGTH = 8
 
+# Font for subtractorcodes
+CODE_FONT = QtGui.QFont("Arial", 10, QtGui.QFont.SansSerif)
+
 # Characters for first and second column
 ROW_IDENTIFIERS = string.ascii_uppercase
 
 # Adds space between sets of characters, 0 => no spaces.
 # If code is 12345678 and interval is 2, code will be 1234 5678
 SPACE_INTERVAL = 4
-SPACE_AMOUNT = 5
+SPACE_AMOUNT = 3
 
 HEADLINE_TEXT = "Subtraktorkoder"
 
@@ -56,6 +59,7 @@ class SubtractorcodesModule(CodeTableBase):
         self.space_interval = SPACE_INTERVAL
         self.space_amount = SPACE_AMOUNT
         self.code_character_type = "digits"
+        self.code_font = CODE_FONT
 
         CodeTableBase.__init__(self, size, data)
 
@@ -114,6 +118,7 @@ class SubtractorcodesModule(CodeTableBase):
             # Insert code
             item_code = QTableWidgetItem(code)
             item_code.setTextAlignment(Qt.AlignCenter)
+            item_code.setFont(self.code_font)
             item_code.setFlags(item_code.flags() ^ Qt.ItemIsEditable)
             self.setItem(selected_row_index + 1, 2, item_code)
 
diff --git a/soitool/soi.py b/soitool/soi.py
index fcb67a2360e13a1ce094bcf7e864079ffd2494ed..bb776a51b5b81ea6c7c6fc1ead7a0fb573952935 100644
--- a/soitool/soi.py
+++ b/soitool/soi.py
@@ -469,10 +469,42 @@ class SOI:
                     self.placement_strategy
                 )
             )
+        self.reorganize_attachments()
         # Call listener functions
         for listener in self.reorganization_listeners:
             listener()
 
+    def reorganize_attachments(self):
+        """Reorganize attachments. Each on own page in correct order.
+
+        Order taken from order in attachment list directly. Attachments appear
+        at the top-left corner always.
+        """
+        for i, attachment in enumerate(self.attachments):
+            first_attachment_page = (
+                self.get_number_of_non_attachment_pages() + 1
+            )
+            attachment["meta"]["x"] = 0
+            attachment["meta"]["y"] = 0
+            attachment["meta"]["page"] = first_attachment_page + i
+            self.update_module_widget_position(attachment)
+
+    def get_number_of_non_attachment_pages(self):
+        """Calculate how many pages non-attachment modules require.
+
+        The minimum page count is 1.
+
+        Returns
+        -------
+        int
+            Number of pages required for non-attachment modules.
+        """
+        pages = 1
+        for module in self.modules:
+            if module["meta"]["page"] > pages:
+                pages += 1
+        return pages
+
     def get_rectpack_packer(self):
         """Return rectpack packer set up for this SOI.
 
diff --git a/test/test_serialize_export_import.py b/test/test_serialize_export_import.py
index 3a9e20815cf18a62ae65564c8731f17ffc68e838..143c31635110ed0a69270a28e3124aba99960d80 100644
--- a/test/test_serialize_export_import.py
+++ b/test/test_serialize_export_import.py
@@ -14,6 +14,12 @@ from soitool.serialize_export_import_soi import (
     SERIALIZED_SOI_SCHEMA,
 )
 
+# The error being ignored here is pylint telling us that 'test' is a standard
+# module, so the import should be placed further up. In our case we have an
+# actual custom module called 'test', so pylint is confused.
+# pylint: disable=C0411
+from test.test_soi import deep_copy_of_modules_list
+
 app = QApplication.instance()
 if app is None:
     app = QApplication([])
@@ -52,6 +58,27 @@ MODULES = [
 ]
 
 
+# We'd like to use the same list of modules for both main and attachment
+# modules, so we need this to help make a copy of widgets. Otherwise main and
+# attachment modules would be referencing the same underlying widgets, as Qt
+# does not support cloning of QWidgets by default.
+def tablemodule_cloner(module):
+    """Return new TableModule that is a clone of parameter module.
+
+    Parameters
+    ----------
+    module : TableModule
+        Module to clone.
+
+    Returns
+    -------
+    TableModule
+        New TableModule that is a clone of parameter module.
+    """
+    width, height = module.get_size()
+    return TableModule({"width": width, "height": height}, module.get_data())
+
+
 class SerializeTest(unittest.TestCase):
     """Testcase for functions in module 'serialize_export_import_soi.py'."""
 
@@ -71,8 +98,8 @@ class SerializeTest(unittest.TestCase):
             algorithm_bin=ALGORITHM_BIN,
             algorithm_pack=ALGORITHM_PACK,
             algorithm_sort=ALGORITHM_SORT,
-            modules=MODULES,
-            attachments=MODULES,
+            modules=deep_copy_of_modules_list(MODULES, tablemodule_cloner),
+            attachments=deep_copy_of_modules_list(MODULES, tablemodule_cloner),
         )
         date = datetime.strptime(DATE, "%Y-%m-%d")
         date_formatted_for_file_path = date.strftime("%Y_%m_%d")
diff --git a/test/test_soi.py b/test/test_soi.py
index 4045214d1af11cf35a64c47c32d087716a5dc891..d122741addd8ee8a7337d8532aa832aa08b0c57d 100644
--- a/test/test_soi.py
+++ b/test/test_soi.py
@@ -36,11 +36,15 @@ class TestModule(ModuleBase, QWidget, metaclass=Meta):
         QWidget.__init__(self, *args, **kwargs)
         ModuleBase.__init__(self)
 
+        self.color = color
+        self.width = width
+        self.height = height
+
         self.setAutoFillBackground(True)
         palette = self.palette()
-        palette.setColor(QPalette.Window, QColor(color))
+        palette.setColor(QPalette.Window, QColor(self.color))
         self.setPalette(palette)
-        self.setGeometry(0, 0, width, height)
+        self.setGeometry(0, 0, self.width, self.height)
 
     def get_size(self):
         """Override."""
@@ -60,6 +64,22 @@ class TestModule(ModuleBase, QWidget, metaclass=Meta):
         raise NotImplementedError()
 
 
+def testmodule_cloner(module):
+    """Return new TestModule that is a clone of parameter module.
+
+    Parameters
+    ----------
+    module : TestModule
+        Module to clone.
+
+    Returns
+    -------
+    TestModule
+        New TestModule that is a clone of parameter module.
+    """
+    return TestModule(module.color, module.width, module.height)
+
+
 # The modules below have sizes that make the ideal for testing.
 # Sorting them by width should yield
 #  1. wide_module
@@ -105,8 +125,10 @@ class TestSOI(unittest.TestCase):
         simply sets the default.
         """
         self.soi = SOI(
-            modules=list(TEST_MODULES),
-            attachments=list(TEST_MODULES),
+            modules=deep_copy_of_modules_list(TEST_MODULES, testmodule_cloner),
+            attachments=deep_copy_of_modules_list(
+                TEST_MODULES, testmodule_cloner
+            ),
             algorithm_sort="none",
             placement_strategy="auto",
         )
@@ -238,7 +260,9 @@ class TestSOI(unittest.TestCase):
         self.soi.modules.append(module_maximum_size)
 
         # Store modules so we can use the exact same input for both algorithms
-        input_modules = self.soi.modules
+        input_modules = deep_copy_of_modules_list(
+            self.soi.modules, testmodule_cloner
+        )
 
         # Test packing with Guillotine algorithm
         expected_module_packing_metadata_guillotinebssfsas = [
@@ -255,7 +279,9 @@ class TestSOI(unittest.TestCase):
         )
 
         # Restore modules and test packing with MaxRects
-        self.soi.modules = input_modules
+        self.soi.modules = deep_copy_of_modules_list(
+            input_modules, testmodule_cloner
+        )
         expected_module_packing_metadata_maxrectsbl = [
             {"x": 0, "y": 0, "page": 1, "name": "tall_module"},
             {"x": 100, "y": 0, "page": 1, "name": "wide_module"},
@@ -387,3 +413,70 @@ class TestSOI(unittest.TestCase):
             ValueError,
             lambda: self.soi.update_properties(invalid_key="garbage"),
         )
+
+    def test_reorganize_attachments(self):
+        """Test that calling reorganize properly reorganized attachments.
+
+        Attachments should appear on their own pages, starting after the last
+        non-attachment page. The attachments should be placed at the top-left
+        corner.
+        """
+        self.soi.reorganize()
+
+        # Modules of SOI from self.setUp will all be placed on first page, so
+        # expect the three attachment modules also from self.setUp on pages
+        # 2,3,4
+        self.assertEqual(
+            self.soi.attachments[0]["meta"],
+            {"x": 0, "y": 0, "page": 2, "name": "tall_module"},
+        )
+        self.assertEqual(
+            self.soi.attachments[1]["meta"],
+            {"x": 0, "y": 0, "page": 3, "name": "wide_module"},
+        )
+        self.assertEqual(
+            self.soi.attachments[2]["meta"],
+            {"x": 0, "y": 0, "page": 4, "name": "big_module"},
+        )
+
+
+def deep_copy_of_modules_list(modules, widget_cloner):
+    """Get deep copy of modules list. Works for both modules and attachments.
+
+    This is necessary because copying lists in Python only works on one level
+    of nesting, such that:
+
+    ```python
+    a = [[1,2,3]
+    b = a.copy()
+    b[0][0] = 9
+    print(
+        "nested list in 'a' now updated "
+        "to '{}'!! Not what we want! ".format(a[0][0])
+    )
+    ```
+
+    There is a package copy that contains deepcopy for this, but we can't use
+    it because some of our objects "Can't be pickled", quoting the error.
+
+    Parameters
+    ----------
+    modules : list
+        List of modules.
+    widget_cloner : function
+        Function that given a Qt widget as a parameter can return a clone of
+        that widget. Example usage: `widget_cloner(mywidget)` should return a
+        widget that is a clone of mywidget.
+
+    Returns
+    -------
+    list
+        List of modules, deep copied from input list.
+    """
+    return [
+        {
+            "meta": module["meta"].copy(),
+            "widget": widget_cloner(module["widget"]),
+        }
+        for module in modules
+    ]