From 0786977633a7c3c2a1b6ee387951f7951da0bc9b Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 06:11:25 +0930 Subject: [PATCH 01/84] refactor(api): move metadata url_return -> urls.self ref: #411 nofusscomputing/centurion_erp_ui#29 nofusscomputing/centurion_erp_ui#33 --- app/api/react_ui_metadata.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py index 2cc07e17c..20489ef03 100644 --- a/app/api/react_ui_metadata.py +++ b/app/api/react_ui_metadata.py @@ -64,23 +64,31 @@ def determine_metadata(self, request, view): metadata["description"] = view.get_view_description() - if 'pk' in view.kwargs: + metadata['urls']: dict = {} - if view.kwargs['pk']: + url_self = None - qs = view.get_queryset()[0] - if hasattr(qs, 'get_url'): + if view.kwargs.get('pk', None) is not None: + + qs = view.get_queryset()[0] + + if hasattr(qs, 'get_url'): + + url_self = qs.get_url( request ) - metadata['return_url'] = qs.get_url( request ) elif view.kwargs: - metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs ) + url_self = reverse('v2:' + view.basename + '-list', request = view.request, kwargs = view.kwargs ) else: - metadata['return_url'] = reverse('v2:' + view.basename + '-list', request = view.request ) + url_self = reverse('v2:' + view.basename + '-list', request = view.request ) + + if url_self: + + metadata['urls'].update({'self': url_self}) metadata["renders"] = [ From cee396da3fb48aca63a506dbe10c510445fe9294 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 12:55:59 +0930 Subject: [PATCH 02/84] refactor(assistance): make content the first tab for kb articles ref: #411 closes #405 --- app/assistance/models/knowledge_base.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/assistance/models/knowledge_base.py b/app/assistance/models/knowledge_base.py index 706e394bb..520a6dde0 100644 --- a/app/assistance/models/knowledge_base.py +++ b/app/assistance/models/knowledge_base.py @@ -250,6 +250,24 @@ class Meta: page_layout: dict = [ + { + "name": "Content", + "slug": "content", + "sections": [ + { + "layout": "single", + "fields": [ + 'summary', + ] + }, + { + "layout": "single", + "fields": [ + 'content', + ] + } + ] + }, { "name": "Details", "slug": "details", From e4ce1b539e802c2111447495e6bc4e478cf6bbcc Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 13:57:31 +0930 Subject: [PATCH 03/84] feat(api): Add `return_url` to metadata ref: #410 #411 --- app/api/react_ui_metadata.py | 4 ++++ app/api/viewsets/common.py | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py index 20489ef03..b021da447 100644 --- a/app/api/react_ui_metadata.py +++ b/app/api/react_ui_metadata.py @@ -90,6 +90,10 @@ def determine_metadata(self, request, view): metadata['urls'].update({'self': url_self}) + if view.get_return_url(): + + metadata['urls'].update({'return_url': view.get_return_url()}) + metadata["renders"] = [ renderer.media_type for renderer in view.renderer_classes diff --git a/app/api/viewsets/common.py b/app/api/viewsets/common.py index 74c6e00c6..71b8eb7f9 100644 --- a/app/api/viewsets/common.py +++ b/app/api/viewsets/common.py @@ -115,6 +115,26 @@ def get_page_layout(self): return self.page_layout + def get_return_url(self) -> str: + """Metadata return URL + + This URL is an optional URL that if required the view must + override this method. If the URL for a cancel operation + is not the models URL, then this method is used to return + the URL that will be used. + + Defining this URL will predominatly be for sub-models. It's + recommended that the `reverse` function + (rest_framework.reverse.reverse) be used with a `request` + object. + + Returns: + str: Full url in format `://./api//` + """ + + return None + + def get_table_fields(self): if len(self.table_fields) < 1: From 4ae965603c58061eae9405c5a88b1b5f0106de73 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 13:58:00 +0930 Subject: [PATCH 04/84] feat(api): Add `back` url to metadata ref: #410 #411 --- app/api/react_ui_metadata.py | 4 ++++ app/api/viewsets/common.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py index b021da447..2ea73030d 100644 --- a/app/api/react_ui_metadata.py +++ b/app/api/react_ui_metadata.py @@ -90,6 +90,10 @@ def determine_metadata(self, request, view): metadata['urls'].update({'self': url_self}) + if view.get_back_url(): + + metadata['urls'].update({'back': view.get_back_url()}) + if view.get_return_url(): metadata['urls'].update({'return_url': view.get_return_url()}) diff --git a/app/api/viewsets/common.py b/app/api/viewsets/common.py index 71b8eb7f9..c549f59eb 100644 --- a/app/api/viewsets/common.py +++ b/app/api/viewsets/common.py @@ -82,6 +82,25 @@ def allowed_methods(self): view_name: str = None + def get_back_url(self) -> str: + """Metadata Back URL + + This URL is an optional URL that if required the view must + override this method. If the URL for a back operation + is not the models URL, then this method is used to return + the URL that will be used. + + Defining this URL will predominatly be for sub-models. It's + recommended that the `reverse` function + (rest_framework.reverse.reverse) be used with a `request` + object. + + Returns: + str: Full url in format `://./api//` + """ + + return None + def get_model_documentation(self): From 22615e46ef9ad5e0a4ebc48edbfc69d0802bda2c Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 13:58:47 +0930 Subject: [PATCH 05/84] fix(access): correct team users table to correct data key ref: #411 --- app/access/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/access/models.py b/app/access/models.py index 0cc5cdf8d..5f850ff7d 100644 --- a/app/access/models.py +++ b/app/access/models.py @@ -369,7 +369,7 @@ def save(self, force_insert=False, force_update=False, using=None, update_fields { "layout": "table", "name": "Users", - "field": "user", + "field": "users", }, ] }, From c8b6a31cd4acefdab065768c039ea4789e0a5d95 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 14:03:31 +0930 Subject: [PATCH 06/84] feat(access): add `back` and `return_url` urls to team metadata ref: #410 #411 --- app/access/viewsets/team.py | 38 +++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/access/viewsets/team.py b/app/access/viewsets/team.py index 0c3b4fc9b..551ec13f8 100644 --- a/app/access/viewsets/team.py +++ b/app/access/viewsets/team.py @@ -1,5 +1,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse +from access.models import Organization + from access.serializers.teams import ( Team, TeamModelSerializer, @@ -124,6 +126,24 @@ class ViewSet( ModelViewSet ): view_description = 'Teams belonging to a single organization' + + def get_back_url(self) -> str: + + if( + getattr(self, '_back_url', None) is None + ): + + return_model = Organization.objects.get( + pk = self.kwargs['organization_id'] + ) + + self._back_url = str( + return_model.get_url( self.request ) + ) + + return self._back_url + + def get_queryset(self): queryset = super().get_queryset() @@ -146,3 +166,21 @@ def get_serializer_class(self): return globals()[str( self.model._meta.verbose_name) + 'ModelSerializer'] + + def get_return_url(self) -> str: + + if getattr(self, '_get_return_url', None): + + return self._get_return_url + + if self.kwargs.get('pk', None) is None: + + return_model = Organization.objects.get( + pk = self.kwargs['organization_id'] + ) + + self._get_return_url = return_model.get_url( self.request ) + + return self._get_return_url + + return None From 7c9320a84bdf516f7311ccc8452968b86316679e Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 14:03:39 +0930 Subject: [PATCH 07/84] feat(access): add `back` and `return_url` urls to team user metadata ref: #410 #411 --- app/access/viewsets/team_user.py | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/access/viewsets/team_user.py b/app/access/viewsets/team_user.py index 2d753a213..7cc9c6a52 100644 --- a/app/access/viewsets/team_user.py +++ b/app/access/viewsets/team_user.py @@ -1,5 +1,7 @@ from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter, OpenApiResponse +from access.models import Team + from access.serializers.team_user import ( TeamUsers, TeamUserModelSerializer, @@ -147,6 +149,24 @@ class ViewSet( ModelViewSet ): view_description = 'Users belonging to a single team' + + def get_back_url(self) -> str: + + if( + getattr(self, '_back_url', None) is None + ): + + return_model =Team.objects.get( + pk = self.kwargs['team_id'] + ) + + self._back_url = str( + return_model.get_url( self.request ) + ) + + return self._back_url + + def get_queryset(self): queryset = super().get_queryset() @@ -172,3 +192,21 @@ def get_serializer_class(self): return globals()[str( self.model._meta.verbose_name).replace(' ', '') + 'ModelSerializer'] + + def get_return_url(self): + + if getattr(self, '_get_return_url', None): + + return self._get_return_url + + if self.kwargs.get('pk', None) is None: + + return_model = Team.objects.get( + pk = self.kwargs['team_id'] + ) + + self._get_return_url = return_model.get_url( self.request ) + + return self._get_return_url + + return None From aaf2d23c53ea188da53aa628501c21dec8480097 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 14:54:33 +0930 Subject: [PATCH 08/84] test(api): API Metadata functional Test Cases ref: #408 #411 #412 --- .../abstract/test_metadata_functional.py | 502 ++++++++++++++++++ 1 file changed, 502 insertions(+) create mode 100644 app/api/tests/abstract/test_metadata_functional.py diff --git a/app/api/tests/abstract/test_metadata_functional.py b/app/api/tests/abstract/test_metadata_functional.py new file mode 100644 index 000000000..43a03c63a --- /dev/null +++ b/app/api/tests/abstract/test_metadata_functional.py @@ -0,0 +1,502 @@ +import pytest +from django.test import Client + +from rest_framework.reverse import reverse + + + +class MetadataAttributesFunctional: + """ Functional Tests for API, HTTP/Options Method + + These tests ensure that **ALL** serializers include the metaclass that adds the required + data to the HTTP Options method. + + Metaclass adds data required for the UI to function correctly. + """ + + app_namespace: str = None + + url_name: str = None + + + def test_method_options_request_list_ok(self): + """Test HTTP/Options Method + + Ensure the request returns `OK`. + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert response.status_code == 200 + + + def test_method_options_request_list_data_returned(self): + """Test HTTP/Options Method + + Ensure the request returns data. + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert response.data is not None + + + def test_method_options_request_list_data_type(self): + """Test HTTP/Options Method + + Ensure the request data returned is of type `dict` + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert type(response.data) is dict + + + def test_method_options_request_list_data_has_key_table_fields(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `table_fields` + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert 'table_fields' in response.data + + + def test_method_options_request_list_data_key_table_fields_is_list(self): + """Test HTTP/Options Method + + Ensure the request data['table_fields'] is of type `list` + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert type(response.data['table_fields']) is list + + + def test_method_options_request_list_data_key_table_fields_is_list_of_str(self): + """Test HTTP/Options Method + + Ensure the request data['table_fields'] list is of `str` + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + all_string = True + + for item in response.data['table_fields']: + + if type(item) is not str: + + all_string = False + + + assert all_string + + + def test_method_options_request_detail_ok(self): + """Test HTTP/Options Method + + Ensure the request returns `OK`. + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert response.status_code == 200 + + + def test_method_options_request_detail_data_returned(self): + """Test HTTP/Options Method + + Ensure the request returns data. + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert response.data is not None + + + def test_method_options_request_detail_data_type(self): + """Test HTTP/Options Method + + Ensure the request data returned is of type `dict` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert type(response.data) is dict + + + def test_method_options_request_detail_data_has_key_page_layout(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `layout` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert 'layout' in response.data + + + def test_method_options_request_detail_data_key_page_layout_is_list(self): + """Test HTTP/Options Method + + Ensure the request data['layout'] is of type `list` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert type(response.data['layout']) is list + + + def test_method_options_request_detail_data_key_page_layout_is_list_of_dict(self): + """Test HTTP/Options Method + + Ensure the request data['layout'] list is of `dict` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + all_dict = True + + for item in response.data['layout']: + + if type(item) is not dict: + + all_dict = False + + + assert all_dict + + + def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_name(self): + """Test HTTP/Options Method + + Ensure the request data['layout'].x has key `name` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + has_key = True + + for item in response.data['layout']: + + if 'name' not in item: + + has_key = False + + + assert has_key + + + def test_method_options_request_detail_data_key_page_layout_dicts_key_type_name(self): + """Test HTTP/Options Method + + Ensure the request data['layout'].x.[name] is of type `str` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + all_are_str = True + + for item in response.data['layout']: + + if type(item['name']) is not str: + + all_are_str = False + + + assert all_are_str + + + def test_method_options_request_detail_data_key_page_layout_dicts_key_exists_sections(self): + """Test HTTP/Options Method + + Ensure the request data['layout'].x has key `sections` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + has_key = True + + for item in response.data['layout']: + + if 'sections' not in item: + + has_key = False + + + assert has_key + + + def test_method_options_request_detail_data_key_page_layout_dicts_key_type_sections(self): + """Test HTTP/Options Method + + Ensure the request data['layout'].x.[sections] is of type `list` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + all_are_str = True + + for item in response.data['layout']: + + if type(item['sections']) is not list: + + all_are_str = False + + + assert all_are_str + + + + def test_method_options_request_detail_data_has_key_urls(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `urls` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert 'urls' in response.data + + + def test_method_options_request_detail_data_key_urls_is_dict(self): + """Test HTTP/Options Method + + Ensure the request data key `urls` is dict + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert type(response.data['urls']) is dict + + + + def test_method_options_request_detail_data_has_key_urls_self(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `urls.self` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert 'urls' in response.data + + + def test_method_options_request_detail_data_key_urls_self_is_str(self): + """Test HTTP/Options Method + + Ensure the request data key `urls.self` is a string + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert type(response.data['urls']['self']) is str + + + + @pytest.mark.skip(reason='to be written') + def test_method_options_no_field_is_generic(self): + """Test HTTP/Options Method + + Fields are used for the UI to setup inputs correctly. + + Ensure all fields at path `.actions...type` do not have `GenericField` as the value. + """ + + pass From 1904c2e28cbd9c4454b636bc17b729c8641862a6 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 14:57:48 +0930 Subject: [PATCH 09/84] test(access): API Metadata checks for Team model ref: #408 #410 #411 #412 --- .../team/test_team_permission_viewset.py | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/app/access/tests/functional/team/test_team_permission_viewset.py b/app/access/tests/functional/team/test_team_permission_viewset.py index aec401438..91400a3b4 100644 --- a/app/access/tests/functional/team/test_team_permission_viewset.py +++ b/app/access/tests/functional/team/test_team_permission_viewset.py @@ -6,10 +6,13 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser, User from django.contrib.contenttypes.models import ContentType -from django.test import TestCase +from django.test import Client, TestCase + +from rest_framework.reverse import reverse from access.models import Organization, Team, TeamUsers, Permission +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases @@ -199,3 +202,100 @@ class TeamViewSet( ): pass + + + +class TeamMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase, + +): + + + + def test_method_options_request_detail_data_has_key_urls_back(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `urls.back` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert 'back' in response.data['urls'] + + + def test_method_options_request_detail_data_key_urls_back_is_str(self): + """Test HTTP/Options Method + + Ensure the request data key `urls.back` is str + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert type(response.data['urls']['back']) is str + + + + def test_method_options_request_list_data_has_key_urls_return_url(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `urls.return_url` + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert 'return_url' in response.data['urls'] + + + def test_method_options_request_list_data_key_urls_return_url_is_str(self): + """Test HTTP/Options Method + + Ensure the request data key `urls.return_url` is str + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert type(response.data['urls']['return_url']) is str + + From deb93378b0d5dbd9c87bbcb355af00fd85de3bcf Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 15:00:43 +0930 Subject: [PATCH 10/84] test(access): API Metadata checks for Team User model ref: #408 #410 #411 #412 --- .../test_team_user_permission_viewset.py | 102 +++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/app/access/tests/functional/team_user/test_team_user_permission_viewset.py b/app/access/tests/functional/team_user/test_team_user_permission_viewset.py index 45f099b05..2dad2d8d9 100644 --- a/app/access/tests/functional/team_user/test_team_user_permission_viewset.py +++ b/app/access/tests/functional/team_user/test_team_user_permission_viewset.py @@ -6,10 +6,13 @@ from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser, User from django.contrib.contenttypes.models import ContentType -from django.test import TestCase +from django.test import Client, TestCase + +from rest_framework.reverse import reverse from access.models import Organization, Team, TeamUsers, Permission +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases @@ -208,4 +211,99 @@ class TeamUserViewSet( TestCase ): - pass \ No newline at end of file + pass + + + +class TeamUserMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase, + +): + + + + def test_method_options_request_detail_data_has_key_urls_back(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `urls.back` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert 'back' in response.data['urls'] + + + def test_method_options_request_detail_data_key_urls_back_is_str(self): + """Test HTTP/Options Method + + Ensure the request data key `urls.back` is str + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) + + assert type(response.data['urls']['back']) is str + + + + def test_method_options_request_list_data_has_key_urls_return_url(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `urls.return_url` + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert 'return_url' in response.data['urls'] + + + def test_method_options_request_list_data_key_urls_return_url_is_str(self): + """Test HTTP/Options Method + + Ensure the request data key `urls.return_url` is str + """ + + client = Client() + client.force_login(self.view_user) + + if self.url_kwargs: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( url, content_type='application/json' ) + + assert type(response.data['urls']['return_url']) is str From 6ed4db0502e0b4b09cf6b88f07fdda8e33881be4 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 30 Nov 2024 16:13:20 +0930 Subject: [PATCH 11/84] feat(api): Add API version details to the metadata ref: #411 nofusscomputing/centurion_erp_ui#29 --- app/api/react_ui_metadata.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py index 2ea73030d..1b9f6fff6 100644 --- a/app/api/react_ui_metadata.py +++ b/app/api/react_ui_metadata.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.utils.encoding import force_str from rest_framework import serializers @@ -139,6 +140,32 @@ def determine_metadata(self, request, view): metadata['layout'] = view.get_page_layout() + build_repo: str = None + + if settings.BUILD_REPO: + + build_repo = settings.BUILD_REPO + + build_sha: str = None + + if settings.BUILD_SHA: + + build_sha = settings.BUILD_SHA + + build_version: str = 'development' + + if settings.BUILD_VERSION: + + build_version = settings.BUILD_VERSION + + + metadata['version']: dict = { + 'project_url': build_repo, + 'sha': build_sha, + 'version': build_version, + } + + metadata['navigation'] = [ { "display_name": "Access", From 827fe14369c379ebd2c50ef96cd9e04b08944f46 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 1 Dec 2024 11:11:14 +0930 Subject: [PATCH 12/84] fix(core): History query must also be for self, not just children ref: #414 #415 --- app/core/viewsets/history.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/core/viewsets/history.py b/app/core/viewsets/history.py index be7fcd097..27fabeda1 100644 --- a/app/core/viewsets/history.py +++ b/app/core/viewsets/history.py @@ -1,3 +1,5 @@ +from django.db.models import Q + from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse from api.viewsets.common import ModelViewSet @@ -46,9 +48,10 @@ def get_queryset(self): queryset = super().get_queryset() self.queryset = queryset.filter( - item_parent_class = self.kwargs['model_class'], - item_parent_pk = self.kwargs['model_id'] - ).order_by('-created') + Q(item_pk = self.kwargs['model_id'], item_class = self.kwargs['model_class']) + | + Q(item_parent_pk = self.kwargs['model_id'], item_parent_class = self.kwargs['model_class']) + ) return self.queryset From d0118e1f6fcbc3f27bc9cbf49d8e2fdb658d6c68 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 3 Dec 2024 17:13:59 +0930 Subject: [PATCH 13/84] feat(api): Filter navigation menu by user permissions if the user has the permission they will have the nav menu. ref: #409 #415 --- app/api/react_ui_metadata.py | 338 +++++++++++------- .../centurion_erp/development/views.md | 68 ++++ 2 files changed, 274 insertions(+), 132 deletions(-) diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py index 1b9f6fff6..98c12b11f 100644 --- a/app/api/react_ui_metadata.py +++ b/app/api/react_ui_metadata.py @@ -1,6 +1,8 @@ from django.conf import settings from django.utils.encoding import force_str +from django.contrib.auth.models import ContentType, Permission + from rest_framework import serializers from rest_framework_json_api.metadata import JSONAPIMetadata from rest_framework.request import clone_request @@ -166,136 +168,7 @@ def determine_metadata(self, request, view): } - metadata['navigation'] = [ - { - "display_name": "Access", - "name": "access", - "pages": [ - { - "display_name": "Organization", - "name": "organization", - "link": "/access/organization" - } - ] - }, - { - "display_name": "Assistance", - "name": "assistance", - "pages": [ - { - "display_name": "Requests", - "name": "request", - "icon": "ticket_request", - "link": "/assistance/ticket/request" - }, - { - "display_name": "Knowledge Base", - "name": "knowledge_base", - "icon": "information", - "link": "/assistance/knowledge_base" - } - ] - }, - { - "display_name": "ITAM", - "name": "itam", - "pages": [ - { - "display_name": "Devices", - "name": "device", - "icon": "device", - "link": "/itam/device" - }, - { - "display_name": "Operating System", - "name": "operating_system", - "link": "/itam/operating_system" - }, - { - "display_name": "Software", - "name": "software", - "link": "/itam/software" - } - ] - }, - { - "display_name": "ITIM", - "name": "itim", - "pages": [ - { - "display_name": "Changes", - "name": "ticket_change", - "link": "/itim/ticket/change" - }, - { - "display_name": "Clusters", - "name": "cluster", - "link": "/itim/cluster" - }, - { - "display_name": "Incidents", - "name": "ticket_incident", - "link": "/itim/ticket/incident" - }, - { - "display_name": "Problems", - "name": "ticket_problem", - "link": "/itim/ticket/problem" - }, - { - "display_name": "Services", - "name": "service", - "link": "/itim/service" - }, - ] - }, - { - "display_name": "Config Management", - "name": "config_management", - "icon": "ansible", - "pages": [ - { - "display_name": "Groups", - "name": "group", - "icon": 'config_management', - "link": "/config_management/group" - } - ] - }, - { - "display_name": "Project Management", - "name": "project_management", - "icon": 'project', - "pages": [ - { - "display_name": "Projects", - "name": "project", - "icon": 'kanban', - "link": "/project_management/project" - } - ] - }, - - { - "display_name": "Settings", - "name": "settings", - "pages": [ - { - "display_name": "System", - "name": "setting", - "icon": "system", - "link": "/settings" - }, - { - "display_name": "Task Log", - "name": "celery_log", - # "icon": "settings", - "link": "/settings/celery_log" - } - ] - } - ] - + metadata['navigation'] = self.get_navigation(request.user) return metadata @@ -377,7 +250,6 @@ def get_field_info(self, field): field_info["children"] = self.get_serializer_info(field) if ( - # not field_info.get("read_only") hasattr(field, "choices") ): field_info["choices"] = [ @@ -396,4 +268,206 @@ def get_field_info(self, field): field.field_name in serializer.included_serializers ) - return field_info \ No newline at end of file + return field_info + + + _nav = { + 'access': { + "display_name": "Access", + "name": "access", + "pages": { + 'view_organization': { + "display_name": "Organization", + "name": "organization", + "link": "/access/organization" + } + } + }, + 'assistance': { + "display_name": "Assistance", + "name": "assistance", + "pages": { + 'core.view_ticket_request': { + "display_name": "Requests", + "name": "request", + "icon": "ticket_request", + "link": "/assistance/ticket/request" + }, + 'view_knowledgebase': { + "display_name": "Knowledge Base", + "name": "knowledge_base", + "icon": "information", + "link": "/assistance/knowledge_base" + } + } + }, + 'itam': { + "display_name": "ITAM", + "name": "itam", + "pages": { + 'view_device': { + "display_name": "Devices", + "name": "device", + "icon": "device", + "link": "/itam/device" + }, + 'view_operatingsystem': { + "display_name": "Operating System", + "name": "operating_system", + "link": "/itam/operating_system" + }, + 'view_software': { + "display_name": "Software", + "name": "software", + "link": "/itam/software" + } + } + }, + 'itim': { + "display_name": "ITIM", + "name": "itim", + "pages": { + 'core.view_ticket_change': { + "display_name": "Changes", + "name": "ticket_change", + "link": "/itim/ticket/change" + }, + 'view_cluster': { + "display_name": "Clusters", + "name": "cluster", + "link": "/itim/cluster" + }, + 'core.view_ticket_incident': { + "display_name": "Incidents", + "name": "ticket_incident", + "link": "/itim/ticket/incident" + }, + 'core.view_ticket_problem': { + "display_name": "Problems", + "name": "ticket_problem", + "link": "/itim/ticket/problem" + }, + 'core.view_service': { + "display_name": "Services", + "name": "service", + "link": "/itim/service" + }, + } + }, + 'config_management': { + "display_name": "Config Management", + "name": "config_management", + "icon": "ansible", + "pages": { + 'view_configgroups': { + "display_name": "Groups", + "name": "group", + "icon": 'config_management', + "link": "/config_management/group" + } + } + }, + 'project_management': { + "display_name": "Project Management", + "name": "project_management", + "icon": 'project', + "pages": { + 'view_project': { + "display_name": "Projects", + "name": "project", + "icon": 'kanban', + "link": "/project_management/project" + } + } + }, + + 'settings': { + "display_name": "Settings", + "name": "settings", + "pages": { + 'view_settings': { + "display_name": "System", + "name": "setting", + "icon": "system", + "link": "/settings" + }, + 'django_celery_results.view_taskresult': { + "display_name": "Task Log", + "name": "celery_log", + # "icon": "settings", + "link": "/settings/celery_log" + } + } + } + } + + + def get_navigation(self, user) -> list(dict()): + """Render the navigation menu + + Check the users permissions agains `_nav`. if they have the permission, add the + menu entry to the navigation to be rendered, + + **No** Menu is to be rendered that contains no menu entries. + + Args: + user (User): User object from the request. + + Returns: + list(dict()): Rendered navigation menu in the format the UI requires it to be. + """ + + nav: list = [] + + processed_permissions: dict = {} + + for group in user.groups.all(): + + for permission in group.permissions.all(): + + if str(permission.codename).startswith('view_'): + + + if not processed_permissions.get(permission.content_type.app_label, None): + + processed_permissions.update({permission.content_type.app_label: {}}) + + if permission.codename not in processed_permissions[permission.content_type.app_label]: + + processed_permissions[permission.content_type.app_label].update({str(permission.codename): '_'}) + + + for app, entry in self._nav.items(): + + new_menu_entry: dict = {} + + new_pages: list = [] + + if processed_permissions.get(app, None): + + for permission, page in entry['pages'].items(): + + if '.' in permission: + + app_permission = str(permission).split('.') + + if processed_permissions[app_permission[0]].get(app_permission[1], None): + + new_pages += [ page ] + + else: + + if processed_permissions[app].get(permission, None): + + new_pages += [ page ] + + + if len(new_pages) > 0: + + new_menu_entry = entry.copy() + + new_menu_entry.update({ 'pages': new_pages }) + + nav += [ new_menu_entry ] + + return nav diff --git a/docs/projects/centurion_erp/development/views.md b/docs/projects/centurion_erp/development/views.md index 796fdd75f..ef2fe5a96 100644 --- a/docs/projects/centurion_erp/development/views.md +++ b/docs/projects/centurion_erp/development/views.md @@ -40,6 +40,8 @@ Views are used with Centurion ERP to Fetch the data for rendering. - _Functional test cases_ `from api.tests.abstract.api_permissions_viewset import APIPermission` +- View Added to Navigation + ## Permissions @@ -66,6 +68,72 @@ def get_dynamic_permissions(self): ``` +## Navigation + +Although Centurion ERP is a Rest API application, there is a UI. The UI uses data from Centurion's API to render the view that the end user sees. One of those items is the navigation structure. + +Location of the navigation is in `app/api/react_ui_metadata.py` under the attribute `_nav`. + + +### Menu Entry + +When adding a view, that is also meant to be seen by the end user, a navigation entry must be added to the correct navgation menu. The entry is a python dictionary and has the following format. + +``` pyhton + +{ + '.': { + "display_name": "", + "name": "", + "icon": "", + "link": "" + } +} + +``` + +- `app name` _Optional_ is the centurion application name the model belongs to. This entry should only be supplied if the application name for the entry does not match the application for the [navigation menu](#menu). + +- `permission name` is the centurion permission required for this menu entry to be rendered for the end user. + +- `display_name` Menu entry name that the end user will see + +- `name` This is used as part of the html rendering of the page. **must be unique** across ALL menu entries + +- `icon` _Optional_ if specified, this is the name of the icon that the UI will place next to the menu entry. If this is not specified, the name key is used as the icon name. + +- `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`_ + + +### Menu + +The navigation menu is obtained by the UI as part of the metadata. The structure of the menu is a python dictionary in the following format: + +``` python + + { + '': { + "display_name": "", + "name": "", + "pages": { + '' + } + } + } + +``` + +- `app name` the centurion application name the menu belongs to. + +- `display_name` Menu name that the end user will see + +- `name` This is used as part of the html rendering of the page. **must be unique** across ALL menu entries + +- `pages` [Menu entry](#menu-entry) dictionaries. + +Upon the UI requesting the navigation menu, the users permission are obtained, and if they have the permission for the menu entry within **any** organization, they will be presented with the menu that has a menu entries. + + ## Pre v1.3 Docs !!! warning From 38ba86b8b50d95862e5a746f0e33e3bf3c90beeb Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 3 Dec 2024 21:40:37 +0930 Subject: [PATCH 14/84] feat(access): filter permissions available only show used permissions ref: #415 closes #404 --- app/access/serializers/teams.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/access/serializers/teams.py b/app/access/serializers/teams.py index 9979c0c3a..2c72ffb0a 100644 --- a/app/access/serializers/teams.py +++ b/app/access/serializers/teams.py @@ -6,9 +6,10 @@ from api.serializers import common +from access.functions.permissions import permission_queryset from access.serializers.organization import OrganizationBaseSerializer -from app.serializers.permission import PermissionBaseSerializer +from app.serializers.permission import Permission, PermissionBaseSerializer from core import fields as centurion_field @@ -74,6 +75,7 @@ def get_url(self, item) -> dict: team_name = centurion_field.CharField( autolink = True ) + permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset()) class Meta: From 85face7cc64fb7dcd663930a41ae96579caebe44 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 17:30:19 +0930 Subject: [PATCH 15/84] fix(access): Team permissions is not a required field ref: #404 #415 --- app/access/serializers/teams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/access/serializers/teams.py b/app/access/serializers/teams.py index 2c72ffb0a..dedd5de9f 100644 --- a/app/access/serializers/teams.py +++ b/app/access/serializers/teams.py @@ -75,7 +75,7 @@ def get_url(self, item) -> dict: team_name = centurion_field.CharField( autolink = True ) - permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset()) + permissions = serializers.PrimaryKeyRelatedField(many = True, queryset=permission_queryset(), required = False) class Meta: From 6010973c3b78e2a3e06a6dc5c4445e696684e5d9 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 17:52:11 +0930 Subject: [PATCH 16/84] fix(core): Remove superfluous check from ticket viewset was fething the users default org wich is not required nor was it used ref: #415 fixes #403 --- app/core/viewsets/ticket.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/core/viewsets/ticket.py b/app/core/viewsets/ticket.py index f894d5fd3..99ad18ad5 100644 --- a/app/core/viewsets/ticket.py +++ b/app/core/viewsets/ticket.py @@ -251,17 +251,6 @@ def get_serializer_class(self): if ( - self.action == 'list' - ): - - user_settings = UserSettings.objects.get( - user = self.request.user - ) - - organization = user_settings.default_organization.id - - - elif ( self.action == 'create' ): From f13bdf5a05e5919680e8bb185c1c530626baab20 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 20:11:18 +0930 Subject: [PATCH 17/84] test(api): correct logic for test class attribute fetching ref: #415 --- app/api/tests/abstract/test_metadata_functional.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/api/tests/abstract/test_metadata_functional.py b/app/api/tests/abstract/test_metadata_functional.py index 43a03c63a..b54e41a6d 100644 --- a/app/api/tests/abstract/test_metadata_functional.py +++ b/app/api/tests/abstract/test_metadata_functional.py @@ -28,7 +28,7 @@ def test_method_options_request_list_ok(self): client = Client() client.force_login(self.view_user) - if self.url_kwargs: + if getattr(self, 'url_kwargs', None): url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) @@ -50,7 +50,7 @@ def test_method_options_request_list_data_returned(self): client = Client() client.force_login(self.view_user) - if self.url_kwargs: + if getattr(self, 'url_kwargs', None): url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) @@ -72,7 +72,7 @@ def test_method_options_request_list_data_type(self): client = Client() client.force_login(self.view_user) - if self.url_kwargs: + if getattr(self, 'url_kwargs', None): url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) @@ -94,7 +94,7 @@ def test_method_options_request_list_data_has_key_table_fields(self): client = Client() client.force_login(self.view_user) - if self.url_kwargs: + if getattr(self, 'url_kwargs', None): url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) @@ -116,7 +116,7 @@ def test_method_options_request_list_data_key_table_fields_is_list(self): client = Client() client.force_login(self.view_user) - if self.url_kwargs: + if getattr(self, 'url_kwargs', None): url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) @@ -138,7 +138,7 @@ def test_method_options_request_list_data_key_table_fields_is_list_of_str(self): client = Client() client.force_login(self.view_user) - if self.url_kwargs: + if getattr(self, 'url_kwargs', None): url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) @@ -169,7 +169,7 @@ def test_method_options_request_detail_ok(self): client = Client() client.force_login(self.view_user) - if self.url_kwargs: + if getattr(self, 'url_kwargs', None): url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) From 2b2c719e698e8867d29e71e44350c8cfc1bf8827 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 20:20:02 +0930 Subject: [PATCH 18/84] test(api): API Metadata test cases for navigation menu rendering ref: #412 #415 --- .../abstract/test_metadata_functional.py | 357 +++++++++++++++++- .../centurion_erp/development/views.md | 4 + 2 files changed, 360 insertions(+), 1 deletion(-) diff --git a/app/api/tests/abstract/test_metadata_functional.py b/app/api/tests/abstract/test_metadata_functional.py index b54e41a6d..6389be6b5 100644 --- a/app/api/tests/abstract/test_metadata_functional.py +++ b/app/api/tests/abstract/test_metadata_functional.py @@ -489,7 +489,6 @@ def test_method_options_request_detail_data_key_urls_self_is_str(self): assert type(response.data['urls']['self']) is str - @pytest.mark.skip(reason='to be written') def test_method_options_no_field_is_generic(self): """Test HTTP/Options Method @@ -500,3 +499,359 @@ def test_method_options_no_field_is_generic(self): """ pass + + + +class MetaDataNavigationEntriesFunctional: + """ Test cases for the Navigation menu + + Navigation menu is rendered as part of the API when a HTTP/OPTIONS + request has been made. Each menu entry requires that a user has View + permissions for that entry to be visible. + + **No** menu entry is to be returned for **any** user whom does not + have the corresponding view permission. + + These test cases are for any model that has a navigation menu entry. + + ## Tests + + - Ensure add user does not have menu entry + - Ensure change user does not have menu entry + - Ensure delete user does not have menu entry + - Ensure the view user has menu entry + - No menu to return without pages for add user + - No menu to return without pages for change user + - No menu to return without pages for delete user + - No menu to return without pages for view user + """ + + menu_id: str = None + """ Name of the Menu entry + + Match for .navigation[i][name] + """ + + menu_entry_id: str = None + """Name of the menu entry + + Match for .navigation[i][pages][i][name] + """ + + app_namespace:str = None + """application namespace""" + + url_name: str = None + """url name""" + + url_kwargs: dict = None + """View URL kwargs""" + + add_user = None + """ User with add permission""" + + change_user = None + """ User with change permission""" + + delete_user = None + """ User with delete permission""" + + view_user = None + """ User with view permission""" + + + + def test_navigation_entry_add_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with add permission, does not + have the menu entry within navigation + """ + + client = Client() + client.force_login(self.add_user) + + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + no_menu_entry_found: bool = True + + for nav_menu in response.data['navigation']: + + if nav_menu['name'] == self.menu_id: + + for menu_entry in nav_menu['pages']: + + if menu_entry['name'] == self.menu_entry_id: + + no_menu_entry_found = False + + assert no_menu_entry_found + + + + def test_navigation_no_empty_menu_add_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with add permission, does not + have any nave menu without pages + """ + + client = Client() + client.force_login(self.add_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + no_empty_menu_found: bool = True + + for nav_menu in response.data['navigation']: + + if len(nav_menu['pages']) == 0: + + no_empty_menu_found = False + + assert no_empty_menu_found + + + + def test_navigation_entry_change_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with change permission, does not + have the menu entry within navigation + """ + + client = Client() + client.force_login(self.change_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + no_menu_entry_found: bool = True + + for nav_menu in response.data['navigation']: + + if nav_menu['name'] == self.menu_id: + + for menu_entry in nav_menu['pages']: + + if menu_entry['name'] == self.menu_entry_id: + + no_menu_entry_found = False + + assert no_menu_entry_found + + + + def test_navigation_no_empty_menu_change_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with change permission, does not + have any nave menu without pages + """ + + client = Client() + client.force_login(self.change_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + no_empty_menu_found: bool = True + + for nav_menu in response.data['navigation']: + + if len(nav_menu['pages']) == 0: + + no_empty_menu_found = False + + assert no_empty_menu_found + + + + def test_navigation_entry_delete_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with delete permission, does not + have the menu entry within navigation + """ + + client = Client() + client.force_login(self.delete_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + no_menu_entry_found: bool = True + + for nav_menu in response.data['navigation']: + + if nav_menu['name'] == self.menu_id: + + for menu_entry in nav_menu['pages']: + + if menu_entry['name'] == self.menu_entry_id: + + no_menu_entry_found = False + + assert no_menu_entry_found + + + + def test_navigation_no_empty_menu_delete_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with delete permission, does not + have any nave menu without pages + """ + + client = Client() + client.force_login(self.delete_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + no_empty_menu_found: bool = True + + for nav_menu in response.data['navigation']: + + if len(nav_menu['pages']) == 0: + + no_empty_menu_found = False + + assert no_empty_menu_found + + + + def test_navigation_entry_view_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with view permission, + has the menu entry within navigation + """ + + client = Client() + client.force_login(self.view_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + menu_entry_found: bool = False + + for nav_menu in response.data['navigation']: + + if nav_menu['name'] == self.menu_id: + + for menu_entry in nav_menu['pages']: + + if menu_entry['name'] == self.menu_entry_id: + + menu_entry_found = True + + assert menu_entry_found + + + + def test_navigation_no_empty_menu_view_user(self): + """Test HTTP/Options Method Navigation Entry + + Ensure that a user with view permission, does not + have any nave menu without pages + """ + + client = Client() + client.force_login(self.view_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-list') + + response = client.options( + url, + content_type='application/json' + ) + + no_empty_menu_found: bool = True + + for nav_menu in response.data['navigation']: + + if len(nav_menu['pages']) == 0: + + no_empty_menu_found = False + + assert no_empty_menu_found diff --git a/docs/projects/centurion_erp/development/views.md b/docs/projects/centurion_erp/development/views.md index ef2fe5a96..2fafcc764 100644 --- a/docs/projects/centurion_erp/development/views.md +++ b/docs/projects/centurion_erp/development/views.md @@ -40,6 +40,10 @@ Views are used with Centurion ERP to Fetch the data for rendering. - _Functional test cases_ `from api.tests.abstract.api_permissions_viewset import APIPermission` + - _Functional test cases_ (Only required if model has an API endpoint)_`from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional` + + - _Functional test cases_ _(Only required if model has nav menu entry)_`from api.tests.abstract.test_metadata_functional import MetaDataNavigationEntriesFunctional` + - View Added to Navigation From 61f6996f5e718dbdcb8722428d1e79d725eae080 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 20:20:35 +0930 Subject: [PATCH 19/84] test(access): API Metadata checks for organization ref: #412 #415 --- .../organization/test_organization_viewset.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/access/tests/functional/organization/test_organization_viewset.py b/app/access/tests/functional/organization/test_organization_viewset.py index f872c71cd..a7f6dbef3 100644 --- a/app/access/tests/functional/organization/test_organization_viewset.py +++ b/app/access/tests/functional/organization/test_organization_viewset.py @@ -13,6 +13,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional @@ -278,3 +279,14 @@ class OrganizationViewSet( ): pass + +class OrganizationMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'access' + + menu_entry_id = 'organization' \ No newline at end of file From fd3bd7f04e61477879845e183a05d513b97a7c59 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:14:35 +0930 Subject: [PATCH 20/84] test(api): correct metadata testcases ref: #412 #415 --- .../abstract/test_metadata_functional.py | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/app/api/tests/abstract/test_metadata_functional.py b/app/api/tests/abstract/test_metadata_functional.py index 6389be6b5..d985e8dcf 100644 --- a/app/api/tests/abstract/test_metadata_functional.py +++ b/app/api/tests/abstract/test_metadata_functional.py @@ -18,6 +18,8 @@ class MetadataAttributesFunctional: url_name: str = None + viewset_type: str = 'list' + def test_method_options_request_list_ok(self): """Test HTTP/Options Method @@ -30,11 +32,11 @@ def test_method_options_request_list_ok(self): if getattr(self, 'url_kwargs', None): - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) else: - url = reverse(self.app_namespace + ':' + self.url_name + '-list') + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) response = client.options( url, content_type='application/json' ) @@ -52,11 +54,11 @@ def test_method_options_request_list_data_returned(self): if getattr(self, 'url_kwargs', None): - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) else: - url = reverse(self.app_namespace + ':' + self.url_name + '-list') + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) response = client.options( url, content_type='application/json' ) @@ -74,11 +76,11 @@ def test_method_options_request_list_data_type(self): if getattr(self, 'url_kwargs', None): - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) else: - url = reverse(self.app_namespace + ':' + self.url_name + '-list') + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) response = client.options( url, content_type='application/json' ) @@ -96,11 +98,11 @@ def test_method_options_request_list_data_has_key_table_fields(self): if getattr(self, 'url_kwargs', None): - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) else: - url = reverse(self.app_namespace + ':' + self.url_name + '-list') + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) response = client.options( url, content_type='application/json' ) @@ -118,11 +120,11 @@ def test_method_options_request_list_data_key_table_fields_is_list(self): if getattr(self, 'url_kwargs', None): - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) else: - url = reverse(self.app_namespace + ':' + self.url_name + '-list') + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) response = client.options( url, content_type='application/json' ) @@ -140,11 +142,11 @@ def test_method_options_request_list_data_key_table_fields_is_list_of_str(self): if getattr(self, 'url_kwargs', None): - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) else: - url = reverse(self.app_namespace + ':' + self.url_name + '-list') + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) response = client.options( url, content_type='application/json' ) @@ -171,11 +173,11 @@ def test_method_options_request_detail_ok(self): if getattr(self, 'url_kwargs', None): - url = reverse(self.app_namespace + ':' + self.url_name + '-list', kwargs = self.url_kwargs) + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) else: - url = reverse(self.app_namespace + ':' + self.url_name + '-list') + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) response = client.options( url, content_type='application/json' ) From b54d710c5091f0f30cc85918a683c32f39fc283c Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:16:07 +0930 Subject: [PATCH 21/84] test(access): API Metadata checks for kb ref: #410 #412 #415 --- .../organization/test_organization_viewset.py | 2 ++ .../test_knowledge_base_viewset.py | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/access/tests/functional/organization/test_organization_viewset.py b/app/access/tests/functional/organization/test_organization_viewset.py index a7f6dbef3..e0d433f51 100644 --- a/app/access/tests/functional/organization/test_organization_viewset.py +++ b/app/access/tests/functional/organization/test_organization_viewset.py @@ -280,6 +280,8 @@ class OrganizationViewSet( pass + + class OrganizationMetadata( ViewSetBase, MetadataAttributesFunctional, diff --git a/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py b/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py index 4f4fc976e..2c934413c 100644 --- a/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py +++ b/app/assistance/tests/functional/knowledge_base/test_knowledge_base_viewset.py @@ -12,6 +12,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from assistance.models.knowledge_base import KnowledgeBase @@ -211,4 +212,17 @@ class KnowledgeBaseViewSet( TestCase, ): - pass \ No newline at end of file + pass + + + +class KnowledgeBaseMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'assistance' + + menu_entry_id = 'knowledge_base' From 2ef5124ccccea9c26dce34d47077a5e70f45dd95 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:16:17 +0930 Subject: [PATCH 22/84] test(access): API Metadata checks for kb category ref: #410 #412 #415 --- .../test_knowledge_base_category_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py b/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py index b72093d4f..c11d5ef5b 100644 --- a/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py +++ b/app/assistance/tests/functional/knowledge_base_category/test_knowledge_base_category_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from assistance.models.knowledge_base import KnowledgeBaseCategory @@ -206,3 +207,13 @@ class KnowledgeBaseCategoryViewSet( ): pass + + + +class KnowledgeBaseCategoryMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From a32d942db6f8757173c6b523738662963d392f7b Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:16:31 +0930 Subject: [PATCH 23/84] test(access): API Metadata checks for request ticket ref: #410 #412 #415 --- .../ticket_request/test_ticket_request_viewset.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/assistance/tests/functional/ticket_request/test_ticket_request_viewset.py b/app/assistance/tests/functional/ticket_request/test_ticket_request_viewset.py index d17d9aa18..5db745430 100644 --- a/app/assistance/tests/functional/ticket_request/test_ticket_request_viewset.py +++ b/app/assistance/tests/functional/ticket_request/test_ticket_request_viewset.py @@ -1,5 +1,7 @@ from django.test import TestCase +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional + from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet @@ -29,3 +31,16 @@ class TicketRequestViewSet( ): pass + + + +class TicketRequestMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'assistance' + + menu_entry_id = 'request' From d5771401c81705361019ea6703e078e802166365 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:16:50 +0930 Subject: [PATCH 24/84] test(config_management): API Metadata checks for config groups ref: #410 #412 #415 --- .../config_groups/test_config_groups_viewset.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py b/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py index 3165e9120..62fad341c 100644 --- a/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py +++ b/app/config_management/tests/functional/config_groups/test_config_groups_viewset.py @@ -12,6 +12,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from config_management.models.groups import ConfigGroups @@ -207,3 +208,16 @@ class ConfigGroupsViewSet( ): pass + + + +class ConfigGroupsMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'config_management' + + menu_entry_id = 'group' From 089f8beef29a6217608ce076e83025082c228056 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:17:00 +0930 Subject: [PATCH 25/84] test(config_management): API Metadata checks for config group software ref: #410 #412 #415 --- .../test_config_groups_software_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py b/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py index 555ea8f4f..bcca13c09 100644 --- a/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py +++ b/app/config_management/tests/functional/config_groups_software/test_config_groups_software_viewset.py @@ -12,6 +12,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from config_management.models.groups import ConfigGroups, ConfigGroupSoftware, Software, SoftwareVersion @@ -247,3 +248,13 @@ class ConfigGroupSoftwareViewSet( ): pass + + + +class ConfigGroupSoftwareMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From cadb3bcac08afc88ac44ccbd4fd80c1ba4800c6e Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:17:19 +0930 Subject: [PATCH 26/84] test(core): API Metadata checks for manufacturers ref: #410 #412 #415 --- .../manufacturer/test_manufacturer_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py b/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py index 0fa3c4071..f42f91058 100644 --- a/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py +++ b/app/core/tests/functional/manufacturer/test_manufacturer_viewset.py @@ -12,6 +12,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from core.models.manufacturer import Manufacturer @@ -207,3 +208,13 @@ class ManufacturerViewSet( ): pass + + + +class ManufacturerMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From c0d5bfad45000d7915f4c3b345810f0e60362789 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:17:35 +0930 Subject: [PATCH 27/84] test(core): API Metadata checks for related tickets ref: #410 #412 #415 --- .../test_related_ticket_viewset.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py b/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py index 429bca494..2c8752675 100644 --- a/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py +++ b/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py @@ -15,16 +15,13 @@ APIPermissionDelete, APIPermissionView, ) +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from core.models.ticket.ticket import Ticket, RelatedTickets -class RelatedTicketsPermissionsAPI( - APIPermissionDelete, - APIPermissionView, - TestCase, -): +class ViewSetBase: model = RelatedTickets @@ -209,6 +206,13 @@ def setUpTestData(self): +class RelatedTicketsPermissionsAPI( + ViewSetBase, + APIPermissionDelete, + APIPermissionView, + TestCase, +): + def test_add_has_permission_post_not_allowed(self): """ Check correct permission for add @@ -273,3 +277,13 @@ def test_returned_results_only_user_orgs(self): """ pass + + + +class RelatedTicketsMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 2be581983967354537d95bb537fb0b04f50c314b Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:17:45 +0930 Subject: [PATCH 28/84] test(core): API Metadata checks for history ref: #410 #412 #415 --- .../test_history/test_history_viewset.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/core/tests/functional/test_history/test_history_viewset.py b/app/core/tests/functional/test_history/test_history_viewset.py index 17b9cb7ec..ba395d7fd 100644 --- a/app/core/tests/functional/test_history/test_history_viewset.py +++ b/app/core/tests/functional/test_history/test_history_viewset.py @@ -12,6 +12,7 @@ from access.models import Organization, Team, TeamUsers, Permission from api.tests.abstract.api_permissions_viewset import APIPermissionView +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from core.models.history import History @@ -19,7 +20,7 @@ -class HistoryPermissionsAPI(APIPermissionView, TestCase): +class ViewSetBase: model = History @@ -218,6 +219,15 @@ def setUpTestData(self): user = self.different_organization_user ) + + +class HistoryPermissionsAPI( + ViewSetBase, + APIPermissionView, + TestCase +): + + def test_view_list_has_permission(self): """ Check correct permission for view @@ -326,3 +336,12 @@ def test_returned_results_only_user_orgs(self): pass + + +class HistoryMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 47898d7e1d316308b9c0a9cfca7ce6186696aa5b Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:17:54 +0930 Subject: [PATCH 29/84] test(core): API Metadata checks for ticket category ref: #410 #412 #415 --- .../ticket_category/test_ticket_category_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py b/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py index 7d3b3a2dc..ceba3256b 100644 --- a/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py +++ b/app/core/tests/functional/ticket_category/test_ticket_category_viewset.py @@ -12,6 +12,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from core.models.ticket.ticket_category import TicketCategory @@ -203,3 +204,13 @@ class TicketCategoryViewSet( ): pass + + + +class TicketCategoryMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From f8338ff6f0641cae6e45adf40a8050de90d3b029 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:18:04 +0930 Subject: [PATCH 30/84] test(core): API Metadata checks for ticket comment ref: #410 #412 #415 --- .../test_ticket_comment_viewset.py | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py b/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py index ee4fa13e4..a535788d8 100644 --- a/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py +++ b/app/core/tests/functional/ticket_comment/test_ticket_comment_viewset.py @@ -6,6 +6,7 @@ from access.models import Organization, Team, TeamUsers, Permission from api.tests.abstract.api_permissions_viewset import APIPermissions +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from core.models.ticket.ticket_comment import Ticket, TicketComment @@ -13,10 +14,7 @@ -class TicketCommentPermissionsAPI( - APIPermissions, - TestCase -): +class ViewSetBase: """ Test Cases common to ALL ticket types """ model = TicketComment @@ -230,6 +228,11 @@ def setUpTestData(self): ) +class TicketCommentPermissionsAPI( + ViewSetBase, + APIPermissions, + TestCase +): def test_returned_results_only_user_orgs(self): """Test not required @@ -238,3 +241,13 @@ def test_returned_results_only_user_orgs(self): """ pass + + + +class TicketCommentMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From cb636fff011fedd1e8a7cd01a46d15352b00dac1 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:18:14 +0930 Subject: [PATCH 31/84] test(core): API Metadata checks for ticket comment category ref: #410 #412 #415 --- .../test_ticket_comment_category_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py b/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py index 9b92985d9..492331a5b 100644 --- a/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py +++ b/app/core/tests/functional/ticket_comment_category/test_ticket_comment_category_viewset.py @@ -12,6 +12,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from core.models.ticket.ticket_comment_category import TicketCommentCategory @@ -203,3 +204,13 @@ class TicketCommentCategoryViewSet( ): pass + + + +class TicketCommentCategoryMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From eb4a132f394eb7988b8047ce564c43fbf48d3d56 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:18:28 +0930 Subject: [PATCH 32/84] test(itam): API Metadata checks for device ref: #410 #412 #415 --- .../functional/device/test_device_viewset.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/itam/tests/functional/device/test_device_viewset.py b/app/itam/tests/functional/device/test_device_viewset.py index e5ca698ec..d859e506c 100644 --- a/app/itam/tests/functional/device/test_device_viewset.py +++ b/app/itam/tests/functional/device/test_device_viewset.py @@ -6,9 +6,10 @@ from django.test import Client, TestCase from access.models import Organization, Team, TeamUsers, Permission -from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.api_serializer_viewset import SerializersTestCases from api.tests.abstract.api_permissions_viewset import APIPermissions +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from itam.models.device import Device @@ -198,4 +199,17 @@ class DeviceViewSet( TestCase ): - pass \ No newline at end of file + pass + + + +class DeviceMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itam' + + menu_entry_id = 'device' From c9caa96f98a2a969ba4bc635e3834d9be96d00e3 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:18:36 +0930 Subject: [PATCH 33/84] test(itam): API Metadata checks for device model ref: #410 #412 #415 --- .../device_model/test_device_model_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/itam/tests/functional/device_model/test_device_model_viewset.py b/app/itam/tests/functional/device_model/test_device_model_viewset.py index 34f8fc8ac..8a621d1d3 100644 --- a/app/itam/tests/functional/device_model/test_device_model_viewset.py +++ b/app/itam/tests/functional/device_model/test_device_model_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.models.device import DeviceModel @@ -199,3 +200,13 @@ class DeviceModelViewSet( ): pass + + + +class DeviceModelMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 702644adc51031db783d2f1c463c50240a3fa7cd Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:18:45 +0930 Subject: [PATCH 34/84] test(itam): API Metadata checks for device OS ref: #410 #412 #415 --- .../test_device_operating_system_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py b/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py index 4e8e45f6e..9ed62fd38 100644 --- a/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py +++ b/app/itam/tests/functional/device_operating_system/test_device_operating_system_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.serializers.device_operating_system import Device, DeviceOperatingSystem, DeviceOperatingSystemModelSerializer from itam.models.operating_system import OperatingSystem, OperatingSystemVersion @@ -257,3 +258,13 @@ class DeviceOperatingViewSet( ): pass + + + +class DeviceOperatingMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From c4459bd21f932e957fa18d05800541b203132952 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:18:54 +0930 Subject: [PATCH 35/84] test(itam): API Metadata checks for device type ref: #410 #412 #415 --- .../device_type/test_device_type_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/itam/tests/functional/device_type/test_device_type_viewset.py b/app/itam/tests/functional/device_type/test_device_type_viewset.py index ffe68d3e4..cf2c1a8a2 100644 --- a/app/itam/tests/functional/device_type/test_device_type_viewset.py +++ b/app/itam/tests/functional/device_type/test_device_type_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.models.device import DeviceType @@ -198,4 +199,14 @@ class DeviceTypeViewSet( TestCase, ): - pass \ No newline at end of file + pass + + + +class DeviceTypeMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From df5234e8d3593be0e3da4ba76d9d80778c455a4c Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:19:11 +0930 Subject: [PATCH 36/84] test(itam): API Metadata checks for operating system ref: #410 #412 #415 --- .../test_operating_system_installs_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py b/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py index ce444f598..eafc9aead 100644 --- a/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py +++ b/app/itam/tests/functional/endpoint_operating_system_installs/test_operating_system_installs_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissionView from api.tests.abstract.api_serializer_viewset import SerializerView +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.serializers.device_operating_system import Device, DeviceOperatingSystem, DeviceOperatingSystemModelSerializer from itam.models.operating_system import OperatingSystem, OperatingSystemVersion @@ -180,3 +181,13 @@ class OperatingSystemInstallsViewSet( ): pass + + + +class OperatingSystemInstallsMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 250ba96c842a1c93e16e21882ff9ab02f95b9f2c Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:19:19 +0930 Subject: [PATCH 37/84] test(itam): API Metadata checks for software ref: #410 #412 #415 --- .../test_software_installs_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py b/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py index 055afa608..91c290d75 100644 --- a/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py +++ b/app/itam/tests/functional/endpoint_software_installs/test_software_installs_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.models.device import Device, DeviceSoftware from itam.models.software import Software @@ -228,4 +229,14 @@ class SoftwareInstallsViewSet( TestCase ): - pass \ No newline at end of file + pass + + + +class SoftwareInstallsMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From f3ef3be8839e78351d653e7e6ab0a65195def5bc Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:19:33 +0930 Subject: [PATCH 38/84] test(itam): API Metadata checks for operating system ref: #410 #412 #415 --- .../test_operating_system_viewset.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/itam/tests/functional/operating_system/test_operating_system_viewset.py b/app/itam/tests/functional/operating_system/test_operating_system_viewset.py index d5cb00f1c..ee7ba5a8d 100644 --- a/app/itam/tests/functional/operating_system/test_operating_system_viewset.py +++ b/app/itam/tests/functional/operating_system/test_operating_system_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from itam.models.operating_system import OperatingSystem @@ -198,4 +199,17 @@ class OperatingSystemViewSet( TestCase, ): - pass \ No newline at end of file + pass + + + +class OperatingSystemMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itam' + + menu_entry_id = 'operating_system' From b80d8a5c64f6b1bf856f27448decd41818840fbc Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:19:42 +0930 Subject: [PATCH 39/84] test(itam): API Metadata checks for operating system version ref: #410 #412 #415 --- .../test_operating_system_version_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py b/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py index aaea04e2e..747c66909 100644 --- a/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py +++ b/app/itam/tests/functional/operating_system_version/test_operating_system_version_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.models.operating_system import OperatingSystem, OperatingSystemVersion @@ -205,3 +206,13 @@ class OperatingSystemVersionPermissionsAPI(ViewSetBase, APIPermissions, TestCase class OperatingSystemVersionViewSetBase(ViewSetBase, SerializersTestCases, TestCase): pass + + + +class OperatingSystemVersionMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 4df7e57ca7b5be1f27e058ee7e7d19c2abf615c7 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:19:52 +0930 Subject: [PATCH 40/84] test(itam): API Metadata checks for software ref: #410 #412 #415 --- .../functional/software/test_software_viewset.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/itam/tests/functional/software/test_software_viewset.py b/app/itam/tests/functional/software/test_software_viewset.py index b2cd17ead..7b05b28a3 100644 --- a/app/itam/tests/functional/software/test_software_viewset.py +++ b/app/itam/tests/functional/software/test_software_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from itam.models.software import Software @@ -191,3 +192,16 @@ class SoftwarePermissionsAPI(ViewSetBase, APIPermissions, TestCase): class SoftwareViewSet(ViewSetBase, SerializersTestCases, TestCase): pass + + + +class SoftwareMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itam' + + menu_entry_id = 'software' From 9e78aa5940329ed3e539bb878bd21d1318c4f901 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:19:59 +0930 Subject: [PATCH 41/84] test(itam): API Metadata checks for software category ref: #410 #412 #415 --- .../test_software_category_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/itam/tests/functional/software_category/test_software_category_viewset.py b/app/itam/tests/functional/software_category/test_software_category_viewset.py index 90f5e4b57..499505a9d 100644 --- a/app/itam/tests/functional/software_category/test_software_category_viewset.py +++ b/app/itam/tests/functional/software_category/test_software_category_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.models.software import SoftwareCategory @@ -190,4 +191,14 @@ class SoftwareCategoryPermissionsAPI(ViewSetBase, APIPermissions, TestCase): class SoftwareCategoryViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class SoftwareCategoryMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 871cdc6b5d7f6ebbb120a0c408a13d01df636efd Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:20:10 +0930 Subject: [PATCH 42/84] test(itam): API Metadata checks for software version ref: #410 #412 #415 --- .../test_software_version_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/itam/tests/functional/software_version/test_software_version_viewset.py b/app/itam/tests/functional/software_version/test_software_version_viewset.py index c2df23152..e8b32cd9f 100644 --- a/app/itam/tests/functional/software_version/test_software_version_viewset.py +++ b/app/itam/tests/functional/software_version/test_software_version_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itam.models.software import Software, SoftwareVersion @@ -203,4 +204,14 @@ class SoftwareVersionPermissionsAPI(ViewSetBase, APIPermissions, TestCase): class SoftwareVersionViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class SoftwareVersionMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 6291510ba41273efdfcdf2ace99fd76ec5317722 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:20:20 +0930 Subject: [PATCH 43/84] test(itim): API Metadata checks for cluster ref: #410 #412 #415 --- .../functional/cluster/test_cluster_viewset.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/itim/tests/functional/cluster/test_cluster_viewset.py b/app/itim/tests/functional/cluster/test_cluster_viewset.py index 82ae657c4..f200a0ca9 100644 --- a/app/itim/tests/functional/cluster/test_cluster_viewset.py +++ b/app/itim/tests/functional/cluster/test_cluster_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from itim.models.clusters import Cluster @@ -190,4 +191,17 @@ class ClusterPermissionsAPI(ViewSetBase, APIPermissions, TestCase): class ClusterViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class ClusterMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itim' + + menu_entry_id = 'cluster' From 7f46daeb54de76789f91739e0a472d4b92c47ffa Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:20:26 +0930 Subject: [PATCH 44/84] test(itim): API Metadata checks for cluster type ref: #410 #412 #415 --- .../cluster_types/test_cluster_type_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py b/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py index 6ebb7bd39..b130f96e5 100644 --- a/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py +++ b/app/itim/tests/functional/cluster_types/test_cluster_type_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itim.models.clusters import ClusterType @@ -191,4 +192,14 @@ class ClusterTypePermissionsAPI(ViewSetBase, APIPermissions, TestCase): class ClusterTypeViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class ClusterTypeMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 8f1e61a7a6322230df393a48fde4fa9776f5c547 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:20:34 +0930 Subject: [PATCH 45/84] test(itim): API Metadata checks for port ref: #410 #412 #415 --- app/itim/tests/functional/port/test_port_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/itim/tests/functional/port/test_port_viewset.py b/app/itim/tests/functional/port/test_port_viewset.py index 0af6215ca..d373d4924 100644 --- a/app/itim/tests/functional/port/test_port_viewset.py +++ b/app/itim/tests/functional/port/test_port_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from itim.models.services import Port @@ -193,4 +194,14 @@ class PortPermissionsAPI(ViewSetBase, APIPermissions, TestCase): class PortViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class PortMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From da5c4d76e9bf5322141fd43bc1a6a4f5f9c880f6 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:20:43 +0930 Subject: [PATCH 46/84] test(itim): API Metadata checks for service ref: #410 #412 #415 --- .../functional/service/test_service_viewset.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/itim/tests/functional/service/test_service_viewset.py b/app/itim/tests/functional/service/test_service_viewset.py index 512d7b89e..4840817af 100644 --- a/app/itim/tests/functional/service/test_service_viewset.py +++ b/app/itim/tests/functional/service/test_service_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from itam.models.device import Device @@ -212,4 +213,17 @@ class ServicePermissionsAPI(ViewSetBase, APIPermissions, TestCase): class ServiceViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class ServiceMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itim' + + menu_entry_id = 'service' From 873f241d146144e13be35b9a5a9e418c1addead1 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:20:56 +0930 Subject: [PATCH 47/84] test(itim): API Metadata checks for change ticket ref: #410 #412 #415 --- .../ticket_change/test_ticket_change_viewset.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/itim/tests/functional/ticket_change/test_ticket_change_viewset.py b/app/itim/tests/functional/ticket_change/test_ticket_change_viewset.py index dc441c841..22b9bbf4c 100644 --- a/app/itim/tests/functional/ticket_change/test_ticket_change_viewset.py +++ b/app/itim/tests/functional/ticket_change/test_ticket_change_viewset.py @@ -1,6 +1,7 @@ from django.test import TestCase from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional class ViewSetBase( TicketViewSetBase ): @@ -28,3 +29,16 @@ class TicketChangeViewSet( ): pass + + + +class TicketChangeMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itim' + + menu_entry_id = 'ticket_change' From eb3071c93d7adf82cb545cc99a6f5f5bac1dc03a Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:21:04 +0930 Subject: [PATCH 48/84] test(itim): API Metadata checks for incident ticket ref: #410 #412 #415 --- .../test_ticket_incident_viewset.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/itim/tests/functional/ticket_incident/test_ticket_incident_viewset.py b/app/itim/tests/functional/ticket_incident/test_ticket_incident_viewset.py index 237ff2ea1..24b9d975b 100644 --- a/app/itim/tests/functional/ticket_incident/test_ticket_incident_viewset.py +++ b/app/itim/tests/functional/ticket_incident/test_ticket_incident_viewset.py @@ -1,6 +1,7 @@ from django.test import TestCase from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional class ViewSetBase( TicketViewSetBase ): @@ -28,3 +29,16 @@ class TicketIncidentViewSet( ): pass + + + +class TicketIncidentMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itim' + + menu_entry_id = 'ticket_incident' From fcf3d568e4c7667f566c4a3ea57c786b0156ded8 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:21:13 +0930 Subject: [PATCH 49/84] test(itim): API Metadata checks for problem ticket ref: #410 #412 #415 --- .../ticket_problem/test_ticket_problem_viewset.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/itim/tests/functional/ticket_problem/test_ticket_problem_viewset.py b/app/itim/tests/functional/ticket_problem/test_ticket_problem_viewset.py index 851c1b444..488b20d98 100644 --- a/app/itim/tests/functional/ticket_problem/test_ticket_problem_viewset.py +++ b/app/itim/tests/functional/ticket_problem/test_ticket_problem_viewset.py @@ -1,6 +1,7 @@ from django.test import TestCase from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional class ViewSetBase( TicketViewSetBase ): @@ -28,3 +29,16 @@ class TicketProblemViewSet( ): pass + + + +class TicketProblemMetadata( + ViewSetBase, + MetadataAttributesFunctional, + MetaDataNavigationEntriesFunctional, + TestCase +): + + menu_id = 'itim' + + menu_entry_id = 'ticket_problem' From d9b5ec7d414ff0a5bef97673cec9b72c98b13c92 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:21:31 +0930 Subject: [PATCH 50/84] test(project_management): API Metadata checks for project ref: #410 #412 #415 --- .../functional/project/test_project_viewset.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/project_management/tests/functional/project/test_project_viewset.py b/app/project_management/tests/functional/project/test_project_viewset.py index e63a0079e..5bf6cdffa 100644 --- a/app/project_management/tests/functional/project/test_project_viewset.py +++ b/app/project_management/tests/functional/project/test_project_viewset.py @@ -9,6 +9,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional, MetaDataNavigationEntriesFunctional from project_management.models.projects import Project @@ -274,3 +275,16 @@ def test_add_has_permission_import_fields(self): class ProjectViewSet(ViewSetBase, SerializersTestCases, TestCase): pass + + + +class ProjectMetadata( + ViewSetBase, + MetaDataNavigationEntriesFunctional, + MetadataAttributesFunctional, + TestCase +): + + menu_id = 'project_management' + + menu_entry_id = 'project' From 80a82605aad586d571ce4682f920c1631c3dd7b4 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:21:40 +0930 Subject: [PATCH 51/84] test(project_management): API Metadata checks for project milestone ref: #410 #412 #415 --- .../test_project_milestone_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py b/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py index 8a0c8f75b..f84b95dee 100644 --- a/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py +++ b/app/project_management/tests/functional/project_milestone/test_project_milestone_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from project_management.models.project_milestone import Project, ProjectMilestone @@ -205,3 +206,13 @@ class ProjectMilestonePermissionsAPI(ViewSetBase, APIPermissions, TestCase): class ProjectMilestoneViewSet(ViewSetBase, SerializersTestCases, TestCase): pass + + + +class ProjectMilestoneMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From f85cc0fd3df75099e2940d465133bd6d5a8dd327 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:21:49 +0930 Subject: [PATCH 52/84] test(project_management): API Metadata checks for project state ref: #410 #412 #415 --- .../project_state/test_project_state_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/project_management/tests/functional/project_state/test_project_state_viewset.py b/app/project_management/tests/functional/project_state/test_project_state_viewset.py index 85d67748d..660848a82 100644 --- a/app/project_management/tests/functional/project_state/test_project_state_viewset.py +++ b/app/project_management/tests/functional/project_state/test_project_state_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from project_management.models.project_states import ProjectState @@ -190,4 +191,14 @@ class ProjectStatePermissionsAPI(ViewSetBase, APIPermissions, TestCase): class ProjectStateViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class ProjectStateMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From 741a149f33385bdb2342445bd24ea3772c6545a1 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:21:57 +0930 Subject: [PATCH 53/84] test(project_management): API Metadata checks for project task ref: #410 #412 #415 --- .../project_task/test_project_task_viewset.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/project_management/tests/functional/project_task/test_project_task_viewset.py b/app/project_management/tests/functional/project_task/test_project_task_viewset.py index 2ce84db89..11b36d28e 100644 --- a/app/project_management/tests/functional/project_task/test_project_task_viewset.py +++ b/app/project_management/tests/functional/project_task/test_project_task_viewset.py @@ -1,7 +1,7 @@ from django.test import TestCase from core.tests.abstract.test_ticket_viewset import Ticket, TicketViewSetBase, TicketViewSetPermissionsAPI, TicketViewSet - +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional class ViewSetBase( TicketViewSetBase ): @@ -29,3 +29,13 @@ class TicketProjectTaskViewSet( ): pass + + + +class TicketProjectTaskMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From c57d465c5bacb283513462d5f56b76951a171db9 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:22:05 +0930 Subject: [PATCH 54/84] test(project_management): API Metadata checks for project type ref: #410 #412 #415 --- .../project_type/test_project_type_viewset.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/project_management/tests/functional/project_type/test_project_type_viewset.py b/app/project_management/tests/functional/project_type/test_project_type_viewset.py index 189c74811..24fe61d64 100644 --- a/app/project_management/tests/functional/project_type/test_project_type_viewset.py +++ b/app/project_management/tests/functional/project_type/test_project_type_viewset.py @@ -8,6 +8,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from project_management.models.project_types import ProjectType @@ -191,3 +192,13 @@ class ProjectTypePermissionsAPI(ViewSetBase, APIPermissions, TestCase): class ProjectTypeViewSet(ViewSetBase, SerializersTestCases, TestCase): pass + + + +class ProjectTypeMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From becf55e22ffc9fbf8101b7bcacabb87c94d06a96 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:22:22 +0930 Subject: [PATCH 55/84] test(settings): API Metadata checks for app settings ref: #410 #412 #415 --- .../app_settings/test_app_settings_viewset.py | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/settings/tests/functional/app_settings/test_app_settings_viewset.py b/app/settings/tests/functional/app_settings/test_app_settings_viewset.py index 58c8d2075..3776730ae 100644 --- a/app/settings/tests/functional/app_settings/test_app_settings_viewset.py +++ b/app/settings/tests/functional/app_settings/test_app_settings_viewset.py @@ -16,6 +16,7 @@ SerializerChange, SerializerView, ) +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from settings.models.app_settings import AppSettings @@ -240,4 +241,22 @@ class AppSettingsViewSet( TestCase, ): - pass \ No newline at end of file + pass + + + +class AppSettingsMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + viewset_type = 'detail' + + @classmethod + def setUpTestData(self): + + super().setUpTestData() + + self.url_kwargs = self.url_view_kwargs + From 11abf177abef84b14778170f886f48ef483472a9 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:22:34 +0930 Subject: [PATCH 56/84] test(settings): API Metadata checks for external links ref: #410 #412 #415 --- .../external_links/test_external_link_viewset.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/settings/tests/functional/external_links/test_external_link_viewset.py b/app/settings/tests/functional/external_links/test_external_link_viewset.py index 77aa08961..2374b2ed4 100644 --- a/app/settings/tests/functional/external_links/test_external_link_viewset.py +++ b/app/settings/tests/functional/external_links/test_external_link_viewset.py @@ -12,6 +12,7 @@ from api.tests.abstract.api_permissions_viewset import APIPermissions from api.tests.abstract.api_serializer_viewset import SerializersTestCases +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from settings.models.external_link import ExternalLink @@ -199,4 +200,14 @@ class ExternalLinkPermissionsAPI(ViewSetBase, APIPermissions, TestCase): class ExternalLinkViewSet(ViewSetBase, SerializersTestCases, TestCase): - pass \ No newline at end of file + pass + + + +class ExternalLinkMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + pass From a10f1b3694516ba5ecf3f01592e71d00ed3410a4 Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:22:52 +0930 Subject: [PATCH 57/84] test(settings): API Metadata checks for user settings ref: #410 #412 #415 --- .../test_user_settings_viewset.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/settings/tests/functional/user_settings/test_user_settings_viewset.py b/app/settings/tests/functional/user_settings/test_user_settings_viewset.py index 3588b8dc2..6c99f997c 100644 --- a/app/settings/tests/functional/user_settings/test_user_settings_viewset.py +++ b/app/settings/tests/functional/user_settings/test_user_settings_viewset.py @@ -16,6 +16,7 @@ SerializerChange, SerializerView, ) +from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional from settings.models.user_settings import UserSettings @@ -267,3 +268,20 @@ class UserSettingsViewSet( ): pass + + + +class UserSettingsMetadata( + ViewSetBase, + MetadataAttributesFunctional, + TestCase +): + + viewset_type = 'detail' + + @classmethod + def setUpTestData(self): + + super().setUpTestData() + + self.url_kwargs = self.url_view_kwargs From 497adc68f4daf5d8a728c2ed2db455cb0623f49a Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 4 Dec 2024 23:37:09 +0930 Subject: [PATCH 58/84] fix(api): during metadata navigation permission checks, cater for non-existant keys ref: #410 #412 #415 --- app/api/react_ui_metadata.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py index 98c12b11f..77b0f4b9a 100644 --- a/app/api/react_ui_metadata.py +++ b/app/api/react_ui_metadata.py @@ -347,7 +347,7 @@ def get_field_info(self, field): "name": "ticket_problem", "link": "/itim/ticket/problem" }, - 'core.view_service': { + 'view_service': { "display_name": "Services", "name": "service", "link": "/itim/service" @@ -443,31 +443,35 @@ def get_navigation(self, user) -> list(dict()): new_pages: list = [] - if processed_permissions.get(app, None): + # if processed_permissions.get(app, None): # doesn't cater for `.` in perm - for permission, page in entry['pages'].items(): + for permission, page in entry['pages'].items(): - if '.' in permission: + if '.' in permission: - app_permission = str(permission).split('.') + app_permission = str(permission).split('.') + + if processed_permissions.get(app_permission[0], None): if processed_permissions[app_permission[0]].get(app_permission[1], None): new_pages += [ page ] - else: + else: + + if processed_permissions.get(app, None): if processed_permissions[app].get(permission, None): new_pages += [ page ] - if len(new_pages) > 0: + if len(new_pages) > 0: - new_menu_entry = entry.copy() + new_menu_entry = entry.copy() - new_menu_entry.update({ 'pages': new_pages }) + new_menu_entry.update({ 'pages': new_pages }) - nav += [ new_menu_entry ] + nav += [ new_menu_entry ] return nav From c91bc6ee104963edeeea85fee53a8e6b0842543a Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 5 Dec 2024 07:23:42 +0930 Subject: [PATCH 59/84] fix(access): Add missing `table_fields` attribute to team users model ref: nofusscomputing/centurion_erp_ui#29 --- app/access/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/access/models.py b/app/access/models.py index 5f850ff7d..b1d31e4bd 100644 --- a/app/access/models.py +++ b/app/access/models.py @@ -489,7 +489,10 @@ class Meta: page_layout: list = [] - table_fields: list = [] + table_fields: list = [ + 'user', + 'manager' + ] def delete(self, using=None, keep_parents=False): From ebeea4f5268132118d0d17270ae143c2cc16055b Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 5 Dec 2024 14:30:59 +0930 Subject: [PATCH 60/84] feat(access): Modify Admin User panel by removing perms and adding teams ref: #408 --- app/access/admin.py | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/app/access/admin.py b/app/access/admin.py index 27814c80b..303b92708 100644 --- a/app/access/admin.py +++ b/app/access/admin.py @@ -1,5 +1,6 @@ from django.contrib import admin -from django.contrib.auth.models import Group +from django.contrib.auth.models import Group, User +from django.contrib.auth.admin import UserAdmin from .models import * @@ -25,6 +26,40 @@ class OrganizationAdmin(admin.ModelAdmin): list_filter = ["created"] search_fields = ["team_name"] - admin.site.register(Organization,OrganizationAdmin) + +class TeamUserInline(admin.TabularInline): + model = TeamUsers + extra = 0 + + readonly_fields = ['created', 'modified'] + fields = ['team'] + + fk_name = 'user' + + +admin.site.unregister(User) +class UsrAdmin(UserAdmin): + + fieldsets = ( + (None, {"fields": ("username", "password")}), + ("Personal info", {"fields": ("first_name", "last_name", "email")}), + ( + "Permissions", + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + ), + + }, + ), + ("Important dates", {"fields": ("last_login", "date_joined")}), + ) + + inlines = [TeamUserInline] + +admin.site.register(User,UsrAdmin) + From cebb02de990cf85e8f12bf2a3a306ada07e71802 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 14:15:47 +0930 Subject: [PATCH 61/84] feat(api): When fething an items url dueing metadata creation, used named parameters ref: #412 --- app/api/react_ui_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/react_ui_metadata.py b/app/api/react_ui_metadata.py index 77b0f4b9a..cc5c06077 100644 --- a/app/api/react_ui_metadata.py +++ b/app/api/react_ui_metadata.py @@ -78,7 +78,7 @@ def determine_metadata(self, request, view): if hasattr(qs, 'get_url'): - url_self = qs.get_url( request ) + url_self = qs.get_url( request=request ) elif view.kwargs: From 00bebbd8f4e0ed76ed3da61dc26468cc239c06cd Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 14:19:04 +0930 Subject: [PATCH 62/84] fix(core): Remove requirement that ticket be specified for related tickets `get_url` ref: #412 #417 --- app/core/models/ticket/ticket.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/core/models/ticket/ticket.py b/app/core/models/ticket/ticket.py index 3bc153cde..c058802be 100644 --- a/app/core/models/ticket/ticket.py +++ b/app/core/models/ticket/ticket.py @@ -1186,11 +1186,7 @@ class Related(models.IntegerChoices): ] - def get_url( self, ticket_id, request = None ) -> str: - - if not ticket_id: - - return '' + def get_url( self, request = None ) -> str: if request: @@ -1198,7 +1194,7 @@ def get_url( self, ticket_id, request = None ) -> str: "v2:_api_v2_ticket_related-detail", request = request, kwargs={ - 'ticket_id': ticket_id, + 'ticket_id': self.from_ticket_id.id, 'pk': self.id } ) @@ -1206,7 +1202,7 @@ def get_url( self, ticket_id, request = None ) -> str: return reverse( "v2:_api_v2_ticket_related-detail", kwargs={ - 'ticket_id': ticket_id, + 'ticket_id': self.from_ticket_id.id, 'pk': self.id } ) From 8b790b451ede72a55dda5c076c472ad386d62c8b Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 14:24:54 +0930 Subject: [PATCH 63/84] test(api): Refactor test so that endpoints not expected to have an endpoint or be rendered in a table wont be tested for it. ref: #410 #412 #417 --- .../abstract/test_metadata_functional.py | 304 ++++++++++-------- 1 file changed, 167 insertions(+), 137 deletions(-) diff --git a/app/api/tests/abstract/test_metadata_functional.py b/app/api/tests/abstract/test_metadata_functional.py index d985e8dcf..fbc360985 100644 --- a/app/api/tests/abstract/test_metadata_functional.py +++ b/app/api/tests/abstract/test_metadata_functional.py @@ -5,7 +5,7 @@ -class MetadataAttributesFunctional: +class MetadataAttributesFunctionalBase: """ Functional Tests for API, HTTP/Options Method These tests ensure that **ALL** serializers include the metaclass that adds the required @@ -87,10 +87,10 @@ def test_method_options_request_list_data_type(self): assert type(response.data) is dict - def test_method_options_request_list_data_has_key_table_fields(self): + def test_method_options_request_detail_ok(self): """Test HTTP/Options Method - Ensure the request data returned has key `table_fields` + Ensure the request returns `OK`. """ client = Client() @@ -106,88 +106,95 @@ def test_method_options_request_list_data_has_key_table_fields(self): response = client.options( url, content_type='application/json' ) - assert 'table_fields' in response.data + assert response.status_code == 200 - def test_method_options_request_list_data_key_table_fields_is_list(self): + def test_method_options_request_detail_data_returned(self): """Test HTTP/Options Method - Ensure the request data['table_fields'] is of type `list` + Ensure the request returns data. """ client = Client() client.force_login(self.view_user) - if getattr(self, 'url_kwargs', None): - - url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) - - else: - - url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) - - response = client.options( url, content_type='application/json' ) + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) - assert type(response.data['table_fields']) is list + assert response.data is not None - def test_method_options_request_list_data_key_table_fields_is_list_of_str(self): + def test_method_options_request_detail_data_type(self): """Test HTTP/Options Method - Ensure the request data['table_fields'] list is of `str` + Ensure the request data returned is of type `dict` """ client = Client() client.force_login(self.view_user) - if getattr(self, 'url_kwargs', None): - - url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) - - else: + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) - url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) + assert type(response.data) is dict - response = client.options( url, content_type='application/json' ) - all_string = True - for item in response.data['table_fields']: + def test_method_options_request_detail_data_has_key_urls(self): + """Test HTTP/Options Method - if type(item) is not str: + Ensure the request data returned has key `urls` + """ - all_string = False + client = Client() + client.force_login(self.view_user) + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) - assert all_string + assert 'urls' in response.data - def test_method_options_request_detail_ok(self): + def test_method_options_request_detail_data_key_urls_is_dict(self): """Test HTTP/Options Method - Ensure the request returns `OK`. + Ensure the request data key `urls` is dict """ client = Client() client.force_login(self.view_user) - if getattr(self, 'url_kwargs', None): - - url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) - - else: - - url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-detail', + kwargs=self.url_view_kwargs + ), + content_type='application/json' + ) - response = client.options( url, content_type='application/json' ) + assert type(response.data['urls']) is dict - assert response.status_code == 200 - def test_method_options_request_detail_data_returned(self): + def test_method_options_request_detail_data_has_key_urls_self(self): """Test HTTP/Options Method - Ensure the request returns data. + Ensure the request data returned has key `urls.self` """ client = Client() @@ -201,13 +208,13 @@ def test_method_options_request_detail_data_returned(self): content_type='application/json' ) - assert response.data is not None + assert 'urls' in response.data - def test_method_options_request_detail_data_type(self): + def test_method_options_request_detail_data_key_urls_self_is_str(self): """Test HTTP/Options Method - Ensure the request data returned is of type `dict` + Ensure the request data key `urls.self` is a string """ client = Client() @@ -221,7 +228,112 @@ def test_method_options_request_detail_data_type(self): content_type='application/json' ) - assert type(response.data) is dict + assert type(response.data['urls']['self']) is str + + + @pytest.mark.skip(reason='to be written') + def test_method_options_no_field_is_generic(self): + """Test HTTP/Options Method + + Fields are used for the UI to setup inputs correctly. + + Ensure all fields at path `.actions...type` do not have `GenericField` as the value. + """ + + pass + + + +class MetadataAttributesFunctionalTable: + """Test cases for Metadata + + These test cases are for models that are expected to + be rendered in a table. + """ + + + def test_method_options_request_list_data_has_key_table_fields(self): + """Test HTTP/Options Method + + Ensure the request data returned has key `table_fields` + """ + + client = Client() + client.force_login(self.view_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) + + response = client.options( url, content_type='application/json' ) + + assert 'table_fields' in response.data + + + def test_method_options_request_list_data_key_table_fields_is_list(self): + """Test HTTP/Options Method + + Ensure the request data['table_fields'] is of type `list` + """ + + client = Client() + client.force_login(self.view_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) + + response = client.options( url, content_type='application/json' ) + + assert type(response.data['table_fields']) is list + + + def test_method_options_request_list_data_key_table_fields_is_list_of_str(self): + """Test HTTP/Options Method + + Ensure the request data['table_fields'] list is of `str` + """ + + client = Client() + client.force_login(self.view_user) + + if getattr(self, 'url_kwargs', None): + + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type, kwargs = self.url_kwargs) + + else: + + url = reverse(self.app_namespace + ':' + self.url_name + '-' + self.viewset_type) + + response = client.options( url, content_type='application/json' ) + + all_string = True + + for item in response.data['table_fields']: + + if type(item) is not str: + + all_string = False + + + assert all_string + + + +class MetadataAttributesFunctionalEndpoint: + """Test cases for Metadata + + These test cases are for models that will have an + endpoint. i.e. A Detail view + """ def test_method_options_request_detail_data_has_key_page_layout(self): @@ -410,97 +522,13 @@ def test_method_options_request_detail_data_key_page_layout_dicts_key_type_secti - def test_method_options_request_detail_data_has_key_urls(self): - """Test HTTP/Options Method - - Ensure the request data returned has key `urls` - """ - - client = Client() - client.force_login(self.view_user) - - response = client.options( - reverse( - self.app_namespace + ':' + self.url_name + '-detail', - kwargs=self.url_view_kwargs - ), - content_type='application/json' - ) - - assert 'urls' in response.data - - - def test_method_options_request_detail_data_key_urls_is_dict(self): - """Test HTTP/Options Method - - Ensure the request data key `urls` is dict - """ - - client = Client() - client.force_login(self.view_user) - - response = client.options( - reverse( - self.app_namespace + ':' + self.url_name + '-detail', - kwargs=self.url_view_kwargs - ), - content_type='application/json' - ) - - assert type(response.data['urls']) is dict - - - - def test_method_options_request_detail_data_has_key_urls_self(self): - """Test HTTP/Options Method - - Ensure the request data returned has key `urls.self` - """ - - client = Client() - client.force_login(self.view_user) - - response = client.options( - reverse( - self.app_namespace + ':' + self.url_name + '-detail', - kwargs=self.url_view_kwargs - ), - content_type='application/json' - ) - - assert 'urls' in response.data - - - def test_method_options_request_detail_data_key_urls_self_is_str(self): - """Test HTTP/Options Method - - Ensure the request data key `urls.self` is a string - """ - - client = Client() - client.force_login(self.view_user) - - response = client.options( - reverse( - self.app_namespace + ':' + self.url_name + '-detail', - kwargs=self.url_view_kwargs - ), - content_type='application/json' - ) - - assert type(response.data['urls']['self']) is str - - - @pytest.mark.skip(reason='to be written') - def test_method_options_no_field_is_generic(self): - """Test HTTP/Options Method - - Fields are used for the UI to setup inputs correctly. - - Ensure all fields at path `.actions...type` do not have `GenericField` as the value. - """ +class MetadataAttributesFunctional( + MetadataAttributesFunctionalEndpoint, + MetadataAttributesFunctionalTable, + MetadataAttributesFunctionalBase, +): - pass + pass @@ -857,3 +885,5 @@ def test_navigation_no_empty_menu_view_user(self): no_empty_menu_found = False assert no_empty_menu_found + + From 34c327b7b67d8d9e1ecf9c6c1822607aea85af40 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 14:27:25 +0930 Subject: [PATCH 64/84] test(core): Dont test related ticket for table or detail view this view not expected to be ref: #410 #412 #417 --- .../related_ticket/test_related_ticket_viewset.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py b/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py index 2c8752675..a25cabd7f 100644 --- a/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py +++ b/app/core/tests/functional/related_ticket/test_related_ticket_viewset.py @@ -15,7 +15,9 @@ APIPermissionDelete, APIPermissionView, ) -from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional +from api.tests.abstract.test_metadata_functional import ( + MetadataAttributesFunctionalBase +) from core.models.ticket.ticket import Ticket, RelatedTickets @@ -282,7 +284,7 @@ def test_returned_results_only_user_orgs(self): class RelatedTicketsMetadata( ViewSetBase, - MetadataAttributesFunctional, + MetadataAttributesFunctionalBase, TestCase ): From 3bce54b495acffc17385bf21cf2b3eed184a6917 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 14:27:58 +0930 Subject: [PATCH 65/84] test(steeings): Dont test app settings for table view this view not expected to be ref: #410 #412 #417 --- .../functional/app_settings/test_app_settings_viewset.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/settings/tests/functional/app_settings/test_app_settings_viewset.py b/app/settings/tests/functional/app_settings/test_app_settings_viewset.py index 3776730ae..697d91662 100644 --- a/app/settings/tests/functional/app_settings/test_app_settings_viewset.py +++ b/app/settings/tests/functional/app_settings/test_app_settings_viewset.py @@ -16,7 +16,10 @@ SerializerChange, SerializerView, ) -from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional +from api.tests.abstract.test_metadata_functional import ( + MetadataAttributesFunctionalBase, + MetadataAttributesFunctionalEndpoint +) from settings.models.app_settings import AppSettings @@ -247,7 +250,8 @@ class AppSettingsViewSet( class AppSettingsMetadata( ViewSetBase, - MetadataAttributesFunctional, + MetadataAttributesFunctionalEndpoint, + MetadataAttributesFunctionalBase, TestCase ): From d9b9e320191ec29631002cb88dfcd93c3495fb41 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 14:28:38 +0930 Subject: [PATCH 66/84] test(settings): Dont test user settings for table view this view not expected to be ref: #410 #417 closes #412 --- .../user_settings/test_user_settings_viewset.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/settings/tests/functional/user_settings/test_user_settings_viewset.py b/app/settings/tests/functional/user_settings/test_user_settings_viewset.py index 6c99f997c..590645221 100644 --- a/app/settings/tests/functional/user_settings/test_user_settings_viewset.py +++ b/app/settings/tests/functional/user_settings/test_user_settings_viewset.py @@ -16,7 +16,10 @@ SerializerChange, SerializerView, ) -from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional +from api.tests.abstract.test_metadata_functional import ( + MetadataAttributesFunctionalEndpoint, + MetadataAttributesFunctionalBase, +) from settings.models.user_settings import UserSettings @@ -273,7 +276,8 @@ class UserSettingsViewSet( class UserSettingsMetadata( ViewSetBase, - MetadataAttributesFunctional, + MetadataAttributesFunctionalEndpoint, + MetadataAttributesFunctionalBase, TestCase ): From 5de88933301d23130bcfa35d9d813677f3e52420 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 14:49:47 +0930 Subject: [PATCH 67/84] fix(core): correctr the required parameters for related ticket serializer when fetching own url ref: #410 #412 #417 --- app/core/serializers/ticket_related.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/serializers/ticket_related.py b/app/core/serializers/ticket_related.py index 879d34ea0..7594bc533 100644 --- a/app/core/serializers/ticket_related.py +++ b/app/core/serializers/ticket_related.py @@ -81,7 +81,7 @@ def get_url(self, item) -> dict: ticket_id = int(self._kwargs['context']['view'].kwargs['ticket_id']) return { - '_self': item.get_url( ticket_id = ticket_id, request = request ), + '_self': item.get_url( request = request ), } From 39539a4baec4894b4a767c506c66b9fa9000f1e7 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 15:04:46 +0930 Subject: [PATCH 68/84] test(core): Dont test History for table view this view has a seperate view, History. ref: #410 #412 #417 --- .../tests/functional/test_history/test_history_viewset.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/core/tests/functional/test_history/test_history_viewset.py b/app/core/tests/functional/test_history/test_history_viewset.py index ba395d7fd..3f7be67a0 100644 --- a/app/core/tests/functional/test_history/test_history_viewset.py +++ b/app/core/tests/functional/test_history/test_history_viewset.py @@ -12,7 +12,10 @@ from access.models import Organization, Team, TeamUsers, Permission from api.tests.abstract.api_permissions_viewset import APIPermissionView -from api.tests.abstract.test_metadata_functional import MetadataAttributesFunctional +from api.tests.abstract.test_metadata_functional import ( + MetadataAttributesFunctionalBase, + MetadataAttributesFunctionalEndpoint, +) from core.models.history import History @@ -340,7 +343,8 @@ def test_returned_results_only_user_orgs(self): class HistoryMetadata( ViewSetBase, - MetadataAttributesFunctional, + MetadataAttributesFunctionalEndpoint, + MetadataAttributesFunctionalBase, TestCase ): From 3cdd8adf38257a20bd2e0f9ae7820cd919416a81 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 15:30:23 +0930 Subject: [PATCH 69/84] test(core): Correct `url.self`checks to use list view history uses curtom view and has no detail ref: #410 #412 #417 --- .../test_history/test_history_viewset.py | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/app/core/tests/functional/test_history/test_history_viewset.py b/app/core/tests/functional/test_history/test_history_viewset.py index 3f7be67a0..fce8c102f 100644 --- a/app/core/tests/functional/test_history/test_history_viewset.py +++ b/app/core/tests/functional/test_history/test_history_viewset.py @@ -348,4 +348,52 @@ class HistoryMetadata( TestCase ): - pass + + + + def test_method_options_request_detail_data_has_key_urls_self(self): + """Test HTTP/Options Method + + This is a custom test of a test with the same name. + history view has no detail view, due to using a custom + view "history", + + Ensure the request data returned has key `urls.self` + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-list', + kwargs=self.url_kwargs + ), + content_type='application/json' + ) + + assert 'urls' in response.data + + + def test_method_options_request_detail_data_key_urls_self_is_str(self): + """Test HTTP/Options Method + + This is a custom test of a test with the same name. + history view has no detail view, due to using a custom + view "history", + + Ensure the request data key `urls.self` is a string + """ + + client = Client() + client.force_login(self.view_user) + + response = client.options( + reverse( + self.app_namespace + ':' + self.url_name + '-list', + kwargs=self.url_kwargs + ), + content_type='application/json' + ) + + assert type(response.data['urls']['self']) is str From c5faf3115da5d544eb1b766d7e24d6c8ee3ee2cd Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 15:57:11 +0930 Subject: [PATCH 70/84] fix(settings): Add missing `get_url` function to app_settings model ref: #410 #412 #417 --- app/settings/models/app_settings.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/settings/models/app_settings.py b/app/settings/models/app_settings.py index 69e64882a..06e6c9375 100644 --- a/app/settings/models/app_settings.py +++ b/app/settings/models/app_settings.py @@ -1,5 +1,7 @@ from django.db import models +from rest_framework.reverse import reverse + from access.fields import * from access.models import Organization @@ -138,6 +140,20 @@ def get_organization(self): return self.global_organization + + def get_url( self, request = None ) -> str: + + model_name = str(self._meta.verbose_name.lower()).replace(' ', '_') + + + if request: + + return reverse(f"v2:_api_v2_app_settings-detail", request=request, kwargs = { 'pk': self.pk } ) + + return reverse(f"v2:_api_v2_app_settings-detail", kwargs = { 'pk': self.pk } ) + + + def clean(self): from django.core.exceptions import ValidationError From 17f47040d6737905a1769eee5c45d9d15339fdbf Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 15:57:36 +0930 Subject: [PATCH 71/84] fix(settings): Add missing `get_url` function to user_settings model ref: #412 #417 closes #410 --- app/settings/models/user_settings.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/settings/models/user_settings.py b/app/settings/models/user_settings.py index 462ea215e..2a7acf84d 100644 --- a/app/settings/models/user_settings.py +++ b/app/settings/models/user_settings.py @@ -1,5 +1,7 @@ import zoneinfo +from rest_framework.reverse import reverse + from django.contrib.auth.models import User from django.conf import settings from django.db import models @@ -86,6 +88,20 @@ def get_organization(self): return self.default_organization + + def get_url( self, request = None ) -> str: + + model_name = str(self._meta.verbose_name.lower()).replace(' ', '_') + + + if request: + + return reverse(f"v2:_api_v2_user_settings-detail", request=request, kwargs = { 'pk': self.pk } ) + + return reverse(f"v2:_api_v2_user_settings-detail", kwargs = { 'pk': self.pk } ) + + + @receiver(post_save, sender=User) def new_user_callback(sender, **kwargs): settings = UserSettings.objects.filter(user=kwargs['instance']) From ca2da06d2cd393cabb7e172ad47dfb2dd922d952 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 16:33:13 +0930 Subject: [PATCH 72/84] chore: squash previous releases migrations Every release that occurs is squash ALL migrations to limit the amount of migrations ref: #408 #417 --- ...on_options_alter_team_options_and_more.py} | 40 +++++++- .../migrations/0002_alter_team_options.py | 17 ---- ...ptions_alter_teamusers_options_and_more.py | 44 --------- .../migrations/0005_alter_team_team_name.py | 18 ---- .../0006_alter_team_organization.py | 20 ---- ...ken_expires_alter_authtoken_id_and_more.py | 2 +- ...2_alter_knowledgebase_options_and_more.py} | 16 +++- ...03_alter_knowledgebase_options_and_more.py | 21 ----- ...ter_knowledgebase_organization_and_more.py | 27 ------ .../0004_alter_configgroups_options.py | 17 ---- ...04_alter_configgroups_options_and_more.py} | 29 ++++-- .../0005_alter_configgroupsoftware_options.py | 17 ---- .../migrations/0007_configgroups_hosts.py | 19 ---- .../0008_move_data_configgroup_hosts.py | 75 --------------- .../0009_alter_configgroupsoftware_action.py | 18 ---- ..._configgrouphosts_organization_and_more.py | 31 ------- ...lter_history_after_alter_history_before.py | 23 ----- ...ns_alter_manufacturer_options_and_more.py} | 59 ++++++++---- .../0008_alter_manufacturer_options.py | 17 ---- .../migrations/0009_alter_notes_options.py | 17 ---- ...er_history_options_alter_history_action.py | 22 ----- app/core/migrations/0012_alter_notes_note.py | 19 ---- .../0013_alter_manufacturer_modified.py | 20 ---- .../0014_alter_ticketcomment_options.py | 17 ---- ...5_alter_relatedtickets_options_and_more.py | 21 ----- ...lter_manufacturer_organization_and_more.py | 56 ----------- ...ons_alter_devicemodel_options_and_more.py} | 86 ++++++++++++++--- ...05_alter_devicesoftware_action_and_more.py | 34 ------- .../migrations/0006_alter_device_options.py | 17 ---- .../0007_alter_devicemodel_options.py | 17 ---- ...008_alter_deviceoperatingsystem_options.py | 17 ---- .../0009_alter_devicesoftware_options.py | 17 ---- .../0010_alter_devicetype_options.py | 17 ---- .../0011_alter_operatingsystem_options.py | 17 ---- ...12_alter_operatingsystemversion_options.py | 17 ---- .../migrations/0013_alter_software_options.py | 17 ---- .../0014_alter_softwarecategory_options.py | 17 ---- .../0016_alter_devicesoftware_action.py | 18 ---- .../0017_alter_softwareversion_options.py | 17 ---- ...0018_alter_device_organization_and_more.py | 71 -------------- ...0019_alter_deviceoperatingsystem_device.py | 19 ---- ...ns_alter_cluster_cluster_type_and_more.py} | 21 +++-- ..._alter_port_options_alter_port_protocol.py | 22 ----- ...007_alter_cluster_organization_and_more.py | 36 ------- ...t_description_alter_project_id_and_more.py | 11 +-- ...003_alter_project_organization_and_more.py | 36 ------- ...ns_alter_externallink_options_and_more.py} | 88 +++++++++++++++++- .../0005_alter_externallink_options.py | 17 ---- ...ettings_device_model_is_global_and_more.py | 93 ------------------- .../0007_alter_appsettings_options.py | 17 ---- .../0008_alter_usersettings_options.py | 17 ---- .../0010_alter_externallink_organization.py | 21 ----- 52 files changed, 290 insertions(+), 1169 deletions(-) rename app/access/migrations/{0003_alter_organization_id_alter_organization_manager_and_more.py => 0002_alter_organization_options_alter_team_options_and_more.py} (50%) delete mode 100644 app/access/migrations/0002_alter_team_options.py delete mode 100644 app/access/migrations/0004_alter_organization_options_alter_teamusers_options_and_more.py delete mode 100644 app/access/migrations/0005_alter_team_team_name.py delete mode 100644 app/access/migrations/0006_alter_team_organization.py rename app/assistance/migrations/{0002_remove_knowledgebasecategory_slug_and_more.py => 0002_alter_knowledgebase_options_and_more.py} (67%) delete mode 100644 app/assistance/migrations/0003_alter_knowledgebase_options_and_more.py delete mode 100644 app/assistance/migrations/0004_alter_knowledgebase_organization_and_more.py delete mode 100644 app/config_management/migrations/0004_alter_configgroups_options.py rename app/config_management/migrations/{0006_alter_configgrouphosts_group_and_more.py => 0004_alter_configgroups_options_and_more.py} (76%) delete mode 100644 app/config_management/migrations/0005_alter_configgroupsoftware_options.py delete mode 100644 app/config_management/migrations/0007_configgroups_hosts.py delete mode 100644 app/config_management/migrations/0008_move_data_configgroup_hosts.py delete mode 100644 app/config_management/migrations/0009_alter_configgroupsoftware_action.py delete mode 100644 app/config_management/migrations/0010_alter_configgrouphosts_organization_and_more.py delete mode 100644 app/core/migrations/0007_alter_history_after_alter_history_before.py rename app/core/migrations/{0010_alter_history_action_alter_history_after_and_more.py => 0007_alter_history_options_alter_manufacturer_options_and_more.py} (72%) delete mode 100644 app/core/migrations/0008_alter_manufacturer_options.py delete mode 100644 app/core/migrations/0009_alter_notes_options.py delete mode 100644 app/core/migrations/0011_alter_history_options_alter_history_action.py delete mode 100644 app/core/migrations/0012_alter_notes_note.py delete mode 100644 app/core/migrations/0013_alter_manufacturer_modified.py delete mode 100644 app/core/migrations/0014_alter_ticketcomment_options.py delete mode 100644 app/core/migrations/0015_alter_relatedtickets_options_and_more.py delete mode 100644 app/core/migrations/0016_alter_manufacturer_organization_and_more.py rename app/itam/migrations/{0015_alter_device_device_model_alter_device_device_type_and_more.py => 0005_alter_device_options_alter_devicemodel_options_and_more.py} (75%) delete mode 100644 app/itam/migrations/0005_alter_devicesoftware_action_and_more.py delete mode 100644 app/itam/migrations/0006_alter_device_options.py delete mode 100644 app/itam/migrations/0007_alter_devicemodel_options.py delete mode 100644 app/itam/migrations/0008_alter_deviceoperatingsystem_options.py delete mode 100644 app/itam/migrations/0009_alter_devicesoftware_options.py delete mode 100644 app/itam/migrations/0010_alter_devicetype_options.py delete mode 100644 app/itam/migrations/0011_alter_operatingsystem_options.py delete mode 100644 app/itam/migrations/0012_alter_operatingsystemversion_options.py delete mode 100644 app/itam/migrations/0013_alter_software_options.py delete mode 100644 app/itam/migrations/0014_alter_softwarecategory_options.py delete mode 100644 app/itam/migrations/0016_alter_devicesoftware_action.py delete mode 100644 app/itam/migrations/0017_alter_softwareversion_options.py delete mode 100644 app/itam/migrations/0018_alter_device_organization_and_more.py delete mode 100644 app/itam/migrations/0019_alter_deviceoperatingsystem_device.py rename app/itim/migrations/{0005_alter_cluster_cluster_type_alter_cluster_id_and_more.py => 0005_alter_port_options_alter_cluster_cluster_type_and_more.py} (76%) delete mode 100644 app/itim/migrations/0006_alter_port_options_alter_port_protocol.py delete mode 100644 app/itim/migrations/0007_alter_cluster_organization_and_more.py delete mode 100644 app/project_management/migrations/0003_alter_project_organization_and_more.py rename app/settings/migrations/{0009_usersettings_timezone_alter_usersettings_user.py => 0005_alter_appsettings_options_alter_externallink_options_and_more.py} (83%) delete mode 100644 app/settings/migrations/0005_alter_externallink_options.py delete mode 100644 app/settings/migrations/0006_alter_appsettings_device_model_is_global_and_more.py delete mode 100644 app/settings/migrations/0007_alter_appsettings_options.py delete mode 100644 app/settings/migrations/0008_alter_usersettings_options.py delete mode 100644 app/settings/migrations/0010_alter_externallink_organization.py diff --git a/app/access/migrations/0003_alter_organization_id_alter_organization_manager_and_more.py b/app/access/migrations/0002_alter_organization_options_alter_team_options_and_more.py similarity index 50% rename from app/access/migrations/0003_alter_organization_id_alter_organization_manager_and_more.py rename to app/access/migrations/0002_alter_organization_options_alter_team_options_and_more.py index ee85fcf2b..ccde4e196 100644 --- a/app/access/migrations/0003_alter_organization_id_alter_organization_manager_and_more.py +++ b/app/access/migrations/0002_alter_organization_options_alter_team_options_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 import access.models import django.db.models.deletion @@ -9,11 +9,23 @@ class Migration(migrations.Migration): dependencies = [ - ('access', '0002_alter_team_options'), + ('access', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ + migrations.AlterModelOptions( + name='organization', + options={'ordering': ['name'], 'verbose_name': 'Organization', 'verbose_name_plural': 'Organizations'}, + ), + migrations.AlterModelOptions( + name='team', + options={'ordering': ['team_name'], 'verbose_name': 'Team', 'verbose_name_plural': 'Teams'}, + ), + migrations.AlterModelOptions( + name='teamusers', + options={'ordering': ['user'], 'verbose_name': 'Team User', 'verbose_name_plural': 'Team Users'}, + ), migrations.AlterField( model_name='organization', name='id', @@ -47,11 +59,31 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='team', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='team', name='team_name', - field=models.CharField(default='', help_text='Name to give this team', max_length=50, verbose_name='Name'), + field=models.CharField(help_text='Name to give this team', max_length=50, verbose_name='Name'), + ), + migrations.AlterField( + model_name='teamusers', + name='id', + field=models.AutoField(help_text='ID of this Team User', primary_key=True, serialize=False, unique=True, verbose_name='ID'), + ), + migrations.AlterField( + model_name='teamusers', + name='manager', + field=models.BooleanField(blank=True, default=False, help_text='Is this user to be a manager of this team', verbose_name='manager'), + ), + migrations.AlterField( + model_name='teamusers', + name='team', + field=models.ForeignKey(help_text='Team user belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='team', to='access.team', verbose_name='Team'), + ), + migrations.AlterField( + model_name='teamusers', + name='user', + field=models.ForeignKey(help_text='User who will be added to the team', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), ), ] diff --git a/app/access/migrations/0002_alter_team_options.py b/app/access/migrations/0002_alter_team_options.py deleted file mode 100644 index 7dcb95296..000000000 --- a/app/access/migrations/0002_alter_team_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 06:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0001_initial'), - ] - - operations = [ - migrations.AlterModelOptions( - name='team', - options={'ordering': ['team_name'], 'verbose_name': 'Team', 'verbose_name_plural': 'Teams'}, - ), - ] diff --git a/app/access/migrations/0004_alter_organization_options_alter_teamusers_options_and_more.py b/app/access/migrations/0004_alter_organization_options_alter_teamusers_options_and_more.py deleted file mode 100644 index 51a3c7f3f..000000000 --- a/app/access/migrations/0004_alter_organization_options_alter_teamusers_options_and_more.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-16 06:54 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterModelOptions( - name='organization', - options={'ordering': ['name'], 'verbose_name': 'Organization', 'verbose_name_plural': 'Organizations'}, - ), - migrations.AlterModelOptions( - name='teamusers', - options={'ordering': ['user'], 'verbose_name': 'Team User', 'verbose_name_plural': 'Team Users'}, - ), - migrations.AlterField( - model_name='teamusers', - name='id', - field=models.AutoField(help_text='ID of this Team User', primary_key=True, serialize=False, unique=True, verbose_name='ID'), - ), - migrations.AlterField( - model_name='teamusers', - name='manager', - field=models.BooleanField(blank=True, default=False, help_text='Is this user to be a manager of this team', verbose_name='manager'), - ), - migrations.AlterField( - model_name='teamusers', - name='team', - field=models.ForeignKey(help_text='Team user belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='team', to='access.team', verbose_name='Team'), - ), - migrations.AlterField( - model_name='teamusers', - name='user', - field=models.ForeignKey(help_text='User who will be added to the team', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), - ), - ] diff --git a/app/access/migrations/0005_alter_team_team_name.py b/app/access/migrations/0005_alter_team_team_name.py deleted file mode 100644 index 30ce774a1..000000000 --- a/app/access/migrations/0005_alter_team_team_name.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-07 06:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0004_alter_organization_options_alter_teamusers_options_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='team', - name='team_name', - field=models.CharField(help_text='Name to give this team', max_length=50, verbose_name='Name'), - ), - ] diff --git a/app/access/migrations/0006_alter_team_organization.py b/app/access/migrations/0006_alter_team_organization.py deleted file mode 100644 index 974870e8e..000000000 --- a/app/access/migrations/0006_alter_team_organization.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:41 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0005_alter_team_team_name'), - ] - - operations = [ - migrations.AlterField( - model_name='team', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - ] diff --git a/app/api/migrations/0002_alter_authtoken_expires_alter_authtoken_id_and_more.py b/app/api/migrations/0002_alter_authtoken_expires_alter_authtoken_id_and_more.py index e7d7ed735..ded341fe2 100644 --- a/app/api/migrations/0002_alter_authtoken_expires_alter_authtoken_id_and_more.py +++ b/app/api/migrations/0002_alter_authtoken_expires_alter_authtoken_id_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 import django.db.models.deletion from django.conf import settings diff --git a/app/assistance/migrations/0002_remove_knowledgebasecategory_slug_and_more.py b/app/assistance/migrations/0002_alter_knowledgebase_options_and_more.py similarity index 67% rename from app/assistance/migrations/0002_remove_knowledgebasecategory_slug_and_more.py rename to app/assistance/migrations/0002_alter_knowledgebase_options_and_more.py index d72490515..0b69c4e3c 100644 --- a/app/assistance/migrations/0002_remove_knowledgebasecategory_slug_and_more.py +++ b/app/assistance/migrations/0002_alter_knowledgebase_options_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 import access.models import django.db.models.deletion @@ -8,11 +8,19 @@ class Migration(migrations.Migration): dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), + ('access', '0002_alter_organization_options_alter_team_options_and_more'), ('assistance', '0001_initial'), ] operations = [ + migrations.AlterModelOptions( + name='knowledgebase', + options={'ordering': ['title'], 'verbose_name': 'Knowledge Base', 'verbose_name_plural': 'Knowledge Base Articles'}, + ), + migrations.AlterModelOptions( + name='knowledgebasecategory', + options={'ordering': ['name'], 'verbose_name': 'Knowledge Base Category', 'verbose_name_plural': 'Knowledge Base Categories'}, + ), migrations.RemoveField( model_name='knowledgebasecategory', name='slug', @@ -30,7 +38,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='knowledgebase', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='knowledgebasecategory', @@ -50,6 +58,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='knowledgebasecategory', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), ] diff --git a/app/assistance/migrations/0003_alter_knowledgebase_options_and_more.py b/app/assistance/migrations/0003_alter_knowledgebase_options_and_more.py deleted file mode 100644 index 5c9f65ea8..000000000 --- a/app/assistance/migrations/0003_alter_knowledgebase_options_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-16 06:54 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('assistance', '0002_remove_knowledgebasecategory_slug_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='knowledgebase', - options={'ordering': ['title'], 'verbose_name': 'Knowledge Base', 'verbose_name_plural': 'Knowledge Base Articles'}, - ), - migrations.AlterModelOptions( - name='knowledgebasecategory', - options={'ordering': ['name'], 'verbose_name': 'Knowledge Base Category', 'verbose_name_plural': 'Knowledge Base Categories'}, - ), - ] diff --git a/app/assistance/migrations/0004_alter_knowledgebase_organization_and_more.py b/app/assistance/migrations/0004_alter_knowledgebase_organization_and_more.py deleted file mode 100644 index 990f4f4bc..000000000 --- a/app/assistance/migrations/0004_alter_knowledgebase_organization_and_more.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:41 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_alter_team_organization'), - ('assistance', '0003_alter_knowledgebase_options_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='knowledgebase', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='knowledgebasecategory', - name='organization', - field=models.ForeignKey(default=None, help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - preserve_default=False, - ), - ] diff --git a/app/config_management/migrations/0004_alter_configgroups_options.py b/app/config_management/migrations/0004_alter_configgroups_options.py deleted file mode 100644 index e860267dc..000000000 --- a/app/config_management/migrations/0004_alter_configgroups_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 06:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('config_management', '0003_alter_configgroups_options_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='configgroups', - options={'ordering': ['name'], 'verbose_name': 'Config Group', 'verbose_name_plural': 'Config Groups'}, - ), - ] diff --git a/app/config_management/migrations/0006_alter_configgrouphosts_group_and_more.py b/app/config_management/migrations/0004_alter_configgroups_options_and_more.py similarity index 76% rename from app/config_management/migrations/0006_alter_configgrouphosts_group_and_more.py rename to app/config_management/migrations/0004_alter_configgroups_options_and_more.py index 7211f636d..dc205b938 100644 --- a/app/config_management/migrations/0006_alter_configgrouphosts_group_and_more.py +++ b/app/config_management/migrations/0004_alter_configgroups_options_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 import access.models import config_management.models.groups @@ -9,12 +9,25 @@ class Migration(migrations.Migration): dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), - ('config_management', '0005_alter_configgroupsoftware_options'), - ('itam', '0014_alter_softwarecategory_options'), + ('access', '0002_alter_organization_options_alter_team_options_and_more'), + ('config_management', '0003_alter_configgroups_options_and_more'), + ('itam', '0004_alter_deviceoperatingsystem_device_and_more'), ] operations = [ + migrations.AlterModelOptions( + name='configgroups', + options={'ordering': ['name'], 'verbose_name': 'Config Group', 'verbose_name_plural': 'Config Groups'}, + ), + migrations.AlterModelOptions( + name='configgroupsoftware', + options={'ordering': ['-action', 'software'], 'verbose_name': 'Config Group Software', 'verbose_name_plural': 'Config Group Softwares'}, + ), + migrations.AddField( + model_name='configgroups', + name='hosts', + field=models.ManyToManyField(blank=True, help_text='Hosts that are part of this group', to='itam.device', verbose_name='Hosts'), + ), migrations.AlterField( model_name='configgrouphosts', name='group', @@ -43,7 +56,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='configgrouphosts', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='configgroups', @@ -73,7 +86,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='configgroups', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='configgroups', @@ -83,7 +96,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='configgroupsoftware', name='action', - field=models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, help_text='ACtion to perform with this software', max_length=1, null=True, verbose_name='Action'), + field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='ACtion to perform with this software', null=True, verbose_name='Action'), ), migrations.AlterField( model_name='configgroupsoftware', @@ -108,7 +121,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='configgroupsoftware', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='configgroupsoftware', diff --git a/app/config_management/migrations/0005_alter_configgroupsoftware_options.py b/app/config_management/migrations/0005_alter_configgroupsoftware_options.py deleted file mode 100644 index 620b0ab88..000000000 --- a/app/config_management/migrations/0005_alter_configgroupsoftware_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 06:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('config_management', '0004_alter_configgroups_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='configgroupsoftware', - options={'ordering': ['-action', 'software'], 'verbose_name': 'Config Group Software', 'verbose_name_plural': 'Config Group Softwares'}, - ), - ] diff --git a/app/config_management/migrations/0007_configgroups_hosts.py b/app/config_management/migrations/0007_configgroups_hosts.py deleted file mode 100644 index d2215e7ca..000000000 --- a/app/config_management/migrations/0007_configgroups_hosts.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-16 06:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('config_management', '0006_alter_configgrouphosts_group_and_more'), - ('itam', '0015_alter_device_device_model_alter_device_device_type_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='configgroups', - name='hosts', - field=models.ManyToManyField(blank=True, help_text='Hosts that are part of this group', to='itam.device', verbose_name='Hosts'), - ), - ] diff --git a/app/config_management/migrations/0008_move_data_configgroup_hosts.py b/app/config_management/migrations/0008_move_data_configgroup_hosts.py deleted file mode 100644 index 2a3bafe8a..000000000 --- a/app/config_management/migrations/0008_move_data_configgroup_hosts.py +++ /dev/null @@ -1,75 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-16 06:54 - -from django.db import migrations, models - - -def migrate_to_configgroups_hosts(apps, schema_editor): - - if schema_editor.connection.alias != "default": - - return - - print('') - - ConfigGroups = apps.get_model('config_management', 'ConfigGroups') - ConfigGroupHosts = apps.get_model('config_management', 'ConfigGroupHosts') - - current_data = ConfigGroupHosts.objects.all() - - for host in current_data: - - print(f'Begin migrating host {host.host} in group {host.group}:') - - config_group = ConfigGroups.objects.get(pk = host.group.id) - - print(f' migrate {host.host} in group {config_group}') - - config_group.hosts.add( host.host ) - - try: - - was_migrated = ConfigGroups.objects.get(pk = host.group.id) - - if host.host in was_migrated.hosts.all(): - - print(f' successfully migrated {host.id} {host.host} to new table') - - ConfigGroupHosts.objects.get(pk = host.id).delete() - - try: - - ConfigGroupHosts.objects.get(pk = host.id) - - print(f' Error Failed to remove old data for host {host.host}') - - except ConfigGroupHosts.DoesNotExist: - - print(f' Old data removed') - - except ConfigGroupHosts.DoesNotExist: - - print(f' Error, {host.host} was not migrated to new table') - - - old_data = ConfigGroupHosts.objects.all() - - if len(old_data) == 0: - - print(f'Successfully migrated data to new table, removing old table') - - migrations.DeleteModel("ConfigGroupHosts") - - - - - - -class Migration(migrations.Migration): - - dependencies = [ - ('config_management', '0007_configgroups_hosts'), - ] - - operations = [ - migrations.RunPython(migrate_to_configgroups_hosts), - ] diff --git a/app/config_management/migrations/0009_alter_configgroupsoftware_action.py b/app/config_management/migrations/0009_alter_configgroupsoftware_action.py deleted file mode 100644 index 48e3a9170..000000000 --- a/app/config_management/migrations/0009_alter_configgroupsoftware_action.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-17 03:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('config_management', '0008_move_data_configgroup_hosts'), - ] - - operations = [ - migrations.AlterField( - model_name='configgroupsoftware', - name='action', - field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='ACtion to perform with this software', null=True, verbose_name='Action'), - ), - ] diff --git a/app/config_management/migrations/0010_alter_configgrouphosts_organization_and_more.py b/app/config_management/migrations/0010_alter_configgrouphosts_organization_and_more.py deleted file mode 100644 index a4a9cc2db..000000000 --- a/app/config_management/migrations/0010_alter_configgrouphosts_organization_and_more.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:41 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_alter_team_organization'), - ('config_management', '0009_alter_configgroupsoftware_action'), - ] - - operations = [ - migrations.AlterField( - model_name='configgrouphosts', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='configgroups', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='configgroupsoftware', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - ] diff --git a/app/core/migrations/0007_alter_history_after_alter_history_before.py b/app/core/migrations/0007_alter_history_after_alter_history_before.py deleted file mode 100644 index 348d614ba..000000000 --- a/app/core/migrations/0007_alter_history_after_alter_history_before.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-11 16:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0006_ticket_milestone_ticket_opened_by_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='history', - name='after', - field=models.JSONField(blank=True, default=None, help_text='JSON Object After Change', null=True), - ), - migrations.AlterField( - model_name='history', - name='before', - field=models.JSONField(blank=True, default=None, help_text='JSON Object before Change', null=True), - ), - ] diff --git a/app/core/migrations/0010_alter_history_action_alter_history_after_and_more.py b/app/core/migrations/0007_alter_history_options_alter_manufacturer_options_and_more.py similarity index 72% rename from app/core/migrations/0010_alter_history_action_alter_history_after_and_more.py rename to app/core/migrations/0007_alter_history_options_alter_manufacturer_options_and_more.py index 1a03ba66f..9d65d70bf 100644 --- a/app/core/migrations/0010_alter_history_action_alter_history_after_and_more.py +++ b/app/core/migrations/0007_alter_history_options_alter_manufacturer_options_and_more.py @@ -1,7 +1,9 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 +import access.fields import access.models import django.db.models.deletion +import django.utils.timezone from django.conf import settings from django.db import migrations, models @@ -9,19 +11,39 @@ class Migration(migrations.Migration): dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), - ('config_management', '0006_alter_configgrouphosts_group_and_more'), - ('core', '0009_alter_notes_options'), - ('itam', '0014_alter_softwarecategory_options'), - ('itim', '0005_alter_cluster_cluster_type_alter_cluster_id_and_more'), + ('access', '0002_alter_organization_options_alter_team_options_and_more'), + ('config_management', '0004_alter_configgroups_options_and_more'), + ('core', '0006_ticket_milestone_ticket_opened_by_and_more'), + ('itam', '0004_alter_deviceoperatingsystem_device_and_more'), + ('itim', '0005_alter_port_options_alter_cluster_cluster_type_and_more'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ + migrations.AlterModelOptions( + name='history', + options={'ordering': ['-created'], 'verbose_name': 'History', 'verbose_name_plural': 'History'}, + ), + migrations.AlterModelOptions( + name='manufacturer', + options={'ordering': ['name'], 'verbose_name': 'Manufacturer', 'verbose_name_plural': 'Manufacturers'}, + ), + migrations.AlterModelOptions( + name='notes', + options={'ordering': ['-created'], 'verbose_name': 'Note', 'verbose_name_plural': 'Notes'}, + ), + migrations.AlterModelOptions( + name='relatedtickets', + options={'ordering': ['id'], 'verbose_name': 'Related Ticket', 'verbose_name_plural': 'Related Tickets'}, + ), + migrations.AlterModelOptions( + name='ticketcomment', + options={'ordering': ['created', 'ticket', 'parent_id'], 'verbose_name': 'Ticket Comment', 'verbose_name_plural': 'Ticket Comments'}, + ), migrations.AlterField( model_name='history', name='action', - field=models.IntegerField(choices=[('1', 'Create'), ('2', 'Update'), ('3', 'Delete')], default=None, help_text='History action performed', null=True, verbose_name='Action'), + field=models.IntegerField(choices=[(1, 'Create'), (2, 'Update'), (3, 'Delete')], default=None, help_text='History action performed', null=True, verbose_name='Action'), ), migrations.AlterField( model_name='history', @@ -78,6 +100,11 @@ class Migration(migrations.Migration): name='model_notes', field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'), ), + migrations.AlterField( + model_name='manufacturer', + name='modified', + field=access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified'), + ), migrations.AlterField( model_name='manufacturer', name='name', @@ -86,7 +113,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='manufacturer', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='notes', @@ -116,7 +143,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='notes', name='note', - field=models.TextField(blank=True, default=None, help_text='The tid bit you wish to add', null=True, verbose_name='Note'), + field=models.TextField(help_text='The tid bit you wish to add', verbose_name='Note'), ), migrations.AlterField( model_name='notes', @@ -126,7 +153,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='notes', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='notes', @@ -151,7 +178,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='relatedtickets', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='ticket', @@ -161,7 +188,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='ticket', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='ticketcategory', @@ -176,12 +203,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='ticketcategory', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='ticketcomment', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='ticketcommentcategory', @@ -196,11 +223,11 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='ticketcommentcategory', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='ticketlinkeditem', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), ] diff --git a/app/core/migrations/0008_alter_manufacturer_options.py b/app/core/migrations/0008_alter_manufacturer_options.py deleted file mode 100644 index 5f068e8e0..000000000 --- a/app/core/migrations/0008_alter_manufacturer_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 06:58 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0007_alter_history_after_alter_history_before'), - ] - - operations = [ - migrations.AlterModelOptions( - name='manufacturer', - options={'ordering': ['name'], 'verbose_name': 'Manufacturer', 'verbose_name_plural': 'Manufacturers'}, - ), - ] diff --git a/app/core/migrations/0009_alter_notes_options.py b/app/core/migrations/0009_alter_notes_options.py deleted file mode 100644 index ff3f11e3a..000000000 --- a/app/core/migrations/0009_alter_notes_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 07:02 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0008_alter_manufacturer_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='notes', - options={'ordering': ['-created'], 'verbose_name': 'Note', 'verbose_name_plural': 'Notes'}, - ), - ] diff --git a/app/core/migrations/0011_alter_history_options_alter_history_action.py b/app/core/migrations/0011_alter_history_options_alter_history_action.py deleted file mode 100644 index 711e181bd..000000000 --- a/app/core/migrations/0011_alter_history_options_alter_history_action.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-18 03:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0010_alter_history_action_alter_history_after_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='history', - options={'ordering': ['-created'], 'verbose_name': 'History', 'verbose_name_plural': 'History'}, - ), - migrations.AlterField( - model_name='history', - name='action', - field=models.IntegerField(choices=[(1, 'Create'), (2, 'Update'), (3, 'Delete')], default=None, help_text='History action performed', null=True, verbose_name='Action'), - ), - ] diff --git a/app/core/migrations/0012_alter_notes_note.py b/app/core/migrations/0012_alter_notes_note.py deleted file mode 100644 index c9e7c7c7e..000000000 --- a/app/core/migrations/0012_alter_notes_note.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-19 02:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0011_alter_history_options_alter_history_action'), - ] - - operations = [ - migrations.AlterField( - model_name='notes', - name='note', - field=models.TextField(default='-', help_text='The tid bit you wish to add', verbose_name='Note'), - preserve_default=False, - ), - ] diff --git a/app/core/migrations/0013_alter_manufacturer_modified.py b/app/core/migrations/0013_alter_manufacturer_modified.py deleted file mode 100644 index 7bc6c3b74..000000000 --- a/app/core/migrations/0013_alter_manufacturer_modified.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-19 03:58 - -import access.fields -import django.utils.timezone -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0012_alter_notes_note'), - ] - - operations = [ - migrations.AlterField( - model_name='manufacturer', - name='modified', - field=access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, help_text='Date and time of last modification', verbose_name='Modified'), - ), - ] diff --git a/app/core/migrations/0014_alter_ticketcomment_options.py b/app/core/migrations/0014_alter_ticketcomment_options.py deleted file mode 100644 index 6adff2e1f..000000000 --- a/app/core/migrations/0014_alter_ticketcomment_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-26 13:00 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0013_alter_manufacturer_modified'), - ] - - operations = [ - migrations.AlterModelOptions( - name='ticketcomment', - options={'ordering': ['created', 'ticket', 'parent_id'], 'verbose_name': 'Comment', 'verbose_name_plural': 'Comments'}, - ), - ] diff --git a/app/core/migrations/0015_alter_relatedtickets_options_and_more.py b/app/core/migrations/0015_alter_relatedtickets_options_and_more.py deleted file mode 100644 index b98d93ca3..000000000 --- a/app/core/migrations/0015_alter_relatedtickets_options_and_more.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-03 14:21 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0014_alter_ticketcomment_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='relatedtickets', - options={'ordering': ['id'], 'verbose_name': 'Related Ticket', 'verbose_name_plural': 'Related Tickets'}, - ), - migrations.AlterModelOptions( - name='ticketcomment', - options={'ordering': ['created', 'ticket', 'parent_id'], 'verbose_name': 'Ticket Comment', 'verbose_name_plural': 'Ticket Comments'}, - ), - ] diff --git a/app/core/migrations/0016_alter_manufacturer_organization_and_more.py b/app/core/migrations/0016_alter_manufacturer_organization_and_more.py deleted file mode 100644 index 23c96b10c..000000000 --- a/app/core/migrations/0016_alter_manufacturer_organization_and_more.py +++ /dev/null @@ -1,56 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:41 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_alter_team_organization'), - ('core', '0015_alter_relatedtickets_options_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='manufacturer', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='notes', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='relatedtickets', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='ticket', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='ticketcategory', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='ticketcomment', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='ticketcommentcategory', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='ticketlinkeditem', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - ] diff --git a/app/itam/migrations/0015_alter_device_device_model_alter_device_device_type_and_more.py b/app/itam/migrations/0005_alter_device_options_alter_devicemodel_options_and_more.py similarity index 75% rename from app/itam/migrations/0015_alter_device_device_model_alter_device_device_type_and_more.py rename to app/itam/migrations/0005_alter_device_options_alter_devicemodel_options_and_more.py index 52cb9868a..1f11cb744 100644 --- a/app/itam/migrations/0015_alter_device_device_model_alter_device_device_type_and_more.py +++ b/app/itam/migrations/0005_alter_device_options_alter_devicemodel_options_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 import access.models import django.db.models.deletion @@ -9,11 +9,52 @@ class Migration(migrations.Migration): dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), - ('itam', '0014_alter_softwarecategory_options'), + ('access', '0002_alter_organization_options_alter_team_options_and_more'), + ('core', '0007_alter_history_options_alter_manufacturer_options_and_more'), + ('itam', '0004_alter_deviceoperatingsystem_device_and_more'), ] operations = [ + migrations.AlterModelOptions( + name='device', + options={'ordering': ['name', 'organization'], 'verbose_name': 'Device', 'verbose_name_plural': 'Devices'}, + ), + migrations.AlterModelOptions( + name='devicemodel', + options={'ordering': ['manufacturer', 'name'], 'verbose_name': 'Device Model', 'verbose_name_plural': 'Device Models'}, + ), + migrations.AlterModelOptions( + name='deviceoperatingsystem', + options={'ordering': ['device'], 'verbose_name': 'Device Operating System', 'verbose_name_plural': 'Device Operating Systems'}, + ), + migrations.AlterModelOptions( + name='devicesoftware', + options={'ordering': ['-action', 'software'], 'verbose_name': 'Device Software', 'verbose_name_plural': 'Device Softwares'}, + ), + migrations.AlterModelOptions( + name='devicetype', + options={'ordering': ['name'], 'verbose_name': 'Device Type', 'verbose_name_plural': 'Device Types'}, + ), + migrations.AlterModelOptions( + name='operatingsystem', + options={'ordering': ['name'], 'verbose_name': 'Operating System', 'verbose_name_plural': 'Operating Systems'}, + ), + migrations.AlterModelOptions( + name='operatingsystemversion', + options={'ordering': ['name'], 'verbose_name': 'Operating System Version', 'verbose_name_plural': 'Operating System Versions'}, + ), + migrations.AlterModelOptions( + name='software', + options={'ordering': ['name', 'publisher__name'], 'verbose_name': 'Software', 'verbose_name_plural': 'Softwares'}, + ), + migrations.AlterModelOptions( + name='softwarecategory', + options={'ordering': ['name'], 'verbose_name': 'Software Category', 'verbose_name_plural': 'Software Categories'}, + ), + migrations.AlterModelOptions( + name='softwareversion', + options={'ordering': ['name'], 'verbose_name': 'Software Version', 'verbose_name_plural': 'Software Versions'}, + ), migrations.AlterField( model_name='device', name='device_model', @@ -52,7 +93,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='device', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='devicemodel', @@ -82,12 +123,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='devicemodel', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='deviceoperatingsystem', name='device', - field=models.ForeignKey(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', verbose_name='Device'), + field=models.ForeignKey(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', unique=True, verbose_name='Device'), ), migrations.AlterField( model_name='deviceoperatingsystem', @@ -117,13 +158,18 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='deviceoperatingsystem', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='deviceoperatingsystem', name='version', field=models.CharField(help_text='Version detected as installed', max_length=15, verbose_name='Installed Version'), ), + migrations.AlterField( + model_name='devicesoftware', + name='action', + field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='Action to perform', null=True, verbose_name='Action'), + ), migrations.AlterField( model_name='devicesoftware', name='device', @@ -134,6 +180,11 @@ class Migration(migrations.Migration): name='id', field=models.AutoField(help_text='ID of this item', primary_key=True, serialize=False, unique=True, verbose_name='ID'), ), + migrations.AlterField( + model_name='devicesoftware', + name='installed', + field=models.DateTimeField(blank=True, help_text='Date detected as installed', null=True, verbose_name='Date Installed'), + ), migrations.AlterField( model_name='devicesoftware', name='installedversion', @@ -152,13 +203,18 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='devicesoftware', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='devicesoftware', name='software', field=models.ForeignKey(help_text='Software Name', on_delete=django.db.models.deletion.CASCADE, to='itam.software', verbose_name='Software'), ), + migrations.AlterField( + model_name='devicesoftware', + name='version', + field=models.ForeignKey(blank=True, default=None, help_text='Version to install', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion', verbose_name='Desired Version'), + ), migrations.AlterField( model_name='devicetype', name='id', @@ -182,7 +238,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='devicetype', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='operatingsystem', @@ -207,7 +263,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='operatingsystem', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='operatingsystem', @@ -237,12 +293,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='operatingsystemversion', name='operating_system', - field=models.ForeignKey(help_text='Operating system this version applies to', on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem', verbose_name='Operaating System'), + field=models.ForeignKey(help_text='Operating system this version applies to', on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem', verbose_name='Operating System'), ), migrations.AlterField( model_name='operatingsystemversion', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='software', @@ -272,7 +328,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='software', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='software', @@ -302,7 +358,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='softwarecategory', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='softwareversion', @@ -327,7 +383,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='softwareversion', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='softwareversion', diff --git a/app/itam/migrations/0005_alter_devicesoftware_action_and_more.py b/app/itam/migrations/0005_alter_devicesoftware_action_and_more.py deleted file mode 100644 index 5db66b7c6..000000000 --- a/app/itam/migrations/0005_alter_devicesoftware_action_and_more.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-11 16:03 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0004_alter_deviceoperatingsystem_device_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='devicesoftware', - name='action', - field=models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, help_text='Action to perform', max_length=1, null=True, verbose_name='Action'), - ), - migrations.AlterField( - model_name='devicesoftware', - name='installed', - field=models.DateTimeField(blank=True, help_text='Date detected as installed', null=True, verbose_name='Date Installed'), - ), - migrations.AlterField( - model_name='devicesoftware', - name='installedversion', - field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='installedversion', to='itam.softwareversion', verbose_name='Installed Version'), - ), - migrations.AlterField( - model_name='devicesoftware', - name='version', - field=models.ForeignKey(blank=True, default=None, help_text='Version to install', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion', verbose_name='Desired Version'), - ), - ] diff --git a/app/itam/migrations/0006_alter_device_options.py b/app/itam/migrations/0006_alter_device_options.py deleted file mode 100644 index 37b1c4b90..000000000 --- a/app/itam/migrations/0006_alter_device_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 06:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0005_alter_devicesoftware_action_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='device', - options={'ordering': ['name', 'organization'], 'verbose_name': 'Device', 'verbose_name_plural': 'Devices'}, - ), - ] diff --git a/app/itam/migrations/0007_alter_devicemodel_options.py b/app/itam/migrations/0007_alter_devicemodel_options.py deleted file mode 100644 index ae30b92f8..000000000 --- a/app/itam/migrations/0007_alter_devicemodel_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 07:26 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0006_alter_device_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='devicemodel', - options={'ordering': ['manufacturer', 'name'], 'verbose_name': 'Device Model', 'verbose_name_plural': 'Device Models'}, - ), - ] diff --git a/app/itam/migrations/0008_alter_deviceoperatingsystem_options.py b/app/itam/migrations/0008_alter_deviceoperatingsystem_options.py deleted file mode 100644 index 28d9ebe90..000000000 --- a/app/itam/migrations/0008_alter_deviceoperatingsystem_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 07:33 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0007_alter_devicemodel_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='deviceoperatingsystem', - options={'ordering': ['device'], 'verbose_name': 'Device Operating System', 'verbose_name_plural': 'Device Operating Systems'}, - ), - ] diff --git a/app/itam/migrations/0009_alter_devicesoftware_options.py b/app/itam/migrations/0009_alter_devicesoftware_options.py deleted file mode 100644 index 2a2e7aef5..000000000 --- a/app/itam/migrations/0009_alter_devicesoftware_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 07:39 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0008_alter_deviceoperatingsystem_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='devicesoftware', - options={'ordering': ['-action', 'software'], 'verbose_name': 'Device Software', 'verbose_name_plural': 'Device Softwares'}, - ), - ] diff --git a/app/itam/migrations/0010_alter_devicetype_options.py b/app/itam/migrations/0010_alter_devicetype_options.py deleted file mode 100644 index 6ea3460ad..000000000 --- a/app/itam/migrations/0010_alter_devicetype_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 07:42 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0009_alter_devicesoftware_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='devicetype', - options={'ordering': ['name'], 'verbose_name': 'Device Type', 'verbose_name_plural': 'Device Types'}, - ), - ] diff --git a/app/itam/migrations/0011_alter_operatingsystem_options.py b/app/itam/migrations/0011_alter_operatingsystem_options.py deleted file mode 100644 index 40abb50a7..000000000 --- a/app/itam/migrations/0011_alter_operatingsystem_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 07:51 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0010_alter_devicetype_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='operatingsystem', - options={'ordering': ['name'], 'verbose_name': 'Operating System', 'verbose_name_plural': 'Operating Systems'}, - ), - ] diff --git a/app/itam/migrations/0012_alter_operatingsystemversion_options.py b/app/itam/migrations/0012_alter_operatingsystemversion_options.py deleted file mode 100644 index 606908a10..000000000 --- a/app/itam/migrations/0012_alter_operatingsystemversion_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 07:57 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0011_alter_operatingsystem_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='operatingsystemversion', - options={'ordering': ['name'], 'verbose_name': 'Operating System Version', 'verbose_name_plural': 'Operating System Versions'}, - ), - ] diff --git a/app/itam/migrations/0013_alter_software_options.py b/app/itam/migrations/0013_alter_software_options.py deleted file mode 100644 index 005dc3b4f..000000000 --- a/app/itam/migrations/0013_alter_software_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 08:06 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0012_alter_operatingsystemversion_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='software', - options={'ordering': ['name', 'publisher__name'], 'verbose_name': 'Software', 'verbose_name_plural': 'Softwares'}, - ), - ] diff --git a/app/itam/migrations/0014_alter_softwarecategory_options.py b/app/itam/migrations/0014_alter_softwarecategory_options.py deleted file mode 100644 index 559af963d..000000000 --- a/app/itam/migrations/0014_alter_softwarecategory_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 08:15 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0013_alter_software_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='softwarecategory', - options={'ordering': ['name'], 'verbose_name': 'Software Category', 'verbose_name_plural': 'Software Categories'}, - ), - ] diff --git a/app/itam/migrations/0016_alter_devicesoftware_action.py b/app/itam/migrations/0016_alter_devicesoftware_action.py deleted file mode 100644 index 8c305ccff..000000000 --- a/app/itam/migrations/0016_alter_devicesoftware_action.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-17 03:16 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0015_alter_device_device_model_alter_device_device_type_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='devicesoftware', - name='action', - field=models.IntegerField(blank=True, choices=[(1, 'Install'), (0, 'Remove')], default=None, help_text='Action to perform', null=True, verbose_name='Action'), - ), - ] diff --git a/app/itam/migrations/0017_alter_softwareversion_options.py b/app/itam/migrations/0017_alter_softwareversion_options.py deleted file mode 100644 index 27a930230..000000000 --- a/app/itam/migrations/0017_alter_softwareversion_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-20 07:26 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0016_alter_devicesoftware_action'), - ] - - operations = [ - migrations.AlterModelOptions( - name='softwareversion', - options={'ordering': ['name'], 'verbose_name': 'Software Version', 'verbose_name_plural': 'Software Versions'}, - ), - ] diff --git a/app/itam/migrations/0018_alter_device_organization_and_more.py b/app/itam/migrations/0018_alter_device_organization_and_more.py deleted file mode 100644 index 621908976..000000000 --- a/app/itam/migrations/0018_alter_device_organization_and_more.py +++ /dev/null @@ -1,71 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:43 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_alter_team_organization'), - ('itam', '0017_alter_softwareversion_options'), - ] - - operations = [ - migrations.AlterField( - model_name='device', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='devicemodel', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='deviceoperatingsystem', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='devicesoftware', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='devicetype', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='operatingsystem', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='operatingsystemversion', - name='operating_system', - field=models.ForeignKey(help_text='Operating system this version applies to', on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem', verbose_name='Operating System'), - ), - migrations.AlterField( - model_name='operatingsystemversion', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='software', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='softwarecategory', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='softwareversion', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - ] diff --git a/app/itam/migrations/0019_alter_deviceoperatingsystem_device.py b/app/itam/migrations/0019_alter_deviceoperatingsystem_device.py deleted file mode 100644 index bbfbd54f5..000000000 --- a/app/itam/migrations/0019_alter_deviceoperatingsystem_device.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:43 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('itam', '0018_alter_device_organization_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='deviceoperatingsystem', - name='device', - field=models.ForeignKey(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', unique=True, verbose_name='Device'), - ), - ] diff --git a/app/itim/migrations/0005_alter_cluster_cluster_type_alter_cluster_id_and_more.py b/app/itim/migrations/0005_alter_port_options_alter_cluster_cluster_type_and_more.py similarity index 76% rename from app/itim/migrations/0005_alter_cluster_cluster_type_alter_cluster_id_and_more.py rename to app/itim/migrations/0005_alter_port_options_alter_cluster_cluster_type_and_more.py index c31c814af..c6cf36f3d 100644 --- a/app/itim/migrations/0005_alter_cluster_cluster_type_alter_cluster_id_and_more.py +++ b/app/itim/migrations/0005_alter_port_options_alter_cluster_cluster_type_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 import access.models import django.db.models.deletion @@ -8,11 +8,15 @@ class Migration(migrations.Migration): dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), + ('access', '0002_alter_organization_options_alter_team_options_and_more'), ('itim', '0004_alter_service_config_key_variable'), ] operations = [ + migrations.AlterModelOptions( + name='port', + options={'ordering': ['number', 'protocol'], 'verbose_name': 'Port', 'verbose_name_plural': 'Ports'}, + ), migrations.AlterField( model_name='cluster', name='cluster_type', @@ -36,7 +40,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='cluster', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='cluster', @@ -61,7 +65,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='clustertype', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='port', @@ -81,7 +85,12 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='port', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + ), + migrations.AlterField( + model_name='port', + name='protocol', + field=models.CharField(choices=[('TCP', 'TCP'), ('UDP', 'UDP')], help_text='Layer 4 Network Protocol', max_length=3, verbose_name='Protocol'), ), migrations.AlterField( model_name='service', @@ -101,6 +110,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='service', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), ] diff --git a/app/itim/migrations/0006_alter_port_options_alter_port_protocol.py b/app/itim/migrations/0006_alter_port_options_alter_port_protocol.py deleted file mode 100644 index 173a79747..000000000 --- a/app/itim/migrations/0006_alter_port_options_alter_port_protocol.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-21 11:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('itim', '0005_alter_cluster_cluster_type_alter_cluster_id_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='port', - options={'ordering': ['number', 'protocol'], 'verbose_name': 'Port', 'verbose_name_plural': 'Ports'}, - ), - migrations.AlterField( - model_name='port', - name='protocol', - field=models.CharField(choices=[('TCP', 'TCP'), ('UDP', 'UDP')], help_text='Layer 4 Network Protocol', max_length=3, verbose_name='Protocol'), - ), - ] diff --git a/app/itim/migrations/0007_alter_cluster_organization_and_more.py b/app/itim/migrations/0007_alter_cluster_organization_and_more.py deleted file mode 100644 index 995367d3b..000000000 --- a/app/itim/migrations/0007_alter_cluster_organization_and_more.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:41 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_alter_team_organization'), - ('itim', '0006_alter_port_options_alter_port_protocol'), - ] - - operations = [ - migrations.AlterField( - model_name='cluster', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='clustertype', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='port', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='service', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - ] diff --git a/app/project_management/migrations/0002_alter_project_description_alter_project_id_and_more.py b/app/project_management/migrations/0002_alter_project_description_alter_project_id_and_more.py index 784f7f75b..ebea46fee 100644 --- a/app/project_management/migrations/0002_alter_project_description_alter_project_id_and_more.py +++ b/app/project_management/migrations/0002_alter_project_description_alter_project_id_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 +# Generated by Django 5.1.2 on 2024-12-06 06:47 import access.models import django.db.models.deletion @@ -9,7 +9,6 @@ class Migration(migrations.Migration): dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), ('project_management', '0001_initial'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -48,7 +47,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='project', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='projectmilestone', @@ -68,7 +67,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='projectmilestone', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='projectstate', @@ -83,7 +82,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='projectstate', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), migrations.AlterField( model_name='projecttype', @@ -98,6 +97,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='projecttype', name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), ), ] diff --git a/app/project_management/migrations/0003_alter_project_organization_and_more.py b/app/project_management/migrations/0003_alter_project_organization_and_more.py deleted file mode 100644 index 4f92f2807..000000000 --- a/app/project_management/migrations/0003_alter_project_organization_and_more.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:41 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_alter_team_organization'), - ('project_management', '0002_alter_project_description_alter_project_id_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='project', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='projectmilestone', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='projectstate', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='projecttype', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - ] diff --git a/app/settings/migrations/0009_usersettings_timezone_alter_usersettings_user.py b/app/settings/migrations/0005_alter_appsettings_options_alter_externallink_options_and_more.py similarity index 83% rename from app/settings/migrations/0009_usersettings_timezone_alter_usersettings_user.py rename to app/settings/migrations/0005_alter_appsettings_options_alter_externallink_options_and_more.py index 63e566e53..bed862dc1 100644 --- a/app/settings/migrations/0009_usersettings_timezone_alter_usersettings_user.py +++ b/app/settings/migrations/0005_alter_appsettings_options_alter_externallink_options_and_more.py @@ -1,5 +1,6 @@ -# Generated by Django 5.1.2 on 2024-11-11 20:50 +# Generated by Django 5.1.2 on 2024-12-06 06:47 +import access.models import django.db.models.deletion from django.conf import settings from django.db import migrations, models @@ -8,16 +9,99 @@ class Migration(migrations.Migration): dependencies = [ - ('settings', '0008_alter_usersettings_options'), + ('access', '0002_alter_organization_options_alter_team_options_and_more'), + ('settings', '0004_externallink_cluster'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ + migrations.AlterModelOptions( + name='appsettings', + options={'ordering': ['owner_organization'], 'verbose_name': 'App Settings', 'verbose_name_plural': 'App Settings'}, + ), + migrations.AlterModelOptions( + name='externallink', + options={'ordering': ['name', 'organization'], 'verbose_name': 'External Link', 'verbose_name_plural': 'External Links'}, + ), + migrations.AlterModelOptions( + name='usersettings', + options={'ordering': ['user'], 'verbose_name': 'User Settings', 'verbose_name_plural': 'User Settings'}, + ), migrations.AddField( model_name='usersettings', name='timezone', field=models.CharField(choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Asmera', 'Africa/Asmera'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Timbuktu', 'Africa/Timbuktu'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/ComodRivadavia', 'America/Argentina/ComodRivadavia'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Atka', 'America/Atka'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Buenos_Aires', 'America/Buenos_Aires'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Catamarca', 'America/Catamarca'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Ciudad_Juarez', 'America/Ciudad_Juarez'), ('America/Coral_Harbour', 'America/Coral_Harbour'), ('America/Cordoba', 'America/Cordoba'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Ensenada', 'America/Ensenada'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fort_Wayne', 'America/Fort_Wayne'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Indianapolis', 'America/Indianapolis'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Jujuy', 'America/Jujuy'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Knox_IN', 'America/Knox_IN'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Louisville', 'America/Louisville'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Mendoza', 'America/Mendoza'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montreal', 'America/Montreal'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Nuuk', 'America/Nuuk'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Acre', 'America/Porto_Acre'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Rosario', 'America/Rosario'), ('America/Santa_Isabel', 'America/Santa_Isabel'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Shiprock', 'America/Shiprock'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Virgin', 'America/Virgin'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/South_Pole', 'Antarctica/South_Pole'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Ashkhabad', 'Asia/Ashkhabad'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Calcutta', 'Asia/Calcutta'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Chongqing', 'Asia/Chongqing'), ('Asia/Chungking', 'Asia/Chungking'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Dacca', 'Asia/Dacca'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Harbin', 'Asia/Harbin'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Istanbul', 'Asia/Istanbul'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kashgar', 'Asia/Kashgar'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Katmandu', 'Asia/Katmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macao', 'Asia/Macao'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Rangoon', 'Asia/Rangoon'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Saigon', 'Asia/Saigon'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Tel_Aviv', 'Asia/Tel_Aviv'), ('Asia/Thimbu', 'Asia/Thimbu'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ujung_Pandang', 'Asia/Ujung_Pandang'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Ulan_Bator', 'Asia/Ulan_Bator'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faeroe', 'Atlantic/Faeroe'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Jan_Mayen', 'Atlantic/Jan_Mayen'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/ACT', 'Australia/ACT'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Canberra', 'Australia/Canberra'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/LHI', 'Australia/LHI'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/NSW', 'Australia/NSW'), ('Australia/North', 'Australia/North'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Queensland', 'Australia/Queensland'), ('Australia/South', 'Australia/South'), ('Australia/Sydney', 'Australia/Sydney'), ('Australia/Tasmania', 'Australia/Tasmania'), ('Australia/Victoria', 'Australia/Victoria'), ('Australia/West', 'Australia/West'), ('Australia/Yancowinna', 'Australia/Yancowinna'), ('Brazil/Acre', 'Brazil/Acre'), ('Brazil/DeNoronha', 'Brazil/DeNoronha'), ('Brazil/East', 'Brazil/East'), ('Brazil/West', 'Brazil/West'), ('CET', 'CET'), ('CST6CDT', 'CST6CDT'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Canada/Saskatchewan', 'Canada/Saskatchewan'), ('Canada/Yukon', 'Canada/Yukon'), ('Chile/Continental', 'Chile/Continental'), ('Chile/EasterIsland', 'Chile/EasterIsland'), ('Cuba', 'Cuba'), ('EET', 'EET'), ('EST', 'EST'), ('EST5EDT', 'EST5EDT'), ('Egypt', 'Egypt'), ('Eire', 'Eire'), ('Etc/GMT', 'Etc/GMT'), ('Etc/GMT+0', 'Etc/GMT+0'), ('Etc/GMT+1', 'Etc/GMT+1'), ('Etc/GMT+10', 'Etc/GMT+10'), ('Etc/GMT+11', 'Etc/GMT+11'), ('Etc/GMT+12', 'Etc/GMT+12'), ('Etc/GMT+2', 'Etc/GMT+2'), ('Etc/GMT+3', 'Etc/GMT+3'), ('Etc/GMT+4', 'Etc/GMT+4'), ('Etc/GMT+5', 'Etc/GMT+5'), ('Etc/GMT+6', 'Etc/GMT+6'), ('Etc/GMT+7', 'Etc/GMT+7'), ('Etc/GMT+8', 'Etc/GMT+8'), ('Etc/GMT+9', 'Etc/GMT+9'), ('Etc/GMT-0', 'Etc/GMT-0'), ('Etc/GMT-1', 'Etc/GMT-1'), ('Etc/GMT-10', 'Etc/GMT-10'), ('Etc/GMT-11', 'Etc/GMT-11'), ('Etc/GMT-12', 'Etc/GMT-12'), ('Etc/GMT-13', 'Etc/GMT-13'), ('Etc/GMT-14', 'Etc/GMT-14'), ('Etc/GMT-2', 'Etc/GMT-2'), ('Etc/GMT-3', 'Etc/GMT-3'), ('Etc/GMT-4', 'Etc/GMT-4'), ('Etc/GMT-5', 'Etc/GMT-5'), ('Etc/GMT-6', 'Etc/GMT-6'), ('Etc/GMT-7', 'Etc/GMT-7'), ('Etc/GMT-8', 'Etc/GMT-8'), ('Etc/GMT-9', 'Etc/GMT-9'), ('Etc/GMT0', 'Etc/GMT0'), ('Etc/Greenwich', 'Etc/Greenwich'), ('Etc/UCT', 'Etc/UCT'), ('Etc/UTC', 'Etc/UTC'), ('Etc/Universal', 'Etc/Universal'), ('Etc/Zulu', 'Etc/Zulu'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belfast', 'Europe/Belfast'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Kyiv', 'Europe/Kyiv'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Nicosia', 'Europe/Nicosia'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Tiraspol', 'Europe/Tiraspol'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('Factory', 'Factory'), ('GB', 'GB'), ('GB-Eire', 'GB-Eire'), ('GMT', 'GMT'), ('GMT+0', 'GMT+0'), ('GMT-0', 'GMT-0'), ('GMT0', 'GMT0'), ('Greenwich', 'Greenwich'), ('HST', 'HST'), ('Hongkong', 'Hongkong'), ('Iceland', 'Iceland'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Iran', 'Iran'), ('Israel', 'Israel'), ('Jamaica', 'Jamaica'), ('Japan', 'Japan'), ('Kwajalein', 'Kwajalein'), ('Libya', 'Libya'), ('MET', 'MET'), ('MST', 'MST'), ('MST7MDT', 'MST7MDT'), ('Mexico/BajaNorte', 'Mexico/BajaNorte'), ('Mexico/BajaSur', 'Mexico/BajaSur'), ('Mexico/General', 'Mexico/General'), ('NZ', 'NZ'), ('NZ-CHAT', 'NZ-CHAT'), ('Navajo', 'Navajo'), ('PRC', 'PRC'), ('PST8PDT', 'PST8PDT'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Johnston', 'Pacific/Johnston'), ('Pacific/Kanton', 'Pacific/Kanton'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Ponape', 'Pacific/Ponape'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Samoa', 'Pacific/Samoa'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Truk', 'Pacific/Truk'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('Pacific/Yap', 'Pacific/Yap'), ('Poland', 'Poland'), ('Portugal', 'Portugal'), ('ROC', 'ROC'), ('ROK', 'ROK'), ('Singapore', 'Singapore'), ('Turkey', 'Turkey'), ('UCT', 'UCT'), ('US/Alaska', 'US/Alaska'), ('US/Aleutian', 'US/Aleutian'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/East-Indiana', 'US/East-Indiana'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Indiana-Starke', 'US/Indiana-Starke'), ('US/Michigan', 'US/Michigan'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('US/Samoa', 'US/Samoa'), ('UTC', 'UTC'), ('Universal', 'Universal'), ('W-SU', 'W-SU'), ('WET', 'WET'), ('Zulu', 'Zulu'), ('localtime', 'localtime')], default='UTC', help_text='What Timezone do you wish to have times displayed in', max_length=32, verbose_name='Your Timezone'), ), + migrations.AlterField( + model_name='appsettings', + name='device_model_is_global', + field=models.BooleanField(default=False, help_text='Should Device Models be global', verbose_name='Global Device Models'), + ), + migrations.AlterField( + model_name='appsettings', + name='device_type_is_global', + field=models.BooleanField(default=False, help_text='Should Device Types be global', verbose_name='Global Device Types'), + ), + migrations.AlterField( + model_name='appsettings', + name='global_organization', + field=models.ForeignKey(blank=True, default=None, help_text='Organization global items will be created in', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='global_organization', to='access.organization', verbose_name='Global Organization'), + ), + migrations.AlterField( + model_name='appsettings', + name='id', + field=models.AutoField(help_text='Id of this setting', primary_key=True, serialize=False, unique=True, verbose_name='ID'), + ), + migrations.AlterField( + model_name='appsettings', + name='manufacturer_is_global', + field=models.BooleanField(default=False, help_text='Should Manufacturers / Publishers be global', verbose_name='Global Manufacturers / Publishers'), + ), + migrations.AlterField( + model_name='appsettings', + name='owner_organization', + field=models.ForeignKey(blank=True, default=None, help_text='Organization the settings belong to', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='owner_organization', to='access.organization'), + ), + migrations.AlterField( + model_name='appsettings', + name='software_categories_is_global', + field=models.BooleanField(default=False, help_text='Should Software be global', verbose_name='Global Software Categories'), + ), + migrations.AlterField( + model_name='appsettings', + name='software_is_global', + field=models.BooleanField(default=False, help_text='Should Software be global', verbose_name='Global Software'), + ), + migrations.AlterField( + model_name='externallink', + name='id', + field=models.AutoField(help_text='ID for this external link', primary_key=True, serialize=False, unique=True, verbose_name='ID'), + ), + migrations.AlterField( + model_name='externallink', + name='is_global', + field=models.BooleanField(default=False, help_text='Is this a global object?', verbose_name='Global Object'), + ), + migrations.AlterField( + model_name='externallink', + name='model_notes', + field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'), + ), + migrations.AlterField( + model_name='externallink', + name='organization', + field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), + ), + migrations.AlterField( + model_name='usersettings', + name='default_organization', + field=models.ForeignKey(blank=True, default=None, help_text='Users default Organization', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='access.organization', verbose_name='Default Organization'), + ), + migrations.AlterField( + model_name='usersettings', + name='id', + field=models.AutoField(help_text='ID for this user Setting', primary_key=True, serialize=False, unique=True, verbose_name='ID'), + ), migrations.AlterField( model_name='usersettings', name='user', diff --git a/app/settings/migrations/0005_alter_externallink_options.py b/app/settings/migrations/0005_alter_externallink_options.py deleted file mode 100644 index 15f9147a2..000000000 --- a/app/settings/migrations/0005_alter_externallink_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 09:19 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('settings', '0004_externallink_cluster'), - ] - - operations = [ - migrations.AlterModelOptions( - name='externallink', - options={'ordering': ['name', 'organization'], 'verbose_name': 'External Link', 'verbose_name_plural': 'External Links'}, - ), - ] diff --git a/app/settings/migrations/0006_alter_appsettings_device_model_is_global_and_more.py b/app/settings/migrations/0006_alter_appsettings_device_model_is_global_and_more.py deleted file mode 100644 index 936139cf4..000000000 --- a/app/settings/migrations/0006_alter_appsettings_device_model_is_global_and_more.py +++ /dev/null @@ -1,93 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-13 15:27 - -import access.models -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0003_alter_organization_id_alter_organization_manager_and_more'), - ('settings', '0005_alter_externallink_options'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterField( - model_name='appsettings', - name='device_model_is_global', - field=models.BooleanField(default=False, help_text='Should Device Models be global', verbose_name='Global Device Models'), - ), - migrations.AlterField( - model_name='appsettings', - name='device_type_is_global', - field=models.BooleanField(default=False, help_text='Should Device Types be global', verbose_name='Global Device Types'), - ), - migrations.AlterField( - model_name='appsettings', - name='global_organization', - field=models.ForeignKey(blank=True, default=None, help_text='Organization global items will be created in', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='global_organization', to='access.organization', verbose_name='Global Organization'), - ), - migrations.AlterField( - model_name='appsettings', - name='id', - field=models.AutoField(help_text='Id of this setting', primary_key=True, serialize=False, unique=True, verbose_name='ID'), - ), - migrations.AlterField( - model_name='appsettings', - name='manufacturer_is_global', - field=models.BooleanField(default=False, help_text='Should Manufacturers / Publishers be global', verbose_name='Global Manufacturers / Publishers'), - ), - migrations.AlterField( - model_name='appsettings', - name='owner_organization', - field=models.ForeignKey(blank=True, default=None, help_text='Organization the settings belong to', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='owner_organization', to='access.organization'), - ), - migrations.AlterField( - model_name='appsettings', - name='software_categories_is_global', - field=models.BooleanField(default=False, help_text='Should Software be global', verbose_name='Global Software Categories'), - ), - migrations.AlterField( - model_name='appsettings', - name='software_is_global', - field=models.BooleanField(default=False, help_text='Should Software be global', verbose_name='Global Software'), - ), - migrations.AlterField( - model_name='externallink', - name='id', - field=models.AutoField(help_text='ID for this external link', primary_key=True, serialize=False, unique=True, verbose_name='ID'), - ), - migrations.AlterField( - model_name='externallink', - name='is_global', - field=models.BooleanField(default=False, help_text='Is this a global object?', verbose_name='Global Object'), - ), - migrations.AlterField( - model_name='externallink', - name='model_notes', - field=models.TextField(blank=True, default=None, help_text='Tid bits of information', null=True, verbose_name='Notes'), - ), - migrations.AlterField( - model_name='externallink', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - migrations.AlterField( - model_name='usersettings', - name='default_organization', - field=models.ForeignKey(blank=True, default=None, help_text='Users default Organization', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='access.organization', verbose_name='Default Organization'), - ), - migrations.AlterField( - model_name='usersettings', - name='id', - field=models.AutoField(help_text='ID for this user Setting', primary_key=True, serialize=False, unique=True, verbose_name='ID'), - ), - migrations.AlterField( - model_name='usersettings', - name='user', - field=models.ForeignKey(help_text='User this Setting belongs to', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='User'), - ), - ] diff --git a/app/settings/migrations/0007_alter_appsettings_options.py b/app/settings/migrations/0007_alter_appsettings_options.py deleted file mode 100644 index d2ddf1638..000000000 --- a/app/settings/migrations/0007_alter_appsettings_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-24 02:13 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('settings', '0006_alter_appsettings_device_model_is_global_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='appsettings', - options={'ordering': ['owner_organization'], 'verbose_name': 'App Settings', 'verbose_name_plural': 'App Settings'}, - ), - ] diff --git a/app/settings/migrations/0008_alter_usersettings_options.py b/app/settings/migrations/0008_alter_usersettings_options.py deleted file mode 100644 index b4a73d376..000000000 --- a/app/settings/migrations/0008_alter_usersettings_options.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 5.1.2 on 2024-10-24 02:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('settings', '0007_alter_appsettings_options'), - ] - - operations = [ - migrations.AlterModelOptions( - name='usersettings', - options={'ordering': ['user'], 'verbose_name': 'User Settings', 'verbose_name_plural': 'User Settings'}, - ), - ] diff --git a/app/settings/migrations/0010_alter_externallink_organization.py b/app/settings/migrations/0010_alter_externallink_organization.py deleted file mode 100644 index 3e3761834..000000000 --- a/app/settings/migrations/0010_alter_externallink_organization.py +++ /dev/null @@ -1,21 +0,0 @@ -# Generated by Django 5.1.2 on 2024-11-20 02:41 - -import access.models -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('access', '0006_alter_team_organization'), - ('settings', '0009_usersettings_timezone_alter_usersettings_user'), - ] - - operations = [ - migrations.AlterField( - model_name='externallink', - name='organization', - field=models.ForeignKey(help_text='Organization this belongs to', on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists], verbose_name='Organization'), - ), - ] From 18bb2909a540bde00060c48dd4d9abc558913877 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 16:34:13 +0930 Subject: [PATCH 73/84] docs: update release notes re migration squash ref: #408 #417 --- Release-Notes.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Release-Notes.md b/Release-Notes.md index 23bc4de62..4eb86e703 100644 --- a/Release-Notes.md +++ b/Release-Notes.md @@ -1,3 +1,11 @@ +## Next Release + +- 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. + +!!! Note + If you require the previously squashed migrations for what ever reason. Clone the repo and go to commit 17f47040d6737905a1769eee5c45d9d15339fdbf, which is the commit prior to the squashing which is commit ca2da06d2cd393cabb7e172ad47dfb2dd922d952. + + ## Version 1.4.0 API redesign in preparation for moving the UI out of centurion to it's [own project](https://github.com/nofusscomputing/centurion_erp_ui). This release introduces a **Feature freeze** to the current UI. Only bug fixes will be done for the current UI. From e158f49a218c394cae993496ae1a10bbc45cb9e3 Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 16:40:58 +0930 Subject: [PATCH 74/84] refactor(itam): set deviceoperatingsystem model, device field to be type `onetoone` ref: #417 --- ...0006_alter_deviceoperatingsystem_device.py | 19 +++++++++++++++++++ app/itam/models/device.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 app/itam/migrations/0006_alter_deviceoperatingsystem_device.py diff --git a/app/itam/migrations/0006_alter_deviceoperatingsystem_device.py b/app/itam/migrations/0006_alter_deviceoperatingsystem_device.py new file mode 100644 index 000000000..d03a464a4 --- /dev/null +++ b/app/itam/migrations/0006_alter_deviceoperatingsystem_device.py @@ -0,0 +1,19 @@ +# Generated by Django 5.1.2 on 2024-12-06 07:09 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('itam', '0005_alter_device_options_alter_devicemodel_options_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='deviceoperatingsystem', + name='device', + field=models.OneToOneField(help_text='Device for the Operating System', on_delete=django.db.models.deletion.CASCADE, to='itam.device', verbose_name='Device'), + ), + ] diff --git a/app/itam/models/device.py b/app/itam/models/device.py index 135f119d0..844cb33b7 100644 --- a/app/itam/models/device.py +++ b/app/itam/models/device.py @@ -660,7 +660,7 @@ class Meta: verbose_name_plural = 'Device Operating Systems' - device = models.ForeignKey( + device = models.OneToOneField( Device, blank = False, help_text = 'Device for the Operating System', From 41ffe5b3bc2fd57691717784671ecae3b0e458af Mon Sep 17 00:00:00 2001 From: Jon Date: Fri, 6 Dec 2024 17:06:38 +0930 Subject: [PATCH 75/84] refactor(access): Settings must be an available permissions when setting team permissions ref: #408 #417 --- app/access/functions/permissions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/access/functions/permissions.py b/app/access/functions/permissions.py index dbecedf0c..bf98aa7ea 100644 --- a/app/access/functions/permissions.py +++ b/app/access/functions/permissions.py @@ -23,8 +23,6 @@ def permission_queryset(): 'chordcounter', 'comment', 'groupresult', - 'organization' - 'settings', 'usersettings', ] From b9e8caecc10f0440f5908d760744f97944611c85 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 8 Dec 2024 17:30:11 +0930 Subject: [PATCH 76/84] test(api): Nav menu permission checks ref: #409 #418 --- app/api/tests/unit/test_navigation_menu.py | 898 +++++++++++++++++++++ 1 file changed, 898 insertions(+) create mode 100644 app/api/tests/unit/test_navigation_menu.py 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..f65551672 --- /dev/null +++ b/app/api/tests/unit/test_navigation_menu.py @@ -0,0 +1,898 @@ +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' + } + ], + 'config_management': [ + { + 'content_model': 'configgroups', + 'permission_model': 'configgroups' + } + ], + 'core': [ + { + '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' + } + ], + 'django_celery_results': [ + { + 'content_model': 'taskresult', + 'permission_model': 'taskresult' + } + ], + 'itam': [ + { + 'content_model': 'device', + 'permission_model': 'device' + }, + { + 'content_model': 'operatingsystem', + 'permission_model': 'operatingsystem' + }, + { + 'content_model': 'software', + 'permission_model': 'software' + } + ], + 'itim': [ + { + 'content_model': 'cluster', + 'permission_model': 'cluster' + }, + { + 'content_model': 'service', + 'permission_model': 'service' + } + ], + 'project_management': [ + { + 'content_model': 'project', + 'permission_model': 'project' + } + ], + } + + + # 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 From 10285bedef796da59240d252f4364933de42ce88 Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 8 Dec 2024 18:38:16 +0930 Subject: [PATCH 77/84] feat(api): Add nav menu permission checks for settings ref: #409 #418 --- app/api/react_ui_metadata.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) 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('.') From 03c39d2e2f882fa4751784014e3c5a74a3d03dba Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 8 Dec 2024 18:38:38 +0930 Subject: [PATCH 78/84] test(api): Nav menu permission checks for settings ref: #418 closes #409 --- app/api/tests/unit/test_navigation_menu.py | 659 +++++++++++++++++++++ 1 file changed, 659 insertions(+) diff --git a/app/api/tests/unit/test_navigation_menu.py b/app/api/tests/unit/test_navigation_menu.py index f65551672..7ac7a91d7 100644 --- a/app/api/tests/unit/test_navigation_menu.py +++ b/app/api/tests/unit/test_navigation_menu.py @@ -30,6 +30,10 @@ def setUpTestData(self): { 'content_model': 'knowledgebase', 'permission_model': 'knowledgebase' + }, + { + 'content_model': 'knowledgebasecategory', + 'permission_model': 'knowledgebasecategory' } ], 'config_management': [ @@ -39,6 +43,10 @@ def setUpTestData(self): } ], 'core': [ + { + 'content_model': 'manufacturer', + 'permission_model': 'manufacturer' + }, { 'content_model': 'ticket', 'permission_model': 'ticket_change' @@ -54,6 +62,14 @@ def setUpTestData(self): { 'content_model': 'ticket', 'permission_model': 'ticket_request' + }, + { + 'content_model': 'ticketcategory', + 'permission_model': 'ticketcategory' + }, + { + 'content_model': 'ticketcommentcategory', + 'permission_model': 'ticketcommentcategory' } ], 'django_celery_results': [ @@ -67,6 +83,14 @@ def setUpTestData(self): 'content_model': 'device', 'permission_model': 'device' }, + { + 'content_model': 'devicemodel', + 'permission_model': 'devicemodel' + }, + { + 'content_model': 'devicetype', + 'permission_model': 'devicetype' + }, { 'content_model': 'operatingsystem', 'permission_model': 'operatingsystem' @@ -74,6 +98,10 @@ def setUpTestData(self): { 'content_model': 'software', 'permission_model': 'software' + }, + { + 'content_model': 'softwarecategory', + 'permission_model': 'softwarecategory' } ], 'itim': [ @@ -81,6 +109,10 @@ def setUpTestData(self): 'content_model': 'cluster', 'permission_model': 'cluster' }, + { + 'content_model': 'clustertype', + 'permission_model': 'clustertype' + }, { 'content_model': 'service', 'permission_model': 'service' @@ -90,8 +122,22 @@ def setUpTestData(self): { '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' + } + ] } @@ -896,3 +942,616 @@ def test_navigation_menu_visible_project_management_project_no_additional_exist( 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 From b51ce7d513f03d6b5a991532e20eb7a8eba4982a Mon Sep 17 00:00:00 2001 From: Jon Date: Sun, 8 Dec 2024 18:43:52 +0930 Subject: [PATCH 79/84] docs(api): add test info for nav menu ref: #409 #418 --- docs/projects/centurion_erp/development/views.md | 2 ++ 1 file changed, 2 insertions(+) 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 From c14ee4c4be8f5fba60e673cfbec91e487ae08b9c Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 9 Dec 2024 22:29:57 +0930 Subject: [PATCH 80/84] feat(api): If global organization defined, filter from ALL organization fields This field is not intended to be selectable ref: #418 closes #406 --- app/access/serializers/organization.py | 6 ++-- app/api/serializers/common.py | 39 +++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 4 deletions(-) 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/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 From 5335758c70e264b5fdada66497d25ca9231274fb Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 9 Dec 2024 23:21:36 +0930 Subject: [PATCH 81/84] docs(admin): Include new UI ref: #408 #418 --- .../administration/installation.md | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) 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" From ed6cdaef8b537ed54c75cfe37a3509b09995ffdf Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 9 Dec 2024 23:26:42 +0930 Subject: [PATCH 82/84] docs(pr): add migrations task ref: #408 #418 --- .github/pull_request_template.md | 3 +++ 1 file changed, 3 insertions(+) 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 @@ + - [ ] **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/)._ From c03b7e7d49a866a313d5586ad72a252d63d1df17 Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 9 Dec 2024 23:28:44 +0930 Subject: [PATCH 83/84] feat(python): update django 5.1.2 -> 5.1.4 ref: #408 #418 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 8332b31a1a841dce4f3bd232e0f26af73117e20f Mon Sep 17 00:00:00 2001 From: Jon Date: Mon, 9 Dec 2024 23:43:58 +0930 Subject: [PATCH 84/84] docs(release): added next release version ref: #418 --- Release-Notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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.