Repository: cobbler/cobbler (language: python)

Issue:
Items: Refactor base classes
## Linked Items

Fixes #3439 

## Description

The new item type "Templates" that will be introduced with #3295 doesn't need to have parents, children or things like `autoinstall_meta`. As such before we add this new item type we need to find a better base class that will then be used for the new item type.

Steps for this PR:

1. Make `NetworkInterface` live in its own module - #3739
2. Introduce `BaseItem` - #3758
3. Introduce `InheritableItem` - #3787
4. Introduce `BootableItem` - #3805
5. Make `NetworkInterface` a dedicated collection (with data migration)
6. Introduce `BootLoader` type
7. Introduce the option subobjects:
   - `VirtOptions`
   - `PowerOptions`
   - `IPv4Options` & `IPv6Options`
   - `DnsOptions`
   - `TftpOptions`

Each step should have a green CI so each commit can be individually validated.

## Behaviour changes

Old: <!-- This is the old way Cobbler behaved -->

New: <!-- This is the new way Cobbler behaves -->

## Category

This is related to a:

- [ ] Bugfix
- [ ] Feature
- [ ] Packaging
- [ ] Docs
- [ ] Code Quality
- [x] Refactoring
- [ ] Miscellaneous

## Tests

- [x] Unit-Tests were created
- [ ] System-Tests were created
- [ ] Code is already covered by Unit-Tests
- [x] Code is already covered by System-Tests
- [ ] No tests required 



Discussion / hints:
After #3252 and #3461 are merged I can finally work on this.
## Coverage summary from Codacy
### [See diff coverage on Codacy](https://app.codacy.com/gh/cobbler/cobbler/coverage/pull-requests/3440?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryPullRequest)

 | Coverage variation | Diff coverage | 
 | ------------- | ------------- |
 | :white_check_mark: **+0.03%** (target: -1.00%) | :white_check_mark: **89.69%** (target: 70.00%) |

<details>
<summary>Coverage variation details</summary>

| | Coverable lines | Covered lines | Coverage |
| ------------- | ------------- | ------------- | ------------- |
| Common ancestor commit (69889afb93485ea78424c995415cdfddde32024e) | 15525 | 10242 | 65.97% | |
| Head commit (b8be7af72214cbb7d7e95f71afd8c66f78c7ac59) | 15653 (+128) | 10331 (+89) | 66.00% (**+0.03%**) |

**Coverage variation** is the difference between the coverage for the head and common ancestor commits of the pull request branch: `<coverage of head commit> - <coverage of common ancestor commit>`
</details>

<details>
<summary>Diff coverage details</summary>

| | Coverable lines | Covered lines | Diff coverage |
| ------------- | ------------- | ------------- | ------------- |
| Pull request (#3440) | 359 | 322 | **89.69%** |

**Diff coverage** is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: `<covered lines added or modified>/<coverable lines added or modified> * 100%`
</details>

#### [See your quality gate settings](https://app.codacy.com/gh/cobbler/cobbler/settings/quality?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryGates)&nbsp;&nbsp;&nbsp;&nbsp;[Change summary preferences](https://app.codacy.com/gh/cobbler/cobbler/settings/integrations?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummarySettings)
<sub>You may notice some variations in coverage metrics with the latest Coverage engine update. For more details, visit the [documentation](https://docs.codacy.com/release-notes/cloud/cloud-2023-11-23-new-coverage-engine-status-checks/#differences-in-coverage-metrics-between-the-old-and-new-coverage-engines)</sub>

<!-- 108f945d-e553-4918-8ccf-027c6d4acc2c -->
@tpw56j I am struggling a bit with the implementation of your cache for the last day. To my understanding of the history of Cobbler, the object type `Repo` doesn't have descendants but you tested for it in your tests. Could you please give me a bit of context for this?
@SchoolGuy In implementing caching for proper object cache invalidation, the main role is not logical inheritance, but how one type depends on another. Dependency rules are defined in the `TYPE_DEPENDENCIES` dict. For `Repo`, the directly dependent type is `Profile`.
`Item.descendants()` builds a dependency tree along which caches of dependent objects are invalidated. This tree is not a logical inheritance tree, which is built according to the `LOGICAL_INHERITANCE` dict.

In addition, since dependency rules are known only through `TYPE_DEPENDENCIES`, for reliable testing that we will not get incorrect results from the cache, a complete set of all possible Cobbler objects is required.
@tpw56j Ahhhh thanks now I see where my logic error is. Then my assumption that `Repo` has no parents and children was correct but I forgot that `descendants` also covers arbitrary references. I'll try to change the base class and see if the results improve.
The test was successful. However, that means that there never can be an item type that has a reference to another item type that is not inheritable. I would very much like to remove the "parent"/"children" logic from objects like `Repo` (and also other item types that are not there yet) but it seems that the current implementation makes that impossible. If you would have a spontaneous idea I would appreciate it. If not I will think about how to improve the situation.

I do believe that "parent"/"children" is different from a pure reference like `Profile` with `Repo` is already having.

Edit: And yes I do know that `utils.blender()` requires `grab_tree()` but I am working on getting rid of that too. The only thing a `Repo` can inherit from is the settings and this is a special case anyway.
It seems to me that the current implementation of defining logical inheritance rules and type dependencies via `LOGICAL_INHERITANCE` and `TYPE_DEPENDENCIES` is ugly. I did this because it was easier to implement the caching logic code. But as a result, this code is more difficult to maintain. Perhaps the code could be improved if, for example, decorators were somehow used instead.

`TYPE_DEPENDENCIES` describes formal dependencies between types, including for cascade deletion. This also allows you to narrow down the list of objects to invalidate the cache. If the `InheritableProperty` property of a `Repo` object changes, all caches of `Profile` objects that use them will be invalidated. I think this is correct, otherwise it can lead to subtle errors.

Much depends on what proportion of object caches need to be invalidated. If you know in advance that, for example, the dependency tree will contain 90% percent of all objects, then without building the tree you can simply reset the caches of all objects, as is done now when changing the `settings`. This will be faster than building a dependency tree and traversing it.

In general, I would not be afraid of excessive cache invalidation, even if this in some cases leads to a decrease in cache efficiency. It seems to me that reliability and the absence of false positive cache responses  are much more important.
@tpw56j Please correct my thought if I am wrong but we want to speed up the `to_dict()` function especially for the `resolved=True` case. The only dependency that a `Profile` has on `Repo` is that the object has to exist. This is very different to my understanding what you are implying. Your comment would only be correct in case that we would cache the result of `to_dict()` as a whole. But since we are just caching individual properties we should be fine the the relationship of `Profile` and `Repo`.

I very much agree with your observation in the case of `System` --> `Profile` --> `Distro` since there also a change in properties affects children and parents alike. We have more constraints than the actual existence of the objects.
We cache the entire to_dict result:
```python
    def to_dict(self, resolved: bool = False) -> Dict[Any, Any]:
        """
        This converts everything in this object to a dictionary.

        :param resolved: If this is True, Cobbler will resolve the values to its final form, rather than give you the
                     objects raw value.
        :return: A dictionary with all values present in this object.
        """
        if not self.inmemory:
            self.deserialize()
        cached_result = self.cache.get_dict_cache(resolved)
        if cached_result is not None:
            return cached_result
```
More precisely, we cache 2 results: one with `resolved=True` and one with `resolved=False`
@tpw56j Correct we can do that. However, that doesn't help us with removing the need to define the whole set of methods with the `Repo` object. I would like to make this object slimmer as it has no references to other items that are being serialized as such the concept of "children" doesn't make sense to maintain for it.
Yes, the concept of “children” does not apply in this case, but the concept of “dependency” remains. And this concept must be preserved at least in order to be able to recursively delete or prohibit the deletion of the repo along with all the objects that use it.
@tpw56j I do agree but my initial question was how to separate those two concepts in your mind. At the moment it seems we don't differentiate this.

This difference will become even more important when we begin to use real database schemas.
It seems to me that the easiest method to make objects thinner is to create a hierarchy of classes. Something like this:
```
        Item
        /  \
    Repo ChildItem 
         /     \
      BootItem  Menu
      /  |  \  
Distro Image Profile System
```
Item: only basic properties (uuid, name..).
ChildItem: the additional property children
BootItem: the properties that are needed to boot the OS (kernel_options, ..)

@tpw56j That is exactly what I currently have and due to the fact that I moved `descendants` into `ChildItem`/`InheritableItem` it doesn't work.
@agraul & I just a Slack Huddle about this problem. I will give his idea a shot. He mentioned that an external spectator could be responsible for signalling the items whose caches need to be updated.

Concretely this would mean that we use the `__setattr__` to do a call to the `CobblerAPI` that contains the item type, the object uid/name and the modified attribute. The API could then use a dedicated module to populate the cache invalidation to the directly affected objects which in turn could use this API to recursively forward this signal.

The advantage that I see with this is that the logic to invalidate the caches is now outside of any abstract or specific item and as such the `TYPE_DEPENDENCIES` dict and its attached logic can be moved outside of the item tree as a whole.

I have no idea if that works in the end or not but it is worth a try in my eyes.
`descendants` and `TYPE_DEPENDENCIES` must remain in the `Item`; they also describes dependencies outside of the parent/child relationship. But it needs to be overridden in `ChildItem`/`InheritableItem`.
Item:
```python
    def descendants(self) -> List["ITEM_UNION"]:
        results = set(self)
        for item_type in Item.TYPE_DEPENDENCIES[self.COLLECTION_TYPE]:
            dep_type_items = self.api.find_items(
                item_type[0], {item_type[1]: self.name}, return_list=True
            )
            if dep_type_items is None or not isinstance(dep_type_items, list):
                raise ValueError("Expected list to be returned by find_items")
            results.update(dep_type_items)
            for dep_item in dep_type_items:
                results.update(dep_item.descendants)
        return list(results)
```
ChildItem:
```python
   def descendants(self) -> List["ITEM_UNION"]:
        childs = self.tree_walk()
        results = set(childs)
        childs.append(self)  # type: ignore
        for child in childs:
            results.update(child.descendants)
        return list(results)
```
Edit: We also need to add a call to the `descendants` method from `Item`

ChildItem:
```python
   def descendants(self) -> List["ITEM_UNION"]:
        childs = self.tree_walk()
        results = set(childs)
        childs.append(self)  # type: ignore
        for child in childs:
            results.update(child.descendants)
            results.update(Item.descendants(child))
        return list(results)
```

## Coverage summary from Codacy
### [See diff coverage on Codacy](https://app.codacy.com/gh/cobbler/cobbler/coverage/pull-requests/3440?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryPullRequest)

 | Coverage variation | Diff coverage | 
 | ------------- | ------------- |
 | :white_check_mark: **+0.36%** (target: -1.00%) | :white_check_mark: **81.54%** (target: 70.00%) |

<details>
<summary>Coverage variation details</summary>

| | Coverable lines | Covered lines | Coverage |
| ------------- | ------------- | ------------- | ------------- |
| Common ancestor commit (69889afb93485ea78424c995415cdfddde32024e) | 15525 | 10242 | 65.97% | |
| Head commit (20dc7b3ec34232305a074738b1a5410e1ae747ac) | 15683 (+158) | 10403 (+161) | 66.33% (**+0.36%**) |

**Coverage variation** is the difference between the coverage for the head and common ancestor commits of the pull request branch: `<coverage of head commit> - <coverage of common ancestor commit>`
</details>

<details>
<summary>Diff coverage details</summary>

| | Coverable lines | Covered lines | Diff coverage |
| ------------- | ------------- | ------------- | ------------- |
| Pull request (#3440) | 65 | 53 | **81.54%** |

**Diff coverage** is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: `<covered lines added or modified>/<coverable lines added or modified> * 100%`
</details>

#### [See your quality gate settings](https://app.codacy.com/gh/cobbler/cobbler/settings/quality?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummaryGates)&nbsp;&nbsp;&nbsp;&nbsp;[Change summary preferences](https://app.codacy.com/gh/cobbler/cobbler/settings/integrations?utm_source=github.com&utm_medium=coverageSummary&utm_campaign=coverageSummarySettings)
<sub>You may notice some variations in coverage metrics with the latest Coverage engine update. For more details, visit the [documentation](https://docs.codacy.com/release-notes/cloud/cloud-2023-11-23-new-coverage-engine-status-checks/#differences-in-coverage-metrics-between-the-old-and-new-coverage-engines)</sub>

<!-- 108f945d-e553-4918-8ccf-027c6d4acc2c -->
I have updated the description to reflect the new plan. The last point of the numbered list will be done in this PR. This PR will contain the changelog for this undertaking.

Fix patch:
diff --git a/cobbler/api.py b/cobbler/api.py
index c2c8146acf..d958a23dd2 100644
--- a/cobbler/api.py
+++ b/cobbler/api.py
@@ -159,11 +159,11 @@
 from cobbler.decorator import InheritableDictProperty, InheritableProperty
 from cobbler.items import distro
 from cobbler.items import image as image_module
-from cobbler.items import item as item_base
 from cobbler.items import menu
 from cobbler.items import profile as profile_module
 from cobbler.items import repo
 from cobbler.items import system as system_module
+from cobbler.items.abstract import bootable_item as item_base
 from cobbler.items.abstract.inheritable_item import InheritableItem
 from cobbler.utils import filesystem_helpers, input_converters, signatures
 
@@ -176,7 +176,7 @@
     from cobbler.cobbler_collections.repos import Repos
     from cobbler.cobbler_collections.systems import Systems
     from cobbler.items.abstract.base_item import BaseItem
-    from cobbler.items.item import Item
+    from cobbler.items.abstract.bootable_item import BootableItem
 
 
 # notes on locking:
@@ -481,7 +481,7 @@ def clean_items_cache(self, obj: Union[settings.Settings, Dict[str, Any]]):
 
     # ==========================================================
 
-    def get_item(self, what: str, name: str) -> Optional["Item"]:
+    def get_item(self, what: str, name: str) -> Optional["BootableItem"]:
         """
         Get a general item.
 
@@ -988,7 +988,7 @@ def rename_menu(self, ref: "menu.Menu", newname: str) -> None:
 
     def new_item(
         self, what: str = "", is_subobject: bool = False, **kwargs: Any
-    ) -> "Item":
+    ) -> "BootableItem":
         """
         Creates a new (unconfigured) object. The object is not persisted.
 
@@ -1250,7 +1250,7 @@ def find_items(
         name: str = "",
         return_list: bool = True,
         no_errors: bool = False,
-    ) -> Optional[Union["Item", List["Item"]]]:
+    ) -> Optional[Union["BootableItem", List["BootableItem"]]]:
         """
         This is the abstract base method for finding object int the api. It should not be used by external resources.
         Please reefer to the specific implementations of this method called ``find_<object type>``.
@@ -1299,7 +1299,7 @@ def __find_with_collection(
 
     def __find_without_collection(
         self, name: str, return_list: bool, no_errors: bool, criteria: Dict[Any, Any]
-    ) -> Optional[Union["Item", List["Item"]]]:
+    ) -> Optional[Union["BootableItem", List["BootableItem"]]]:
         collections = [
             "distro",
             "profile",
@@ -1320,7 +1320,7 @@ def __find_without_collection(
                 return match
         return None
 
-    def __find_by_name(self, name: str) -> Optional["Item"]:
+    def __find_by_name(self, name: str) -> Optional["BootableItem"]:
         """
         This is a magic method which just searches all collections for the specified name directly,
         :param name: The name of the item(s).
@@ -1618,7 +1618,7 @@ def signature_update(self) -> None:
 
     def dump_vars(
         self,
-        obj: "item_base.Item",
+        obj: "item_base.BootableItem",
         formatted_output: bool = False,
         remove_dicts: bool = False,
     ) -> Union[Dict[str, Any], str]:
@@ -1706,7 +1706,7 @@ def get_repo_config_for_system(self, obj: "BaseItem") -> str:
 
     # ==========================================================================
 
-    def get_template_file_for_profile(self, obj: "Item", path: str) -> str:
+    def get_template_file_for_profile(self, obj: "BootableItem", path: str) -> str:
         """
         Get the template for the specified profile.
 
@@ -1719,7 +1719,7 @@ def get_template_file_for_profile(self, obj: "Item", path: str) -> str:
             return template_results[path]
         return "# template path not found for specified profile"
 
-    def get_template_file_for_system(self, obj: "Item", path: str) -> str:
+    def get_template_file_for_system(self, obj: "BootableItem", path: str) -> str:
         """
         Get the template for the specified system.
 
diff --git a/cobbler/cli.py b/cobbler/cli.py
index 56167eac33..d5c92fb93f 100644
--- a/cobbler/cli.py
+++ b/cobbler/cli.py
@@ -180,16 +180,6 @@
         0,
         "dict",
     ],
-    [
-        "boot_files",
-        {},
-        0,
-        "TFTP Boot Files",
-        True,
-        "Files copied into tftpboot beyond the kernel/initrd",
-        0,
-        "list",
-    ],
     [
         "boot_loaders",
         "<<inherit>>",
@@ -211,16 +201,6 @@
         "str",
     ],
     ["comment", "", 0, "Comment", True, "Free form text description", 0, "str"],
-    [
-        "fetchable_files",
-        {},
-        0,
-        "Fetchable Files",
-        True,
-        "Templates for tftp or wget/curl",
-        0,
-        "list",
-    ],
     [
         "initrd",
         None,
@@ -503,16 +483,6 @@
         0,
         "dict",
     ],
-    [
-        "boot_files",
-        {},
-        "<<inherit>>",
-        "TFTP Boot Files",
-        True,
-        "Files copied into tftpboot beyond the kernel/initrd",
-        0,
-        "list",
-    ],
     [
         "boot_loaders",
         "<<inherit>>",
@@ -564,16 +534,6 @@
         0,
         "bool",
     ],
-    [
-        "fetchable_files",
-        {},
-        "<<inherit>>",
-        "Fetchable Files",
-        True,
-        "Templates for tftp or wget/curl",
-        0,
-        "dict",
-    ],
     [
         "kernel_options",
         {},
@@ -991,16 +951,6 @@
         0,
         "dict",
     ],
-    [
-        "boot_files",
-        {},
-        "<<inherit>>",
-        "TFTP Boot Files",
-        True,
-        "Files copied into tftpboot beyond the kernel/initrd",
-        0,
-        "list",
-    ],
     [
         "boot_loaders",
         "<<inherit>>",
@@ -1022,16 +972,6 @@
         0,
         "bool",
     ],
-    [
-        "fetchable_files",
-        {},
-        "<<inherit>>",
-        "Fetchable Files",
-        True,
-        "Templates for tftp or wget/curl",
-        0,
-        "dict",
-    ],
     ["gateway", "", 0, "Gateway", True, "", 0, "str"],
     ["hostname", "", 0, "Hostname", True, "", 0, "str"],
     ["image", None, 0, "Image", True, "Parent image (if not a profile)", 0, "str"],
diff --git a/cobbler/cobbler_collections/collection.py b/cobbler/cobbler_collections/collection.py
index 691e6e2e76..2b5d225460 100644
--- a/cobbler/cobbler_collections/collection.py
+++ b/cobbler/cobbler_collections/collection.py
@@ -25,10 +25,9 @@
 
 from cobbler import enums, utils
 from cobbler.cexceptions import CX
-from cobbler.items import distro, image
-from cobbler.items import item as item_base
-from cobbler.items import menu, profile, repo, system
+from cobbler.items import distro, image, menu, profile, repo, system
 from cobbler.items.abstract.base_item import BaseItem
+from cobbler.items.abstract.inheritable_item import InheritableItem
 
 if TYPE_CHECKING:
     from cobbler.actions.sync import CobblerSync
@@ -350,7 +349,7 @@ def rename(
             self.listing[newname] = ref
             self.add_to_indexes(ref)
 
-        for dep_type in item_base.Item.TYPE_DEPENDENCIES[ref.COLLECTION_TYPE]:
+        for dep_type in InheritableItem.TYPE_DEPENDENCIES[ref.COLLECTION_TYPE]:
             items = self.api.find_items(
                 dep_type[0], {dep_type[1]: oldname}, return_list=True
             )
diff --git a/cobbler/items/abstract/base_item.py b/cobbler/items/abstract/base_item.py
index 850ac8130a..394acffc58 100644
--- a/cobbler/items/abstract/base_item.py
+++ b/cobbler/items/abstract/base_item.py
@@ -378,7 +378,7 @@ def _resolve(self, property_name: str) -> Any:
     def _remove_depreacted_dict_keys(cls, dictionary: Dict[Any, Any]) -> None:
         """
         This method does remove keys which should not be deserialized and are only there for API compatibility in
-        :meth:`~cobbler.items.item.Item.to_dict`.
+        :meth:`~cobbler.items.abstract.base_item.BaseItem.to_dict`.
 
         :param dictionary: The dict to update
         """
@@ -485,8 +485,8 @@ def find_match_single_key(
 
     def serialize(self) -> Dict[str, Any]:
         """
-        This method is a proxy for :meth:`~cobbler.items.item.Item.to_dict` and contains additional logic for
-        serialization to a persistent location.
+        This method is a proxy for :meth:`~cobbler.items.abstract.base_item.BaseItem.to_dict` and contains additional
+        logic for serialization to a persistent location.
 
         :return: The dictionary with the information for serialization.
         """
diff --git a/cobbler/items/item.py b/cobbler/items/abstract/bootable_item.py
similarity index 85%
rename from cobbler/items/item.py
rename to cobbler/items/abstract/bootable_item.py
index 1464ef48b7..88e3ee6301 100644
--- a/cobbler/items/item.py
+++ b/cobbler/items/abstract/bootable_item.py
@@ -4,9 +4,10 @@
 Changelog:
 
 V3.4.0 (unreleased):
+    * Renamed to BootableItem
     * (Re-)Added Cache implementation with the following new methods and properties:
         * ``cache``
-        * ``inmemery``
+        * ``inmemory``
         * ``clean_cache()``
     * Overhauled the parent/child system:
         * ``children`` is now inside ``item.py``.
@@ -18,6 +19,8 @@
         * mgmt_classes
         * mgmt_parameters
         * last_cached_mtime
+        * fetchable_files
+        * boot_files
 V3.3.4 (unreleased):
     * No changes
 V3.3.3:
@@ -117,14 +120,14 @@
 T = TypeVar("T")
 
 
-class Item(InheritableItem, ABC):
+class BootableItem(InheritableItem, ABC):
     """
-    An Item is a serializable thing that can appear in a Collection
+    A BootableItem is a serializable thing that can appear in a Collection
     """
 
     # Constants
-    TYPE_NAME = "generic"
-    COLLECTION_TYPE = "generic"
+    TYPE_NAME = "bootable_abstract"
+    COLLECTION_TYPE = "bootable_abstract"
 
     def __init__(
         self, api: "CobblerAPI", *args: Any, is_subobject: bool = False, **kwargs: Any
@@ -140,9 +143,7 @@ def __init__(
         self._kernel_options: Union[Dict[Any, Any], str] = {}
         self._kernel_options_post: Union[Dict[Any, Any], str] = {}
         self._autoinstall_meta: Union[Dict[Any, Any], str] = {}
-        self._fetchable_files: Union[Dict[Any, Any], str] = {}
-        self._boot_files: Union[Dict[Any, Any], str] = {}
-        self._template_files: Dict[str, Any] = {}
+        self._template_files: Dict[str, str] = {}
         self._inmemory = True
 
         if len(kwargs) > 0:
@@ -159,7 +160,7 @@ def __setattr__(self, name: str, value: Any):
         :name: The attribute name.
         :value: The attribute value.
         """
-        if Item._is_dict_key(name) and self._has_initialized:
+        if BootableItem._is_dict_key(name) and self._has_initialized:
             self.clean_cache(name)
         super().__setattr__(name, value)
 
@@ -217,7 +218,7 @@ def _resolve_enum(
         self, property_name: str, enum_type: Type[enums.ConvertableEnum]
     ) -> Any:
         """
-        See :meth:`~cobbler.items.item.Item._resolve`
+        See :meth:`~cobbler.items.abstract.bootable_item.BootableItem._resolve`
         """
         attribute_value, settings_name = self.__common_resolve(property_name)
         unwrapped_value = getattr(attribute_value, "value", "")
@@ -381,11 +382,14 @@ def autoinstall_meta(self, options: Dict[Any, Any]):
         self._autoinstall_meta = self._deduplicate_dict("autoinstall_meta", value)  # type: ignore
 
     @LazyProperty
-    def template_files(self) -> Dict[Any, Any]:
+    def template_files(self) -> Dict[str, str]:
         """
         File mappings for built-in configuration management. The keys are the template source files and the value is the
         destination. The destination must be inside the bootloc (most of the time TFTP server directory).
 
+        This property took over the duties of boot_files additionaly. During signature import the values of "boot_files"
+        will be added to "template_files".
+
         :getter: The dictionary with name-path key-value pairs.
         :setter: A dict. If not a dict must be a str which is split by
                  :meth:`~cobbler.utils.input_converters.input_string_or_dict`. Raises ``TypeError`` otherwise.
@@ -393,7 +397,7 @@ def template_files(self) -> Dict[Any, Any]:
         return self._template_files
 
     @template_files.setter
-    def template_files(self, template_files: Union[str, Dict[Any, Any]]) -> None:
+    def template_files(self, template_files: Union[str, Dict[str, str]]) -> None:
         """
         A comma seperated list of source=destination templates that should be generated during a sync.
 
@@ -407,61 +411,6 @@ def template_files(self, template_files: Union[str, Dict[Any, Any]]) -> None:
         except TypeError as error:
             raise TypeError("invalid template files specified") from error
 
-    @LazyProperty
-    def boot_files(self) -> Dict[Any, Any]:
-        """
-        Files copied into tftpboot beyond the kernel/initrd. These get rendered via Cheetah/Jinja and must be text
-        based.
-
-        :getter: The dictionary with name-path key-value pairs.
-        :setter: A dict. If not a dict must be a str which is split by
-                 :meth:`~cobbler.utils.input_converters.input_string_or_dict`. Raises ``TypeError`` otherwise.
-        """
-        return self._resolve_dict("boot_files")
-
-    @boot_files.setter
-    def boot_files(self, boot_files: Dict[Any, Any]) -> None:
-        """
-        A comma separated list of req_name=source_file_path that should be fetchable via tftp.
-
-        .. note:: This property can be set to ``<<inherit>>``.
-
-        :param boot_files: The new value for the boot files used by the item.
-        """
-        try:
-            self._boot_files = input_converters.input_string_or_dict(
-                boot_files, allow_multiples=False
-            )
-        except TypeError as error:
-            raise TypeError("invalid boot files specified") from error
-
-    @InheritableDictProperty
-    def fetchable_files(self) -> Dict[Any, Any]:
-        """
-        A comma seperated list of ``virt_name=path_to_template`` that should be fetchable via tftp or a webserver
-
-        .. note:: This property can be set to ``<<inherit>>``.
-
-        :getter: The dictionary with name-path key-value pairs.
-        :setter: A dict. If not a dict must be a str which is split by
-                 :meth:`~cobbler.utils.input_converters.input_string_or_dict`. Raises ``TypeError`` otherwise.
-        """
-        return self._resolve_dict("fetchable_files")
-
-    @fetchable_files.setter  # type: ignore[no-redef]
-    def fetchable_files(self, fetchable_files: Union[str, Dict[Any, Any]]):
-        """
-        Setter for the fetchable files.
-
-        :param fetchable_files: Files which will be made available to external users.
-        """
-        try:
-            self._fetchable_files = input_converters.input_string_or_dict(
-                fetchable_files, allow_multiples=False
-            )
-        except TypeError as error:
-            raise TypeError("invalid fetchable files specified") from error
-
     def dump_vars(
         self, formatted_output: bool = True, remove_dicts: bool = False
     ) -> Union[Dict[str, Any], str]:
@@ -493,7 +442,10 @@ def deserialize_ancestor(ancestor_item_type: str, ancestor_name: str):
 
         item_dict = self.api.deserialize_item(self)
         if item_dict["inmemory"]:
-            for ancestor_item_type, ancestor_deps in Item.TYPE_DEPENDENCIES.items():
+            for (
+                ancestor_item_type,
+                ancestor_deps,
+            ) in InheritableItem.TYPE_DEPENDENCIES.items():
                 for ancestor_dep in ancestor_deps:
                     if self.TYPE_NAME == ancestor_dep.dependant_item_type:
                         attr_name = ancestor_dep.dependant_type_attribute
@@ -521,7 +473,7 @@ def _clean_dict_cache(self, name: Optional[str]):
             attr = getattr(type(self), name[1:])
             if (
                 isinstance(attr, (InheritableProperty, InheritableDictProperty))
-                and self.COLLECTION_TYPE != Item.COLLECTION_TYPE
+                and self.COLLECTION_TYPE != InheritableItem.COLLECTION_TYPE  # type: ignore
                 and self.api.get_items(self.COLLECTION_TYPE).get(self.name) is not None
             ):
                 # Invalidating "resolved" caches
diff --git a/cobbler/items/abstract/inheritable_item.py b/cobbler/items/abstract/inheritable_item.py
index 2cb47ff174..a04826825f 100644
--- a/cobbler/items/abstract/inheritable_item.py
+++ b/cobbler/items/abstract/inheritable_item.py
@@ -274,11 +274,11 @@ def children(self) -> List["InheritableItem"]:
         :getter: An empty list in case of items which don't have logical children.
         :setter: Replace the list of children completely with the new provided one.
         """
-        results: List[Any] = []
+        results: List[InheritableItem] = []
         list_items = self.api.get_items(self.COLLECTION_TYPE)
         for obj in list_items:
             if obj.get_parent == self._name:  # type: ignore
-                results.append(obj)
+                results.append(obj)  # type: ignore
         return results
 
     def tree_walk(self) -> List["InheritableItem"]:
@@ -286,7 +286,7 @@ def tree_walk(self) -> List["InheritableItem"]:
         Get all children related by parent/child relationship.
         :return: The list of children objects.
         """
-        results: List[Any] = []
+        results: List["InheritableItem"] = []
         for child in self.children:
             results.append(child)
             results.extend(child.tree_walk())
diff --git a/cobbler/items/abstract/item_cache.py b/cobbler/items/abstract/item_cache.py
index e8b774a187..63707e7977 100644
--- a/cobbler/items/abstract/item_cache.py
+++ b/cobbler/items/abstract/item_cache.py
@@ -43,7 +43,7 @@ def get_dict_cache(self, resolved: bool) -> Optional[Dict[str, Any]]:
         """
         Gettinging the dict cache.
 
-        :param resolved: "resolved" parameter for Item.to_dict().
+        :param resolved: "resolved" parameter for BaseItem.to_dict().
         :return: The cache value for the object, or None if not set.
         """
         if self.settings.cache_enabled:
@@ -55,7 +55,7 @@ def set_dict_cache(self, value: Optional[Dict[str, Any]], resolved: bool):
         Setter for the dict cache.
 
         :param value: Sets the value for the dict cache.
-        :param resolved: "resolved" parameter for Item.to_dict().
+        :param resolved: "resolved" parameter for BaseItem.to_dict().
         """
         if self.settings.cache_enabled:
             self._cached_dict[resolved] = value
@@ -70,7 +70,7 @@ def clean_dict_cache(self):
 
     def clean_cache(self):
         """
-        Cleaninig the Item cache.
+        Cleaning the Item cache.
         """
         if self.settings.cache_enabled:
             self.clean_dict_cache()
diff --git a/cobbler/items/distro.py b/cobbler/items/distro.py
index d6c24c5e28..d98fbecd9f 100644
--- a/cobbler/items/distro.py
+++ b/cobbler/items/distro.py
@@ -129,14 +129,14 @@
 from cobbler import enums, grub, utils, validate
 from cobbler.cexceptions import CX
 from cobbler.decorator import InheritableProperty, LazyProperty
-from cobbler.items import item
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.utils import input_converters, signatures
 
 if TYPE_CHECKING:
     from cobbler.api import CobblerAPI
 
 
-class Distro(item.Item):
+class Distro(BootableItem):
     """
     A Cobbler distribution object
     """
@@ -165,7 +165,6 @@ def __init__(self, api: "CobblerAPI", *args: Any, **kwargs: Any):
         self._os_version = ""
         self._redhat_management_key = enums.VALUE_INHERITED
         self._source_repos = []
-        self._fetchable_files = {}
         self._remote_boot_kernel = ""
         self._remote_grub_kernel = ""
         self._remote_boot_initrd = ""
@@ -183,7 +182,7 @@ def __getattr__(self, name: str) -> Any:
         raise AttributeError(f'Attribute "{name}" did not exist on object type Distro.')
 
     #
-    # override some base class methods first (item.Item)
+    # override some base class methods first (BootableItem)
     #
 
     def make_clone(self):
@@ -203,7 +202,7 @@ def make_clone(self):
     @classmethod
     def _remove_depreacted_dict_keys(cls, dictionary: Dict[Any, Any]):
         r"""
-        See :meth:`~cobbler.items.item.Item._remove_deprecated_dict_keys`.
+        See :meth:`~cobbler.items.item.BootableItem._remove_deprecated_dict_keys`.
 
         :param dictionary: The dict to update
         """
diff --git a/cobbler/items/image.py b/cobbler/items/image.py
index cb8a33ac62..e06b6b3259 100644
--- a/cobbler/items/image.py
+++ b/cobbler/items/image.py
@@ -132,14 +132,14 @@
 from cobbler import autoinstall_manager, enums, validate
 from cobbler.cexceptions import CX
 from cobbler.decorator import InheritableProperty, LazyProperty
-from cobbler.items import item
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.utils import input_converters, signatures
 
 if TYPE_CHECKING:
     from cobbler.api import CobblerAPI
 
 
-class Image(item.Item):
+class Image(BootableItem):
     """
     A Cobbler Image. Tracks a virtual or physical image, as opposed to a answer file (autoinst) led installation.
     """
@@ -188,7 +188,7 @@ def __getattr__(self, name: str):
         raise AttributeError(f'Attribute "{name}" did not exist on object type Image.')
 
     #
-    # override some base class methods first (item.Item)
+    # override some base class methods first (BootableItem)
     #
 
     def make_clone(self):
diff --git a/cobbler/items/menu.py b/cobbler/items/menu.py
index 11f2125b76..165a8b3db7 100644
--- a/cobbler/items/menu.py
+++ b/cobbler/items/menu.py
@@ -31,13 +31,13 @@
 from typing import TYPE_CHECKING, Any
 
 from cobbler.decorator import LazyProperty
-from cobbler.items import item
+from cobbler.items.abstract.bootable_item import BootableItem
 
 if TYPE_CHECKING:
     from cobbler.api import CobblerAPI
 
 
-class Menu(item.Item):
+class Menu(BootableItem):
     """
     A Cobbler menu object.
     """
@@ -63,7 +63,7 @@ def __init__(self, api: "CobblerAPI", *args: Any, **kwargs: Any) -> None:
             self._has_initialized = True
 
     #
-    # override some base class methods first (item.Item)
+    # override some base class methods first (BootableItem)
     #
 
     def make_clone(self) -> "Menu":
diff --git a/cobbler/items/network_interface.py b/cobbler/items/network_interface.py
index 7599517e87..88f6d18a08 100644
--- a/cobbler/items/network_interface.py
+++ b/cobbler/items/network_interface.py
@@ -192,8 +192,8 @@ def to_dict(self, resolved: bool = False) -> Dict[str, Any]:
     # These two methods are currently not used, but we do want to use them in the future, so let's define them.
     def serialize(self) -> Dict[str, Any]:
         """
-        This method is a proxy for :meth:`~cobbler.items.item.Item.to_dict` and contains additional logic for
-        serialization to a persistent location.
+        This method is a proxy for :meth:`~cobbler.items.abstract.base_item.BaseItem.to_dict` and contains additional
+        logic for serialization to a persistent location.
 
         :return: The dictionary with the information for serialization.
         """
@@ -201,7 +201,7 @@ def serialize(self) -> Dict[str, Any]:
 
     def deserialize(self, interface_dict: Dict[str, Any]):
         """
-        This is currently a proxy for :py:meth:`~cobbler.items.item.Item.from_dict` .
+        This is currently a proxy for :py:meth:`~cobbler.items.abstract.base_item.BaseItem.from_dict` .
 
         :param interface_dict: The dictionary with the data to deserialize.
         """
diff --git a/cobbler/items/profile.py b/cobbler/items/profile.py
index 3861c4e0dd..4f71fdfd02 100644
--- a/cobbler/items/profile.py
+++ b/cobbler/items/profile.py
@@ -167,7 +167,7 @@
 from cobbler import autoinstall_manager, enums, validate
 from cobbler.cexceptions import CX
 from cobbler.decorator import InheritableProperty, LazyProperty
-from cobbler.items import item
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.items.distro import Distro
 from cobbler.utils import input_converters
 
@@ -175,7 +175,7 @@
     from cobbler.api import CobblerAPI
 
 
-class Profile(item.Item):
+class Profile(BootableItem):
     """
     A Cobbler profile object.
     """
@@ -193,7 +193,6 @@ def __init__(self, api: "CobblerAPI", *args: Any, **kwargs: Any) -> None:
         # Prevent attempts to clear the to_dict cache before the object is initialized.
         self._has_initialized = False
 
-        self._template_files = {}
         self._autoinstall = enums.VALUE_INHERITED
         self._boot_loaders: Union[List[str], str] = enums.VALUE_INHERITED
         self._dhcp_tag = ""
@@ -220,9 +219,7 @@ def __init__(self, api: "CobblerAPI", *args: Any, **kwargs: Any) -> None:
         self._virt_ram: Union[str, int] = enums.VALUE_INHERITED
         self._virt_type: Union[str, enums.VirtType] = enums.VirtType.INHERITED
 
-        # Overwrite defaults from item.py
-        self._boot_files: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED
-        self._fetchable_files: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED
+        # Overwrite defaults from bootable_item.py
         self._autoinstall_meta: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED
         self._kernel_options: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED
         self._kernel_options_post: Union[Dict[Any, Any], str] = enums.VALUE_INHERITED
@@ -249,7 +246,7 @@ def __getattr__(self, name: str) -> Any:
         )
 
     #
-    # override some base class methods first (item.Item)
+    # override some base class methods first (BootableItem)
     #
 
     def make_clone(self):
diff --git a/cobbler/items/repo.py b/cobbler/items/repo.py
index 3ef282c73c..f5b2ac75bd 100644
--- a/cobbler/items/repo.py
+++ b/cobbler/items/repo.py
@@ -98,14 +98,14 @@
 from cobbler import enums
 from cobbler.cexceptions import CX
 from cobbler.decorator import InheritableProperty, LazyProperty
-from cobbler.items import item
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.utils import input_converters
 
 if TYPE_CHECKING:
     from cobbler.api import CobblerAPI
 
 
-class Repo(item.Item):
+class Repo(BootableItem):
     """
     A Cobbler repo object.
     """
@@ -146,7 +146,7 @@ def __init__(self, api: "CobblerAPI", *args: Any, **kwargs: Any) -> None:
             self._has_initialized = True
 
     #
-    # override some base class methods first (item.Item)
+    # override some base class methods first (BootableItem)
     #
 
     def make_clone(self) -> "Repo":
diff --git a/cobbler/items/system.py b/cobbler/items/system.py
index e6565f5f60..46cbcf4663 100644
--- a/cobbler/items/system.py
+++ b/cobbler/items/system.py
@@ -210,7 +210,7 @@
 from cobbler import autoinstall_manager, enums, power_manager, utils, validate
 from cobbler.cexceptions import CX
 from cobbler.decorator import InheritableProperty, LazyProperty
-from cobbler.items.item import Item
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.items.network_interface import NetworkInterface
 from cobbler.utils import filesystem_helpers, input_converters
 
@@ -218,7 +218,7 @@
     from cobbler.api import CobblerAPI
 
 
-class System(Item):
+class System(BootableItem):
     """
     A Cobbler system object.
     """
@@ -277,10 +277,8 @@ def __init__(self, api: "CobblerAPI", *args: Any, **kwargs: Any) -> None:
         self._serial_baud_rate = enums.BaudRates.DISABLED
         self._display_name = ""
 
-        # Overwrite defaults from item.py
+        # Overwrite defaults from bootable_item.py
         self._owners = enums.VALUE_INHERITED
-        self._boot_files = enums.VALUE_INHERITED
-        self._fetchable_files = enums.VALUE_INHERITED
         self._autoinstall_meta = enums.VALUE_INHERITED
         self._kernel_options = enums.VALUE_INHERITED
         self._kernel_options_post = enums.VALUE_INHERITED
@@ -298,7 +296,7 @@ def __getattr__(self, name: str) -> Any:
         raise AttributeError(f'Attribute "{name}" did not exist on object type System.')
 
     #
-    # override some base class methods first (item.Item)
+    # override some base class methods first (BootableItem)
     #
 
     def make_clone(self):
@@ -334,7 +332,7 @@ def check_if_valid(self):
                     f"Error with system {self.name} - profile or image is required"
                 )
 
-    @Item.name.setter
+    @BootableItem.name.setter
     def name(self, name: str) -> None:
         """
         The systems name.
@@ -342,7 +340,7 @@ def name(self, name: str) -> None:
         :param name: system name string
         """
         # We have defined the Getter in BaseItem. As such this linter error is incorrect.
-        Item.name.fset(self, name)  # type: ignore[reportOptionalCall]
+        BootableItem.name.fset(self, name)  # type: ignore[reportOptionalCall]
         for interface in self.interfaces.values():
             interface.system_name = name
 
diff --git a/cobbler/modules/managers/import_signatures.py b/cobbler/modules/managers/import_signatures.py
index 0f9c300598..f12fe65ea1 100644
--- a/cobbler/modules/managers/import_signatures.py
+++ b/cobbler/modules/managers/import_signatures.py
@@ -461,10 +461,13 @@ def add_entry(self, dirname: str, kernel: str, initrd: str) -> List["Distro"]:
                 template_files=self.signature.get("template_files", {}),
             )
 
+            # This logic is temporary until https://github.com/cobbler/libcobblersignatures/issues/77 is implemented
             boot_files: Dict[str, str] = {}
             for boot_file in self.signature["boot_files"]:
                 boot_files[f"$local_img_path/{boot_file}"] = f"{self.path}/{boot_file}"
-            new_distro.boot_files = boot_files
+            # template_files must be a dict because during creation of the distro we explicitly set it as such.
+            # Also the property template_files is NOT inheritable.
+            new_distro.template_files.update(boot_files)
 
             self.configure_tree_location(new_distro)
 
diff --git a/cobbler/modules/managers/in_tftpd.py b/cobbler/modules/managers/in_tftpd.py
index 8cc4cd2a69..e000772395 100644
--- a/cobbler/modules/managers/in_tftpd.py
+++ b/cobbler/modules/managers/in_tftpd.py
@@ -69,11 +69,11 @@ def write_boot_files_distro(self, distro: "Distro") -> int:
         templater = templar.Templar(self.api)
 
         # Loop through the dict of boot files, executing a cp for each one
-        self.logger.info("processing boot_files for distro: %s", distro.name)
-        for boot_file in list(target["boot_files"].keys()):
+        self.logger.info("processing template_files for distro: %s", distro.name)
+        for boot_file in target["template_files"].keys():
             rendered_target_file = templater.render(boot_file, metadata, None)
             rendered_source_file = templater.render(
-                target["boot_files"][boot_file], metadata, None
+                target["template_files"][boot_file], metadata, None
             )
             file = ""  # to prevent unboundlocalerror
             filedst = ""  # to prevent unboundlocalerror
@@ -104,7 +104,7 @@ def write_boot_files_distro(self, distro: "Distro") -> int:
 
     def write_boot_files(self) -> int:
         """
-        Copy files in ``profile["boot_files"]`` into the TFTP server folder. Used for vmware currently.
+        Copy files in ``profile["template_files"]`` into the TFTP server folder. Used for vmware currently.
 
         :return: ``0`` on success.
         """
diff --git a/cobbler/modules/serializers/__init__.py b/cobbler/modules/serializers/__init__.py
index 6b33e6e1ac..842f2d3ef8 100644
--- a/cobbler/modules/serializers/__init__.py
+++ b/cobbler/modules/serializers/__init__.py
@@ -70,7 +70,7 @@ def deserialize(
         :param collection: The Cobbler collection to know the type of the item.
         :param topological: Sort collection based on each items' depth attribute in the list of collection items. This
                             ensures properly ordered object loading from disk with objects having parent/child
-                            relationships, i.e. profiles/subprofiles.  See cobbler/items/item.py
+                            relationships, i.e. profiles/subprofiles.  See cobbler/items/abstract/inheritable_item.py
         """
         raise NotImplementedError(
             "The implementation for the configured serializer is missing!"
diff --git a/cobbler/modules/serializers/sqlite.py b/cobbler/modules/serializers/sqlite.py
index 8c86222b1a..870ec3ed48 100644
--- a/cobbler/modules/serializers/sqlite.py
+++ b/cobbler/modules/serializers/sqlite.py
@@ -254,7 +254,7 @@ def deserialize(
         :param collection: The Cobbler collection to know the type of the item.
         :param topological: Sort collection based on each items' depth attribute in the list of collection items. This
                             ensures properly ordered object loading from disk with objects having parent/child
-                            relationships, i.e. profiles/subprofiles.  See cobbler/items/item.py
+                            relationships, i.e. profiles/subprofiles.  See cobbler/items/abstract/inheritable_item.py
         """
         self.__connect()
         datastruct = self.deserialize_raw(collection.collection_types())
diff --git a/cobbler/remote.py b/cobbler/remote.py
index 14c937b780..3461a52e39 100644
--- a/cobbler/remote.py
+++ b/cobbler/remote.py
@@ -149,8 +149,9 @@
 
 from cobbler import autoinstall_manager, configgen, enums, tftpgen, utils
 from cobbler.cexceptions import CX
-from cobbler.items import item, network_interface, system
+from cobbler.items import network_interface, system
 from cobbler.items.abstract import base_item
+from cobbler.items.abstract import bootable_item as item
 from cobbler.utils import signatures
 from cobbler.utils.event import CobblerEvent
 from cobbler.utils.thread import CobblerThread
@@ -890,7 +891,12 @@ def get_item_resolved_value(
             return return_value.value
         if isinstance(
             return_value,
-            (enums.DHCP, enums.NetworkInterfaceType, enums.BaudRates, item.Item),
+            (
+                enums.DHCP,
+                enums.NetworkInterfaceType,
+                enums.BaudRates,
+                item.BootableItem,
+            ),
         ):
             return return_value.name
         if isinstance(return_value, dict):
@@ -2179,8 +2185,6 @@ def xapi_object_edit(
                         "kernel_options",
                         "kernel_options_post",
                         "template_files",
-                        "boot_files",
-                        "fetchable_files",
                         "params",
                     ] and attributes.get("in_place"):
                         details = self.get_item(object_type, object_name)
diff --git a/cobbler/serializer.py b/cobbler/serializer.py
index 1b81c79301..1583f234d6 100644
--- a/cobbler/serializer.py
+++ b/cobbler/serializer.py
@@ -117,7 +117,7 @@ def deserialize(
         :param collection: The Cobbler collection to know the type of the item.
         :param topological: Sort collection based on each items' depth attribute in the list of collection items. This
                             ensures properly ordered object loading from disk with objects having parent/child
-                            relationships, i.e. profiles/subprofiles.  See cobbler/items/item.py
+                            relationships, i.e. profiles/subprofiles.  See cobbler/items/abstract/inheritable_item.py
         """
         self.__grab_lock()
         self.storage_object.deserialize(collection, topological)
diff --git a/cobbler/settings/migrations/V3_4_0.py b/cobbler/settings/migrations/V3_4_0.py
index 4dc8764dd9..6020692b91 100644
--- a/cobbler/settings/migrations/V3_4_0.py
+++ b/cobbler/settings/migrations/V3_4_0.py
@@ -328,5 +328,13 @@ def migrate_cobbler_collections(collections_dir: str) -> None:
                 if data["interfaces"][iface]["interface_type"] == "":
                     data["interfaces"][iface]["interface_type"] = "NA"
 
+        # Remove fetchable_files from the items
+        if "fetchable_files" in data:
+            data.pop("fetchable_files", None)
+
+        # Migrate boot_files to template_files
+        if "boot_files" in data and "template_files" in data:
+            data["template_files"] = {**data["template_files"], **data["boot_files"]}
+
         with open(collection_file, "w", encoding="utf-8") as _f:
             _f.write(json.dumps(data))
diff --git a/cobbler/tftpgen.py b/cobbler/tftpgen.py
index ce5864e80c..c2ae7f7849 100644
--- a/cobbler/tftpgen.py
+++ b/cobbler/tftpgen.py
@@ -23,9 +23,9 @@
 
 if TYPE_CHECKING:
     from cobbler.api import CobblerAPI
+    from cobbler.items.abstract.bootable_item import BootableItem
     from cobbler.items.distro import Distro
     from cobbler.items.image import Image
-    from cobbler.items.item import Item
     from cobbler.items.menu import Menu
     from cobbler.items.profile import Profile
     from cobbler.items.system import System
@@ -1541,7 +1541,7 @@ def build_kernel_options(
 
     def write_templates(
         self,
-        obj: "Item",
+        obj: "BootableItem",
         write_file: bool = False,
         path: Optional[str] = None,
     ) -> Dict[str, str]:
@@ -1610,13 +1610,18 @@ def write_templates(
             if path is not None and path != dest:
                 continue
 
-            # If we are writing output to a file, we allow files tobe written into the tftpboot directory, otherwise
+            # If we are writing output to a file, we allow files to be written into the tftpboot directory, otherwise
             # force all templated configs into the rendered directory to ensure that a user granted cobbler privileges
             # via sudo can't overwrite arbitrary system files (This also makes cleanup easier).
-            if os.path.isabs(dest_dir) and write_file:
-                if dest_dir.find(self.bootloc) != 0:
+            if write_file and os.path.isabs(dest_dir):
+                if not (
+                    dest_dir.startswith(self.bootloc)
+                    or dest.startswith(self.settings.webdir)
+                ):
+                    # Allow both the TFTP and web directory since template_files and boot_files were merged
                     raise CX(
-                        f" warning: template destination ({dest_dir}) is outside {self.bootloc}, skipping."
+                        f"warning: template destination ({dest_dir}) is outside {self.bootloc} or"
+                        f" {self.settings.webdir}, skipping."
                     )
             elif write_file:
                 dest_dir = os.path.join(self.settings.webdir, "rendered", dest_dir)
@@ -1626,17 +1631,22 @@ def write_templates(
 
             # Check for problems
             if not os.path.exists(template):
-                raise CX(f"template source {template} does not exist")
+                self.logger.warning("template source %s does not exist", template)
+                continue
             if write_file and not os.path.isdir(dest_dir):
-                raise CX(f"template destination ({dest_dir}) is invalid")
+                self.logger.warning("template destination (%s) is invalid", dest_dir)
+                continue
             if write_file and os.path.exists(dest):
-                raise CX(f"template destination ({dest}) already exists")
+                self.logger.warning("template destination (%s) already exists", dest)
+                continue
             if write_file and os.path.isdir(dest):
-                raise CX(f"template destination ({dest}) is a directory")
+                self.logger.warning("template destination (%s) is a directory", dest)
+                continue
             if template == "" or dest == "":
-                raise CX(
+                self.logger.warning(
                     "either the template source or destination was blank (unknown variable used?)"
                 )
+                continue
 
             with open(template, encoding="UTF-8") as template_fh:
                 template_data = template_fh.read()
diff --git a/cobbler/utils/__init__.py b/cobbler/utils/__init__.py
index 96d693cd32..6d5bd478b4 100644
--- a/cobbler/utils/__init__.py
+++ b/cobbler/utils/__init__.py
@@ -44,7 +44,7 @@
 if TYPE_CHECKING:
     from cobbler.api import CobblerAPI
     from cobbler.cobbler_collections.collection import ITEM
-    from cobbler.items.item import Item
+    from cobbler.items.abstract.bootable_item import BootableItem
     from cobbler.settings import Settings
 
 CHEETAH_ERROR_DISCLAIMER = """
@@ -451,7 +451,7 @@ def file_is_remote(file_location: str) -> bool:
 
 
 def blender(
-    api_handle: "CobblerAPI", remove_dicts: bool, root_obj: "Item"
+    api_handle: "CobblerAPI", remove_dicts: bool, root_obj: "BootableItem"
 ) -> Dict[str, Any]:
     """
     Combine all of the data in an object tree from the perspective of that point on the tree, and produce a merged
@@ -562,10 +562,6 @@ def flatten(data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
         data["autoinstall_meta"] = dict_to_string(data["autoinstall_meta"])
     if "template_files" in data:
         data["template_files"] = dict_to_string(data["template_files"])
-    if "boot_files" in data:
-        data["boot_files"] = dict_to_string(data["boot_files"])
-    if "fetchable_files" in data:
-        data["fetchable_files"] = dict_to_string(data["fetchable_files"])
     if "repos" in data and isinstance(data["repos"], list):
         data["repos"] = " ".join(data["repos"])  # type: ignore
     if "rpm_list" in data and isinstance(data["rpm_list"], list):
@@ -658,8 +654,6 @@ def __consolidate(node: Union["ITEM", "Settings"], results: Dict[Any, Any]) -> D
     dict_removals(results, "kernel_options_post")
     dict_removals(results, "autoinstall_meta")
     dict_removals(results, "template_files")
-    dict_removals(results, "boot_files")
-    dict_removals(results, "fetchable_files")
     return results
 
 
diff --git a/docs/code-autodoc/cobbler.actions.rst b/docs/code-autodoc/cobbler.actions.rst
index 8908d093d3..17cbef6a7e 100644
--- a/docs/code-autodoc/cobbler.actions.rst
+++ b/docs/code-autodoc/cobbler.actions.rst
@@ -68,14 +68,6 @@ cobbler.actions.replicate module
    :undoc-members:
    :show-inheritance:
 
-cobbler.actions.report module
------------------------------
-
-.. automodule:: cobbler.actions.report
-   :members:
-   :undoc-members:
-   :show-inheritance:
-
 cobbler.actions.reposync module
 -------------------------------
 
diff --git a/docs/code-autodoc/cobbler.items.abstract.rst b/docs/code-autodoc/cobbler.items.abstract.rst
new file mode 100644
index 0000000000..0f8c55b520
--- /dev/null
+++ b/docs/code-autodoc/cobbler.items.abstract.rst
@@ -0,0 +1,45 @@
+cobbler.items.abstract package
+==============================
+
+Submodules
+----------
+
+cobbler.items.abstract.base\_item module
+----------------------------------------
+
+.. automodule:: cobbler.items.abstract.base_item
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+cobbler.items.abstract.bootable\_item module
+--------------------------------------------
+
+.. automodule:: cobbler.items.abstract.bootable_item
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+cobbler.items.abstract.inheritable\_item module
+-----------------------------------------------
+
+.. automodule:: cobbler.items.abstract.inheritable_item
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+cobbler.items.abstract.item\_cache module
+-----------------------------------------
+
+.. automodule:: cobbler.items.abstract.item_cache
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+Module contents
+---------------
+
+.. automodule:: cobbler.items.abstract
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/docs/code-autodoc/cobbler.items.rst b/docs/code-autodoc/cobbler.items.rst
index afbc000e66..b946689766 100644
--- a/docs/code-autodoc/cobbler.items.rst
+++ b/docs/code-autodoc/cobbler.items.rst
@@ -1,6 +1,14 @@
 cobbler.items package
 =====================
 
+Subpackages
+-----------
+
+.. toctree::
+   :maxdepth: 4
+
+   cobbler.items.abstract
+
 Submodules
 ----------
 
@@ -20,18 +28,18 @@ cobbler.items.image module
    :undoc-members:
    :show-inheritance:
 
-cobbler.items.item module
+cobbler.items.menu module
 -------------------------
 
-.. automodule:: cobbler.items.item
+.. automodule:: cobbler.items.menu
    :members:
    :undoc-members:
    :show-inheritance:
 
-cobbler.items.menu module
--------------------------
+cobbler.items.network\_interface module
+---------------------------------------
 
-.. automodule:: cobbler.items.menu
+.. automodule:: cobbler.items.network_interface
    :members:
    :undoc-members:
    :show-inheritance:
diff --git a/docs/code-autodoc/cobbler.modules.serializers.rst b/docs/code-autodoc/cobbler.modules.serializers.rst
index 192be18581..9c2f1a5b0e 100644
--- a/docs/code-autodoc/cobbler.modules.serializers.rst
+++ b/docs/code-autodoc/cobbler.modules.serializers.rst
@@ -21,7 +21,7 @@ cobbler.modules.serializers.mongodb module
    :show-inheritance:
 
 cobbler.modules.serializers.sqlite module
-------------------------------------------
+-----------------------------------------
 
 .. automodule:: cobbler.modules.serializers.sqlite
    :members:
diff --git a/docs/code-autodoc/cobbler.settings.migrations.rst b/docs/code-autodoc/cobbler.settings.migrations.rst
index 177bfbd17d..8a1106c607 100644
--- a/docs/code-autodoc/cobbler.settings.migrations.rst
+++ b/docs/code-autodoc/cobbler.settings.migrations.rst
@@ -100,6 +100,22 @@ cobbler.settings.migrations.V3\_3\_3 module
    :undoc-members:
    :show-inheritance:
 
+cobbler.settings.migrations.V3\_3\_4 module
+-------------------------------------------
+
+.. automodule:: cobbler.settings.migrations.V3_3_4
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
+cobbler.settings.migrations.V3\_3\_5 module
+-------------------------------------------
+
+.. automodule:: cobbler.settings.migrations.V3_3_5
+   :members:
+   :undoc-members:
+   :show-inheritance:
+
 cobbler.settings.migrations.V3\_4\_0 module
 -------------------------------------------
 


Test patch:
diff --git a/tests/items/item_test.py b/tests/items/bootable_item_test.py
similarity index 65%
rename from tests/items/item_test.py
rename to tests/items/bootable_item_test.py
index 0e16f7fa7c..da51775051 100644
--- a/tests/items/item_test.py
+++ b/tests/items/bootable_item_test.py
@@ -1,5 +1,5 @@
 """
-Test module that asserts that generic Cobbler Item functionality is working as expected.
+Test module that asserts that generic Cobbler BootableItem functionality is working as expected.
 """
 
 import os
@@ -9,13 +9,8 @@
 
 from cobbler import enums
 from cobbler.api import CobblerAPI
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.items.distro import Distro
-from cobbler.items.image import Image
-from cobbler.items.item import Item
-from cobbler.items.menu import Menu
-from cobbler.items.profile import Profile
-from cobbler.items.repo import Repo
-from cobbler.items.system import System
 from cobbler.settings import Settings
 
 from tests.conftest import does_not_raise
@@ -24,8 +19,11 @@
     from pytest_mock import MockerFixture
 
 
-@pytest.fixture()
-def test_settings(mocker: "MockerFixture", cobbler_api: CobblerAPI):
+@pytest.fixture(name="test_settings")
+def fixture_test_settings(mocker: "MockerFixture", cobbler_api: CobblerAPI):
+    """
+    Test fixture that mocks the settings for the bootable items.
+    """
     settings = mocker.MagicMock(name="item_setting_mock", spec=cobbler_api.settings())
     orig = cobbler_api.settings()
     for key in orig.to_dict():
@@ -35,7 +33,7 @@ def test_settings(mocker: "MockerFixture", cobbler_api: CobblerAPI):
 
 def test_item_create(cobbler_api: CobblerAPI):
     """
-    Assert that an abstract Cobbler Item can be successfully created.
+    Assert that an abstract Cobbler BootableItem can be successfully created.
     """
     # Arrange
 
@@ -43,7 +41,7 @@ def test_item_create(cobbler_api: CobblerAPI):
     titem = Distro(cobbler_api)
 
     # Assert
-    assert isinstance(titem, Item)
+    assert isinstance(titem, BootableItem)
 
 
 def test_from_dict(
@@ -53,7 +51,7 @@ def test_from_dict(
     fk_initrd: str,
 ):
     """
-    Assert that an abstract Cobbler Item can be loaded from dict.
+    Assert that an abstract Cobbler BootableItem can be loaded from dict.
     """
     # Arrange
     folder = create_kernel_initrd(fk_kernel, fk_initrd)
@@ -72,97 +70,6 @@ def test_from_dict(
     assert titem.initrd == initrd_path
 
 
-def test_item_descendants(cobbler_api: CobblerAPI):
-    """
-    Assert that all decendants of a Cobbler Item are correctly captured by called the property.
-    """
-    # Arrange
-    titem = Distro(cobbler_api)
-
-    # Act
-    result = titem.descendants
-
-    # Assert
-    assert result == []
-
-
-def test_descendants(
-    cobbler_api: CobblerAPI,
-    create_distro: Callable[[], Distro],
-    create_image: Callable[[], Image],
-    create_profile: Any,
-    create_system: Any,
-):
-    """
-    Assert that the decendants property is also working with an enabled Cache.
-    """
-    # Arrange
-    test_repo = Repo(cobbler_api)
-    test_repo.name = "test_repo"
-    cobbler_api.add_repo(test_repo)
-    test_menu1 = Menu(cobbler_api)
-    test_menu1.name = "test_menu1"
-    cobbler_api.add_menu(test_menu1)
-    test_menu2 = Menu(cobbler_api)
-    test_menu2.name = "test_menu2"
-    test_menu2.parent = test_menu1.name
-    cobbler_api.add_menu(test_menu2)
-    test_distro = create_distro()
-    test_profile1: Profile = create_profile(
-        distro_name=test_distro.name, name="test_profile1"
-    )
-    test_profile1.enable_menu = False
-    test_profile1.repos = [test_repo.name]
-    test_profile2: Profile = create_profile(
-        profile_name=test_profile1.name, name="test_profile2"
-    )
-    test_profile2.enable_menu = False
-    test_profile2.menu = test_menu2.name
-    test_profile3: Profile = create_profile(
-        profile_name=test_profile1.name, name="test_profile3"
-    )
-    test_profile3.enable_menu = False
-    test_profile3.repos = [test_repo.name]
-    test_image = create_image()
-    test_image.menu = test_menu1.name
-    test_system1: System = create_system(
-        profile_name=test_profile1.name, name="test_system1"
-    )
-    test_system2: System = create_system(
-        image_name=test_image.name, name="test_system2"
-    )
-
-    # Act
-    cache_tests = [
-        test_repo.descendants,
-        test_distro.descendants,
-        test_image.descendants,
-        test_profile1.descendants,
-        test_profile2.descendants,
-        test_profile3.descendants,
-        test_menu1.descendants,
-        test_menu2.descendants,
-        test_system1.descendants,
-        test_system2.descendants,
-    ]
-    results = [
-        [test_profile1, test_profile2, test_profile3, test_system1],
-        [test_profile1, test_profile2, test_profile3, test_system1],
-        [test_system2],
-        [test_profile2, test_profile3, test_system1],
-        [],
-        [],
-        [test_image, test_menu2, test_profile2, test_system2],
-        [test_profile2],
-        [],
-        [],
-    ]
-
-    # Assert
-    for x in range(len(cache_tests)):
-        assert set(cache_tests[x]) == set(results[x])
-
-
 @pytest.mark.parametrize(
     "input_kernel_options,expected_exception,expected_result",
     [
@@ -177,7 +84,7 @@ def test_kernel_options(
     expected_result: Optional[Dict[Any, Any]],
 ):
     """
-    Assert that an abstract Cobbler Item can use the Getter and Setter of the kernel_options property correctly.
+    Assert that an abstract Cobbler BootableItem can use the Getter and Setter of the kernel_options property correctly.
     """
     # Arrange
     titem = Distro(cobbler_api)
@@ -204,7 +111,8 @@ def test_kernel_options_post(
     expected_result: Optional[Dict[Any, Any]],
 ):
     """
-    Assert that an abstract Cobbler Item can use the Getter and Setter of the kernel_options_post property correctly.
+    Assert that an abstract Cobbler BootableItem can use the Getter and Setter of the kernel_options_post property
+    correctly.
     """
     # Arrange
     titem = Distro(cobbler_api)
@@ -231,7 +139,8 @@ def test_autoinstall_meta(
     expected_result: Optional[Dict[Any, Any]],
 ):
     """
-    Assert that an abstract Cobbler Item can use the Getter and Setter of the autoinstall_meta property correctly.
+    Assert that an abstract Cobbler BootableItem can use the Getter and Setter of the autoinstall_meta property
+    correctly.
     """
     # Arrange
     titem = Distro(cobbler_api)
@@ -246,7 +155,7 @@ def test_autoinstall_meta(
 
 def test_template_files(cobbler_api: CobblerAPI):
     """
-    Assert that an abstract Cobbler Item can use the Getter and Setter of the template_files property correctly.
+    Assert that an abstract Cobbler BootableItem can use the Getter and Setter of the template_files property correctly.
     """
     # Arrange
     titem = Distro(cobbler_api)
@@ -258,34 +167,6 @@ def test_template_files(cobbler_api: CobblerAPI):
     assert titem.template_files == {}
 
 
-def test_boot_files(cobbler_api: CobblerAPI):
-    """
-    Assert that an abstract Cobbler Item can use the Getter and Setter of the boot_files property correctly.
-    """
-    # Arrange
-    titem = Distro(cobbler_api)
-
-    # Act
-    titem.boot_files = {}
-
-    # Assert
-    assert titem.boot_files == {}
-
-
-def test_fetchable_files(cobbler_api: CobblerAPI):
-    """
-    Assert that an abstract Cobbler Item can use the Getter and Setter of the fetchable_files property correctly.
-    """
-    # Arrange
-    titem = Distro(cobbler_api)
-
-    # Act
-    titem.fetchable_files = {}
-
-    # Assert
-    assert titem.fetchable_files == {}
-
-
 def test_sort_key(request: "pytest.FixtureRequest", cobbler_api: CobblerAPI):
     """
     Assert that the exported dict contains only the fields given in the argument.
@@ -377,12 +258,12 @@ def test_dump_vars(cobbler_api: CobblerAPI):
     print(result)
     assert "default_ownership" in result
     assert "owners" in result
-    assert len(result) == 171
+    assert len(result) == 169
 
 
 def test_to_dict(cobbler_api: CobblerAPI):
     """
-    Assert that a Cobbler Item can be converted to a dictionary.
+    Assert that a Cobbler BootableItem can be converted to a dictionary.
     """
     # Arrange
     titem = Distro(cobbler_api)
@@ -397,7 +278,7 @@ def test_to_dict(cobbler_api: CobblerAPI):
 
 def test_to_dict_resolved(cobbler_api: CobblerAPI):
     """
-    Assert that a Cobbler Item can be converted to a dictionary with resolved values.
+    Assert that a Cobbler BootableItem can be converted to a dictionary with resolved values.
     """
     # Arrange
     titem = Distro(cobbler_api)
@@ -413,7 +294,7 @@ def test_to_dict_resolved(cobbler_api: CobblerAPI):
 
 def test_serialize(cobbler_api: CobblerAPI):
     """
-    Assert that a given Cobbler Item can be serialized.
+    Assert that a given Cobbler BootableItem can be serialized.
     """
     # Arrange
     kernel_url = "http://10.0.0.1/custom-kernels-are-awesome"
diff --git a/tests/items/cache_test.py b/tests/items/cache_test.py
index 549a357777..b03dcb7759 100644
--- a/tests/items/cache_test.py
+++ b/tests/items/cache_test.py
@@ -9,9 +9,9 @@
 from cobbler.api import CobblerAPI
 from cobbler.cexceptions import CX
 from cobbler.items.abstract.base_item import BaseItem
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.items.distro import Distro
 from cobbler.items.image import Image
-from cobbler.items.item import Item
 from cobbler.items.menu import Menu
 from cobbler.items.profile import Profile
 from cobbler.items.repo import Repo
@@ -467,7 +467,7 @@ def validate_caches(
             obj.to_dict(resolved=False)
             obj.to_dict(resolved=True)
         remain_objs = set(objs) - set(dep)
-        if isinstance(obj_test, Item):
+        if isinstance(obj_test, BootableItem):
             remain_objs.remove(obj_test)
             obj_test.owners = "test"
             if (
diff --git a/tests/items/distro_test.py b/tests/items/distro_test.py
index a4a6b76fa8..0dcdc96403 100644
--- a/tests/items/distro_test.py
+++ b/tests/items/distro_test.py
@@ -395,29 +395,6 @@ def test_source_repos(cobbler_api: CobblerAPI, value: Any):
     assert distro.source_repos == value
 
 
-@pytest.mark.parametrize(
-    "value,expected_exception",
-    [
-        ([""], pytest.raises(TypeError)),
-        # ("test=test test1 test2=0", does_not_raise()), --> Fix this. It works but we can't compare
-        ({"test": "test", "test2": 0}, does_not_raise()),
-    ],
-)
-def test_fetchable_files(cobbler_api: CobblerAPI, value: Any, expected_exception: Any):
-    """
-    Test that verifies if fetchable files can be set as expected.
-    """
-    # Arrange
-    distro = Distro(cobbler_api)
-
-    # Act
-    with expected_exception:
-        distro.fetchable_files = value
-
-        # Assert
-        assert distro.fetchable_files == value
-
-
 @pytest.mark.parametrize(
     "value,expected_exception",
     [
diff --git a/tests/items/inheritable_item_test.py b/tests/items/inheritable_item_test.py
index 431cfed460..7cdd4a466d 100644
--- a/tests/items/inheritable_item_test.py
+++ b/tests/items/inheritable_item_test.py
@@ -1,10 +1,18 @@
+"""
+Test module that asserts that generic Cobbler InheritableItem functionality is working as expected.
+"""
+
 from typing import Any, Callable, Optional
 
 import pytest
 
 from cobbler.api import CobblerAPI
 from cobbler.items.distro import Distro
+from cobbler.items.image import Image
+from cobbler.items.menu import Menu
 from cobbler.items.profile import Profile
+from cobbler.items.repo import Repo
+from cobbler.items.system import System
 
 from tests.conftest import does_not_raise
 
@@ -90,6 +98,97 @@ def test_children(cobbler_api: CobblerAPI):
     assert result == []
 
 
+def test_item_descendants(cobbler_api: CobblerAPI):
+    """
+    Assert that all descendants of a Cobbler BootableItem are correctly captured by called the property.
+    """
+    # Arrange
+    titem = Distro(cobbler_api, name="test_item_descendants")
+
+    # Act
+    result = titem.descendants
+
+    # Assert
+    assert result == []
+
+
+def test_descendants(
+    cobbler_api: CobblerAPI,
+    create_distro: Callable[[], Distro],
+    create_image: Callable[[], Image],
+    create_profile: Any,
+    create_system: Any,
+):
+    """
+    Assert that the descendants property is also working with an enabled Cache.
+    """
+    # Arrange
+    test_repo = Repo(cobbler_api)
+    test_repo.name = "test_repo"
+    cobbler_api.add_repo(test_repo)
+    test_menu1 = Menu(cobbler_api)
+    test_menu1.name = "test_menu1"
+    cobbler_api.add_menu(test_menu1)
+    test_menu2 = Menu(cobbler_api)
+    test_menu2.name = "test_menu2"
+    test_menu2.parent = test_menu1.name
+    cobbler_api.add_menu(test_menu2)
+    test_distro = create_distro()
+    test_profile1: Profile = create_profile(
+        distro_name=test_distro.name, name="test_profile1"
+    )
+    test_profile1.enable_menu = False
+    test_profile1.repos = [test_repo.name]
+    test_profile2: Profile = create_profile(
+        profile_name=test_profile1.name, name="test_profile2"
+    )
+    test_profile2.enable_menu = False
+    test_profile2.menu = test_menu2.name
+    test_profile3: Profile = create_profile(
+        profile_name=test_profile1.name, name="test_profile3"
+    )
+    test_profile3.enable_menu = False
+    test_profile3.repos = [test_repo.name]
+    test_image = create_image()
+    test_image.menu = test_menu1.name
+    test_system1: System = create_system(
+        profile_name=test_profile1.name, name="test_system1"
+    )
+    test_system2: System = create_system(
+        image_name=test_image.name, name="test_system2"
+    )
+
+    # Act
+    cache_tests = [
+        test_repo.descendants,
+        test_distro.descendants,
+        test_image.descendants,
+        test_profile1.descendants,
+        test_profile2.descendants,
+        test_profile3.descendants,
+        test_menu1.descendants,
+        test_menu2.descendants,
+        test_system1.descendants,
+        test_system2.descendants,
+    ]
+    results = [
+        [test_profile1, test_profile2, test_profile3, test_system1],
+        [test_profile1, test_profile2, test_profile3, test_system1],
+        [test_system2],
+        [test_profile2, test_profile3, test_system1],
+        [],
+        [],
+        [test_image, test_menu2, test_profile2, test_system2],
+        [test_profile2],
+        [],
+        [],
+    ]
+
+    # Assert
+    for x in range(len(cache_tests)):
+        assert set(cache_tests[x]) == set(results[x])
+
+
 def test_tree_walk(cobbler_api: CobblerAPI):
     """
     Assert that all descendants of a Cobbler Item are correctly captured by called the method.
diff --git a/tests/items/profile_test.py b/tests/items/profile_test.py
index 77adf21605..58262c015a 100644
--- a/tests/items/profile_test.py
+++ b/tests/items/profile_test.py
@@ -73,7 +73,7 @@ def test_to_dict(
     result = profile.to_dict()
 
     # Assert
-    assert len(result) == 43
+    assert len(result) == 41
     assert result["distro"] == distro.name
     assert result.get("boot_loaders") == enums.VALUE_INHERITED
 
diff --git a/tests/modules/serializer/file_test.py b/tests/modules/serializer/file_test.py
index 6b1fd54f40..1409260b86 100644
--- a/tests/modules/serializer/file_test.py
+++ b/tests/modules/serializer/file_test.py
@@ -10,23 +10,23 @@
 from cobbler.api import CobblerAPI
 from cobbler.cexceptions import CX
 from cobbler.cobbler_collections.collection import Collection
-from cobbler.items.item import Item
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.modules.serializers import file
 from cobbler.settings import Settings
 
 from tests.conftest import does_not_raise
 
 
-class MockItem(Item):
+class MockBootableItem(BootableItem):
     """
     Test Item for the serializer tests.
     """
 
-    def make_clone(self) -> "MockItem":
-        return MockItem(self.api)
+    def make_clone(self) -> "MockBootableItem":
+        return MockBootableItem(self.api)
 
 
-class MockCollection(Collection[MockItem]):
+class MockCollection(Collection[MockBootableItem]):
     """
     Test Collection that is used for the serializer tests.
     """
@@ -39,8 +39,10 @@ def collection_type() -> str:
     def collection_types() -> str:
         return "tests"
 
-    def factory_produce(self, api: "CobblerAPI", seed_data: Dict[Any, Any]) -> MockItem:
-        new_test = MockItem(api)
+    def factory_produce(
+        self, api: "CobblerAPI", seed_data: Dict[Any, Any]
+    ) -> MockBootableItem:
+        new_test = MockBootableItem(api)
         return new_test
 
     def remove(
@@ -156,7 +158,7 @@ def test_serialize_item(
     """
     # Arrange
     serializer_obj.libpath = str(tmpdir)
-    mitem = MockItem(cobbler_api)
+    mitem = MockBootableItem(cobbler_api)
     mitem.name = "test_serializer"
     mcollection = MockCollection(cobbler_api._collection_mgr)  # type: ignore
     os.mkdir(os.path.join(tmpdir, mcollection.collection_types()))
@@ -180,7 +182,7 @@ def test_serialize_delete(
     Test that will assert if a given item can be deleted.
     """
     # Arrange
-    mitem = MockItem(cobbler_api)
+    mitem = MockBootableItem(cobbler_api)
     mitem.name = "test_serializer_del"
     mcollection = MockCollection(cobbler_api._collection_mgr)  # type: ignore
     serializer_obj.libpath = str(tmpdir)
diff --git a/tests/modules/serializer/sqlite_test.py b/tests/modules/serializer/sqlite_test.py
index 131f6a67ef..d5c011f30c 100644
--- a/tests/modules/serializer/sqlite_test.py
+++ b/tests/modules/serializer/sqlite_test.py
@@ -14,7 +14,7 @@
 from cobbler.api import CobblerAPI
 from cobbler.cexceptions import CX
 from cobbler.cobbler_collections.collection import Collection
-from cobbler.items.item import Item
+from cobbler.items.abstract.bootable_item import BootableItem
 from cobbler.modules.serializers import sqlite
 from cobbler.settings import Settings
 
@@ -30,16 +30,16 @@ def test_settings(mocker: MockerFixture, cobbler_api: CobblerAPI) -> Settings:
     return settings
 
 
-class MockItem(Item):
+class MockBootableItem(BootableItem):
     """
     Test Item for the serializer tests.
     """
 
-    def make_clone(self) -> "MockItem":
-        return MockItem(self.api)
+    def make_clone(self) -> "MockBootableItem":
+        return MockBootableItem(self.api)
 
 
-class MockCollection(Collection[MockItem]):
+class MockCollection(Collection[MockBootableItem]):
     """
     Test Collection that is used for the serializer tests.
     """
@@ -52,8 +52,10 @@ def collection_type() -> str:
     def collection_types() -> str:
         return "tests"
 
-    def factory_produce(self, api: "CobblerAPI", seed_data: Dict[Any, Any]) -> MockItem:
-        new_test = MockItem(api)
+    def factory_produce(
+        self, api: "CobblerAPI", seed_data: Dict[Any, Any]
+    ) -> MockBootableItem:
+        new_test = MockBootableItem(api)
         return new_test
 
     def remove(
@@ -157,7 +159,7 @@ def test_serialize_item(
     Test that will assert if a given item can be written to table successfully.
     """
     # Arrange
-    mitem = MockItem(cobbler_api)
+    mitem = MockBootableItem(cobbler_api)
     mitem.name = "test_serializer_item"
     mcollection = MockCollection(cobbler_api._collection_mgr)  # type: ignore
 
@@ -191,7 +193,7 @@ def test_serialize_delete(
     Test that will assert if a given item can be deleted.
     """
     # Arrange
-    mitem = MockItem(cobbler_api)
+    mitem = MockBootableItem(cobbler_api)
     mitem.name = "test_serializer_del"
     mcollection = MockCollection(cobbler_api._collection_mgr)  # type: ignore
     serializer_obj.serialize_item(mcollection, mitem)
@@ -224,7 +226,7 @@ def test_serialize(
     cobbler_api: CobblerAPI,
 ):
     # Arrange
-    mitem = MockItem(cobbler_api)
+    mitem = MockBootableItem(cobbler_api)
     mitem.name = "test_serialize"
     mcollection = MockCollection(cobbler_api._collection_mgr)  # type: ignore
     mcollection.listing[mitem.name] = mitem
@@ -276,7 +278,7 @@ def test_deserialize_raw(
     """
     # Arrange
     mocker.patch("cobbler.settings.read_settings_file", return_value=expected_result)
-    mitem = MockItem(cobbler_api)
+    mitem = MockBootableItem(cobbler_api)
     mitem.name = "test_deserialize_raw"
     mitem.item = "{'name': 'test_deserialize_raw'}"
     mcollection = MockCollection(cobbler_api._collection_mgr)  # type: ignore
diff --git a/tests/utils/utils_test.py b/tests/utils/utils_test.py
index a814d05f13..d03021fa00 100644
--- a/tests/utils/utils_test.py
+++ b/tests/utils/utils_test.py
@@ -212,13 +212,13 @@ def test_blender(cobbler_api):  # type: ignore
     result = utils.blender(cobbler_api, False, root_item)  # type: ignore
 
     # Assert
-    assert len(result) == 171
+    assert len(result) == 169
     # Must be present because the settings have it
     assert "server" in result
     # Must be present because it is a field of distro
     assert "os_version" in result
     # Must be present because it inherits but is a field of distro
-    assert "boot_loaders" in result
+    assert "template_files" in result
 
 
 @pytest.mark.parametrize(
diff --git a/tests/xmlrpcapi/miscellaneous_test.py b/tests/xmlrpcapi/miscellaneous_test.py
index 905db76e89..f6eaa21e81 100644
--- a/tests/xmlrpcapi/miscellaneous_test.py
+++ b/tests/xmlrpcapi/miscellaneous_test.py
@@ -109,7 +109,7 @@ def test_find_items_paged(
     # Example output
     # {'items': [{'ctime': 1589386486.9040322, 'depth': 0, 'mtime': 1589386486.9040322, 'source_repos': [],
     # 'tree_build_time': 0, 'uid': 'cbf288465c724c439cf2ede6c94de4e8', 'arch': 'x86_64', 'autoinstall_meta': {},
-    # 'boot_files': {}, 'boot_loaders': '<<inherit>>', 'breed': 'suse', 'comment': '', 'fetchable_files': {},
+    # 'boot_loaders': '<<inherit>>', 'breed': 'suse', 'comment': '',
     # 'initrd': '/var/log/cobbler/cobbler.log', 'kernel': '/var/log/cobbler/cobbler.log', 'remote_boot_initrd': '~',
     # 'remote_boot_kernel': '~', 'kernel_options': {}, 'kernel_options_post': {},
     # 'name': 'distro_items_paged_1', 'os_version': 'virtio26', 'owners': ['admin'], 'redhat_management_key': '',