Repository: nofusscomputing/centurion_erp
Issue category: Feature Request

Issue:
Hide navigation items when user lacks permission
API v2 provides the navigation structure for the UI to build the navigation tree. This needs to be filtered.


## Details

If a user lacks permission for the location of the nav path, filter it out so it's not rendered within the interface.


## Tasks

- [x] Filter navigation based off of user permissions


## Requirements

-  [ ] Test to ensure that items the user has no access to, is provided.

Hide global organization from organization dropdown
If a global organization has been set, some items are automagically moved to this organization.


## Details

As the global organization is managed it should not be selectable within the organization field by anyone.

For items that are set as global, the `organization` field should be removed as should the `is_global`.


## Tasks

- [ ] #419


Hints:

Patch:
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index aa5d2c6d8..3ca032cf1 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -20,6 +20,9 @@
 
 <!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
 
+ - [ ] **Feature Release ONLY** :red_square: Squash migration files :red_square: 
+    _Multiple migration files created as part of this release are to be sqauashed into a few files as possible so as to limit the number of migrations_ 
+
 - [ ] :firecracker: Contains breaking-change Any Breaking change(s)?
 
     _Breaking Change must also be notated in the commit that introduces it and in [Conventional Commit Format](https://www.conventionalcommits.org/en/v1.0.0/)._
diff --git a/Release-Notes.md b/Release-Notes.md
index 4eb86e703..9a5be712d 100644
--- a/Release-Notes.md
+++ b/Release-Notes.md
@@ -1,4 +1,4 @@
-## Next Release
+## Version 1.5.0
 
 - When v1.4.0 was release the migrations were not merged. As part of the work conducted on this release the v1.4 migrations have been squashed. This should not have any effect on any system that when they updated to v1.4, they ran the migrations and they **completed successfully**. Upgrading from <1.4.0 to this release should also have no difficulties as the migrations required still exist. There are less of them, however with more work per migration.
 
diff --git a/app/access/serializers/organization.py b/app/access/serializers/organization.py
index 455c90b64..42c7d40dd 100644
--- a/app/access/serializers/organization.py
+++ b/app/access/serializers/organization.py
@@ -4,10 +4,10 @@
 
 from access.models import Organization
 
-from api.serializers import common
-
 from app.serializers.user import UserBaseSerializer
 
+from core import fields as centurion_field
+
 
 
 class OrganizationBaseSerializer(serializers.ModelSerializer):
@@ -43,7 +43,6 @@ class Meta:
 
 
 class OrganizationModelSerializer(
-    common.CommonModelSerializer,
     OrganizationBaseSerializer
 ):
 
@@ -56,6 +55,7 @@ def get_url(self, item) -> dict:
             'teams': reverse("v2:_api_v2_organization_team-list", request=self._context['view'].request, kwargs={'organization_id': item.pk}),
         }
 
+    model_notes = centurion_field.MarkdownField( required = False )
 
     class Meta:
 
diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py
index cc5c06077..a3fb3dece 100644
--- a/app/api/react_ui_metadata.py
+++ b/app/api/react_ui_metadata.py
@@ -385,7 +385,7 @@ def get_field_info(self, field):
             "display_name": "Settings",
             "name": "settings",
             "pages": {
-                'view_settings': {
+                'all_settings': {
                     "display_name": "System",
                     "name": "setting",
                     "icon": "system",
@@ -436,6 +436,19 @@ def get_navigation(self, user) -> list(dict()):
 
                         processed_permissions[permission.content_type.app_label].update({str(permission.codename): '_'})
 
+        view_settings: list = [
+            'assistance.view_knowledgebasecategory',
+            'core.view_manufacturer',
+            'core.view_ticketcategory',
+            'core.view_ticketcommentcategory',
+            'itam.view_devicemodel',
+            'itam.view_devicetype',
+            'itam.view_softwarecategory',
+            'itim.view_clustertype',
+            'project_management.view_projectstate',
+            'project_management.view_projecttype',
+            'settings.view_appsettings',
+        ]
 
         for app, entry in self._nav.items():
 
@@ -447,7 +460,22 @@ def get_navigation(self, user) -> list(dict()):
 
             for permission, page in entry['pages'].items():
 
-                if '.' in permission:
+                if permission == 'all_settings':
+
+                    for setting_permission in view_settings:
+
+                        app_permission = str(setting_permission).split('.')
+
+                        if processed_permissions.get(app_permission[0], None):
+
+                            if processed_permissions[app_permission[0]].get(app_permission[1], None):
+
+                                new_pages += [ page ]
+
+                                break
+
+
+                elif '.' in permission:
 
                     app_permission = str(permission).split('.')
 
diff --git a/app/api/serializers/common.py b/app/api/serializers/common.py
index 3033eeb23..39e120e48 100644
--- a/app/api/serializers/common.py
+++ b/app/api/serializers/common.py
@@ -1,8 +1,32 @@
 
 from rest_framework import serializers
 
+from access.serializers.organization import Organization, OrganizationBaseSerializer
+
 from core import fields as centurion_field
 
+from settings.models.app_settings import AppSettings
+
+
+class OrganizationField(serializers.PrimaryKeyRelatedField):
+
+    def get_queryset(self):
+        """ Queryset Override
+        
+        Override the base serializer and filter out the `global_organization`
+        if defined.
+        """
+
+        app_settings = AppSettings.objects.all()
+
+        queryset = Organization.objects.all()
+
+        if getattr(app_settings[0], 'global_organization', None):
+
+            queryset = queryset.exclude(id=app_settings[0].global_organization.id)
+
+        return queryset
+
 
 
 class CommonBaseSerializer(serializers.ModelSerializer):
@@ -12,5 +36,18 @@ class CommonBaseSerializer(serializers.ModelSerializer):
 
 
 class CommonModelSerializer(CommonBaseSerializer):
+    """Common Model Serializer
+
+    _**Note:** This serializer is not inherited by the organization Serializer_
+    _`access.serializers.organization`, this is by design_
+
+    This serializer is included within ALL model (Tenancy Model) serilaizers and is intended to be used
+    to add objects that ALL model serializers will require.
+
+    Args:
+        CommonBaseSerializer (Class): Common base serializer
+    """
 
-    model_notes = centurion_field.MarkdownField( required = False )
\ No newline at end of file
+    model_notes = centurion_field.MarkdownField( required = False )
+    
+    organization = OrganizationField(required = False)
\ No newline at end of file
diff --git a/docs/projects/centurion_erp/administration/installation.md b/docs/projects/centurion_erp/administration/installation.md
index 7378e1c6e..eee888587 100644
--- a/docs/projects/centurion_erp/administration/installation.md
+++ b/docs/projects/centurion_erp/administration/installation.md
@@ -22,11 +22,13 @@ Basic installation steps are as follows:
 
 1. Deploy a RabbitMQ Server
 
-1. Deploy a Web container for Centurion ERP
+1. Deploy a API container for Centurion ERP
+
+1. Deploy a UI container for Centurion ERP
 
 1. Deploy a Worker container for Centurion ERP
 
-1. Add settings file to path `/etc/itsm/settings.py` for both Centurion ERP containers.
+1. Add settings file to path `/etc/itsm/settings.py` for both API and worker Centurion ERP containers.
 
 1. Run migrations
 
@@ -42,12 +44,20 @@ As Centurion ERP is uses the Django Framework, Theoretically Every Django suppor
 
 ### RabbitMQ Server
 
-Centurion ERP uses RabbitMQ as for its worker queue. As tasks are created when using Centurion ERP, they are added to the RabbitMQ server for the background worker to pickup. When the background worker picks up the task, it does it's business, clears the task from the RabbitMQ server and saves the [results](../user/core/index.md#background-worker) within the Database.
+Centurion ERP uses RabbitMQ for its worker queue. As tasks are created when using Centurion ERP, they are added to the RabbitMQ server for the background worker to pickup. When the background worker picks up the task, it does it's business, clears the task from the RabbitMQ server and saves the [results](../user/core/index.md#background-worker) within the Database.
+
+
+### API Container
+
+The [API container](https://hub.docker.com/r/nofusscomputing/centurion-erp) is the guts of Centurion ERP. It provides the endpoints for interacting with Centurion ERP. This container is scalable with the only additional requirement being that a load-balancer be placed in front of all web containers for traffic routing. If deploying to Kubernetes the service load-balancer is sufficient and setting the deployment `replicas` to the number of desired containers is the simplest method to scale.
+
 
+### UI Container
 
-### Web Container
+!!! info
+    Currently we are still developing the UI. As such it's still considered beta. This will remain until the new UI has [feature parity](https://github.com/nofusscomputing/centurion_erp_ui/issues/18) with the current django UI.
 
-The [web container](https://hub.docker.com/r/nofusscomputing/centurion-erp) is the guts of Centurion ERP. It provides the interface and endpoints for interacting with Centurion ERP. This container is scalable with the only additional requirement being that a load-balancer be placed in front of all web containers for traffic routing. If deploying to Kubernetes the service load-balancer is sufficient and setting the deployment `replicas` to the number of desired containers is the simplest method to scale.
+The [UI container](https://hub.docker.com/r/nofusscomputing/centurion-erp-ui) is the user interface for Centurion. The user interface uses the react framework so as to take advantage of the UI running locally on the users machine. This reduces the bandwidth requirements for using Centurion to be approximatly the data they request and not the page as well.
 
 
 ### Background Worker Container
@@ -59,7 +69,7 @@ Configuration for the worker resides in directory `/etc/itsm/` within the contai
 
 ### Settings file
 
-The settings file is a python file `.py` and must remain a valid python file for the application to work. Settings for the application are stored within a docker volume at path `/etc/itsm/`, with the settings living in `.py` files. A database is also required for the application to store it's settings. SQLLite and MariaDB/MySQL are supported.
+The settings file is a python file `.py` and must remain a valid python file for the application to work. Settings for the application are stored within a docker volume at path `/etc/itsm/`, with the settings living in `.py` files. A database is also required for the application to store it's settings. PostgreSQL is supported.
 
 ``` py title="settings.py"
 
diff --git a/docs/projects/centurion_erp/development/views.md b/docs/projects/centurion_erp/development/views.md
index 2fafcc764..ff47d086d 100644
--- a/docs/projects/centurion_erp/development/views.md
+++ b/docs/projects/centurion_erp/development/views.md
@@ -108,6 +108,8 @@ When adding a view, that is also meant to be seen by the end user, a navigation
 
 - `link` the relative URL for the entry. this will be the relative URL of the API after the API's version number. _i.e. `/api/v2/assistance/ticket/request` would become `/assistance/ticket/request`_
 
+Testing of the navigation is via `api.tests.unit.test_navigation_menu.py` If the item has a navigation menu entry the `setUpTestData` will need to be updated, along with test cases for the entry.
+
 
 ### Menu
 
diff --git a/requirements.txt b/requirements.txt
index cb46e7a18..3ee9ca188 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
 
-Django==5.1.2
+Django==5.1.4
 django-cors-headers==4.4.0
 
 django-debug-toolbar==4.3.0


Test patch:
diff --git a/app/api/tests/unit/test_navigation_menu.py b/app/api/tests/unit/test_navigation_menu.py
new file mode 100644
index 000000000..7ac7a91d7
--- /dev/null
+++ b/app/api/tests/unit/test_navigation_menu.py
@@ -0,0 +1,1557 @@
+from django.contrib.auth.models import User
+from django.contrib.contenttypes.models import ContentType
+from django.test import Client, TestCase
+
+from access.models import Organization, Team, TeamUsers, Permission
+
+from api.react_ui_metadata import ReactUIMetadata
+
+
+class NavigationMenu(
+    TestCase
+):
+
+
+    @classmethod
+    def setUpTestData(self):
+        
+        organization = Organization.objects.create(name='test_org')
+
+        self.organization = organization
+
+        users_to_create: dict = {
+            'access': [
+                {
+                    'content_model': 'organization',
+                    'permission_model': 'organization'
+                }
+            ],
+            'assistance': [
+                {
+                    'content_model': 'knowledgebase',
+                    'permission_model': 'knowledgebase'
+                },
+                {
+                    'content_model': 'knowledgebasecategory',
+                    'permission_model': 'knowledgebasecategory'
+                }
+            ],
+            'config_management': [
+                {
+                    'content_model': 'configgroups',
+                    'permission_model': 'configgroups'
+                }
+            ],
+            'core': [
+                {
+                    'content_model': 'manufacturer',
+                    'permission_model': 'manufacturer'
+                },
+                {
+                    'content_model': 'ticket',
+                    'permission_model': 'ticket_change'
+                },
+                {
+                    'content_model': 'ticket',
+                    'permission_model': 'ticket_incident'
+                },
+                {
+                    'content_model': 'ticket',
+                    'permission_model': 'ticket_problem'
+                },
+                {
+                    'content_model': 'ticket',
+                    'permission_model': 'ticket_request'
+                },
+                {
+                    'content_model': 'ticketcategory',
+                    'permission_model': 'ticketcategory'
+                },
+                {
+                    'content_model': 'ticketcommentcategory',
+                    'permission_model': 'ticketcommentcategory'
+                }
+            ],
+            'django_celery_results': [
+                {
+                    'content_model': 'taskresult',
+                    'permission_model': 'taskresult'
+                }
+            ],
+            'itam': [
+                {
+                    'content_model': 'device',
+                    'permission_model': 'device'
+                },
+                {
+                    'content_model': 'devicemodel',
+                    'permission_model': 'devicemodel'
+                },
+                {
+                    'content_model': 'devicetype',
+                    'permission_model': 'devicetype'
+                },
+                {
+                    'content_model': 'operatingsystem',
+                    'permission_model': 'operatingsystem'
+                },
+                {
+                    'content_model': 'software',
+                    'permission_model': 'software'
+                },
+                {
+                    'content_model': 'softwarecategory',
+                    'permission_model': 'softwarecategory'
+                }
+            ],
+            'itim': [
+                {
+                    'content_model': 'cluster',
+                    'permission_model': 'cluster'
+                },
+                {
+                    'content_model': 'clustertype',
+                    'permission_model': 'clustertype'
+                },
+                {
+                    'content_model': 'service',
+                    'permission_model': 'service'
+                }
+            ],
+            'project_management': [
+                {
+                    'content_model': 'project',
+                    'permission_model': 'project'
+                },
+                {
+                    'content_model': 'projectstate',
+                    'permission_model': 'projectstate'
+                },
+                {
+                    'content_model': 'projecttype',
+                    'permission_model': 'projecttype'
+                }
+            ],
+            'settings': [
+                {
+                    'content_model': 'appsettings',
+                    'permission_model': 'appsettings'
+                }
+            ]
+        }
+
+
+        # app_label = 'access'
+        # model_name = 'organization'
+
+        for app_label, model_names in users_to_create.items():
+
+            for model_name in model_names:
+
+                setattr(self, app_label + "_" + model_name['permission_model'], User.objects.create_user(username= app_label + "_" + model_name['permission_model'], password="password"))
+
+                team = Team.objects.create(
+                    team_name = app_label + "_" + model_name['permission_model'],
+                    organization = organization,
+                )
+
+                permission = Permission.objects.get(
+                        codename = 'view_' + model_name['permission_model'],
+                        content_type = ContentType.objects.get(
+                            app_label = app_label,
+                            model = model_name['content_model'],
+                        )
+                    )
+
+                team.permissions.set( [ permission ] )
+
+                team_user = TeamUsers.objects.create(
+                    team = team,
+                    user = getattr(self, app_label + "_" + model_name['permission_model'])
+                )
+
+        self.metadata = ReactUIMetadata()
+
+
+
+
+    def test_navigation_menu_visible_access_organization_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.access_organization)
+
+        menu_name = 'access'
+
+        page_name = 'organization'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_access_organization_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.access_organization)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_config_management_configgroups_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.config_management_configgroups)
+
+        menu_name = 'config_management'
+
+        page_name = 'group'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_config_management_configgroups_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.config_management_configgroups)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_assistance_knowledgebase_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.assistance_knowledgebase)
+
+        menu_name = 'assistance'
+
+        page_name = 'knowledge_base'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_assistance_knowledgebase_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.assistance_knowledgebase)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_assistance_request_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_request)
+
+        menu_name = 'assistance'
+
+        page_name = 'request'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_assistance_request_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_request)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_django_celery_results_taskresult_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.django_celery_results_taskresult)
+
+        menu_name = 'settings'
+
+        page_name = 'celery_log'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_django_celery_results_taskresult_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.django_celery_results_taskresult)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itam_device_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_device)
+
+        menu_name = 'itam'
+
+        page_name = 'device'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itam_device_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_device)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itam_operating_system_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_operatingsystem)
+
+        menu_name = 'itam'
+
+        page_name = 'operating_system'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itam_operating_system_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_operatingsystem)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itam_software_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_software)
+
+        menu_name = 'itam'
+
+        page_name = 'software'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itam_software_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_software)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itim_cluster_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itim_cluster)
+
+        menu_name = 'itim'
+
+        page_name = 'cluster'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itim_cluster_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itim_cluster)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itim_ticket_change_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_change)
+
+        menu_name = 'itim'
+
+        page_name = 'ticket_change'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itim_ticket_change_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_change)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itim_ticket_incident_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_incident)
+
+        menu_name = 'itim'
+
+        page_name = 'ticket_incident'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itim_ticket_incident_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_incident)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itim_ticket_problem_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_problem)
+
+        menu_name = 'itim'
+
+        page_name = 'ticket_problem'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itim_ticket_problem_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticket_problem)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itim_service_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itim_service)
+
+        menu_name = 'itim'
+
+        page_name = 'service'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itim_service_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itim_service)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_project_management_project_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.project_management_project)
+
+        menu_name = 'project_management'
+
+        page_name = 'project'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_project_management_project_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.project_management_project)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+
+
+
+
+
+
+
+
+    def test_navigation_menu_visible_settings_appsettings_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.settings_appsettings)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_settings_appsettings_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.settings_appsettings)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itim_clustertype_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itim_clustertype)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itim_clustertype_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itim_clustertype)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itam_devicemodel_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_devicemodel)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itam_devicemodel_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_devicemodel)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itam_devicetype_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_devicetype)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itam_devicetype_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_devicetype)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_assistance_knowledgebasecategory_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.assistance_knowledgebasecategory)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_assistance_knowledgebasecategory_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.assistance_knowledgebasecategory)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_core_manufacturer_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_manufacturer)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_core_manufacturer_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_manufacturer)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_project_management_projectstate_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.project_management_projectstate)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_project_management_projectstate_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.project_management_projectstate)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_project_management_projecttype_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.project_management_projecttype)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_project_management_projecttype_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.project_management_projecttype)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_itam_softwarecategory_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_softwarecategory)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_itam_softwarecategory_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.itam_softwarecategory)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_core_ticketcategory_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticketcategory)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_core_ticketcategory_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticketcategory)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1
+
+
+
+    def test_navigation_menu_visible_core_ticketcommentcategory_exist(self):
+        """Navigation Menu Check
+
+        Ensure that if the user has the permission, the navigation menu and
+        page is available for the user
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticketcommentcategory)
+
+        menu_name = 'settings'
+
+        page_name = 'setting'
+
+        menu_page_exists: bool = False
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                if(
+                    menu['name'] == menu_name
+                    and page['name'] == page_name
+                ):
+
+                    menu_page_exists = True
+
+
+        assert menu_page_exists
+
+
+
+    def test_navigation_menu_visible_core_ticketcommentcategory_no_additional_exist(self):
+        """Navigation Menu Check
+
+        Ensure that only the navigation menu and entry is the only one displayed
+        for the user who has the desired permission
+        """
+
+        nav_menu = self.metadata.get_navigation(self.core_ticketcommentcategory)
+
+        pages_found: int = 0
+
+
+        for menu in nav_menu:
+
+            for page in menu['pages']:
+
+                pages_found += 1
+
+
+        assert pages_found == 1