From 9cf0ac03d1e2ebba289b1fdead4b6a99b1f8e72e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulc?= Date: Thu, 7 Nov 2024 13:39:23 +0100 Subject: [PATCH 01/10] Allow updating dchosts via dchost-list endpoint --- src/ralph/assets/api/views.py | 26 +++++++++++++++++++++++--- src/ralph/assets/tests/test_api.py | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/ralph/assets/api/views.py b/src/ralph/assets/api/views.py index a1109c5df7..213a5a4886 100644 --- a/src/ralph/assets/api/views.py +++ b/src/ralph/assets/api/views.py @@ -2,6 +2,7 @@ import django_filters from django.db.models import Prefetch from rest_framework.exceptions import ValidationError +from rest_framework.permissions import SAFE_METHODS from ralph.api import RalphAPIViewSet from ralph.api.filters import BooleanFilter @@ -10,9 +11,11 @@ from ralph.assets.api import serializers from ralph.assets.api.filters import NetworkableObjectFilters from ralph.assets.models import BaseObject +from ralph.data_center.models import Cluster, DataCenterAsset from ralph.licences.api import BaseObjectLicenceViewSet from ralph.licences.models import BaseObjectLicence from ralph.networks.models import IPAddress +from ralph.virtual.models import CloudHost, VirtualServer class BusinessSegmentViewSet(RalphAPIViewSet): @@ -255,10 +258,10 @@ class Meta(NetworkableObjectFilters.Meta): # TODO: move to data_center and use DCHost proxy model class DCHostViewSet(BaseObjectViewSetMixin, RalphAPIViewSet): queryset = ( - BaseObject.polymorphic_objects + BaseObject.polymorphic_objects.dc_hosts() ) serializer_class = serializers.DCHostSerializer - http_method_names = ["get", "options", "head"] + http_method_names = ["get", "options", "head", "patch", "post"] filter_fields = [ "id", "service_env", @@ -296,9 +299,26 @@ class DCHostViewSet(BaseObjectViewSetMixin, RalphAPIViewSet): } additional_filter_class = DCHostFilterSet + def get_serializer_class(self, *args, **kwargs): + if self.request.method not in SAFE_METHODS: + obj_ = self.get_object() + if isinstance(obj_, VirtualServer): + from ralph.virtual.api import VirtualServerSaveSerializer + return VirtualServerSaveSerializer + elif isinstance(obj_, DataCenterAsset): + from ralph.data_center.api.serializers import DataCenterAssetSaveSerializer + return DataCenterAssetSaveSerializer + elif isinstance(obj_, CloudHost): + from ralph.virtual.api import SaveCloudHostSerializer + return SaveCloudHostSerializer + elif isinstance(obj_, Cluster): + from ralph.data_center.api.serializers import ClusterSerializer + return ClusterSerializer + return serializers.DCHostSerializer + def get_queryset(self): return ( - self.queryset.dc_hosts() + self.queryset .select_related(*self.select_related) .polymorphic_select_related(Cluster=['type']) .polymorphic_prefetch_related( diff --git a/src/ralph/assets/tests/test_api.py b/src/ralph/assets/tests/test_api.py index cb3cd229fd..b30b507be1 100644 --- a/src/ralph/assets/tests/test_api.py +++ b/src/ralph/assets/tests/test_api.py @@ -1089,6 +1089,31 @@ def test_filter_by_env_name(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 1) + def test_patch_dchost_virtual_server(self): + new_hypervisor = DataCenterAssetFullFactory() + url = reverse('dchost-detail', args=(self.virtual.id,)) + data = { + 'hostname': 'new-hostname', + 'hypervisor': new_hypervisor.id, + } + response = self.client.patch(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.virtual.refresh_from_db() + self.assertEqual(self.virtual.hostname, 'new-hostname') + self.assertEqual(self.virtual.parent.id, new_hypervisor.id) + + def test_patch_dchost_cloudhost(self): + new_hypervisor = DataCenterAssetFullFactory() + url = reverse('dchost-detail', args=(self.cloud_host.id,)) + data = { + 'hostname': 'new-hostname', + 'hypervisor': new_hypervisor.id, + } + response = self.client.patch(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.cloud_host.refresh_from_db() + self.assertEqual(self.cloud_host.hostname, 'new-hostname') + self.assertEqual(self.cloud_host.hypervisor.id, new_hypervisor.id) class ConfigurationModuleAPITests(RalphAPITestCase): def setUp(self): From 3e9a1c67bb15776b17e2a9a5a6fe2fff7f4816f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulc?= Date: Thu, 7 Nov 2024 14:06:33 +0100 Subject: [PATCH 02/10] Fix browsable api --- src/ralph/assets/api/views.py | 37 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/src/ralph/assets/api/views.py b/src/ralph/assets/api/views.py index 213a5a4886..cd90d49a5b 100644 --- a/src/ralph/assets/api/views.py +++ b/src/ralph/assets/api/views.py @@ -11,7 +11,8 @@ from ralph.assets.api import serializers from ralph.assets.api.filters import NetworkableObjectFilters from ralph.assets.models import BaseObject -from ralph.data_center.models import Cluster, DataCenterAsset +from ralph.data_center.models import Cluster, DataCenterAsset, DCHost +from ralph.lib.api.utils import renderer_classes_without_form from ralph.licences.api import BaseObjectLicenceViewSet from ralph.licences.models import BaseObjectLicence from ralph.networks.models import IPAddress @@ -258,9 +259,10 @@ class Meta(NetworkableObjectFilters.Meta): # TODO: move to data_center and use DCHost proxy model class DCHostViewSet(BaseObjectViewSetMixin, RalphAPIViewSet): queryset = ( - BaseObject.polymorphic_objects.dc_hosts() + BaseObject.polymorphic_objects ) serializer_class = serializers.DCHostSerializer + renderer_classes = renderer_classes_without_form(RalphAPIViewSet.renderer_classes) http_method_names = ["get", "options", "head", "patch", "post"] filter_fields = [ "id", @@ -301,24 +303,27 @@ class DCHostViewSet(BaseObjectViewSetMixin, RalphAPIViewSet): def get_serializer_class(self, *args, **kwargs): if self.request.method not in SAFE_METHODS: - obj_ = self.get_object() - if isinstance(obj_, VirtualServer): - from ralph.virtual.api import VirtualServerSaveSerializer - return VirtualServerSaveSerializer - elif isinstance(obj_, DataCenterAsset): - from ralph.data_center.api.serializers import DataCenterAssetSaveSerializer - return DataCenterAssetSaveSerializer - elif isinstance(obj_, CloudHost): - from ralph.virtual.api import SaveCloudHostSerializer - return SaveCloudHostSerializer - elif isinstance(obj_, Cluster): - from ralph.data_center.api.serializers import ClusterSerializer - return ClusterSerializer + try: + obj_ = self.get_object() + if isinstance(obj_, VirtualServer): + from ralph.virtual.api import VirtualServerSaveSerializer + return VirtualServerSaveSerializer + elif isinstance(obj_, DataCenterAsset): + from ralph.data_center.api.serializers import DataCenterAssetSaveSerializer + return DataCenterAssetSaveSerializer + elif isinstance(obj_, CloudHost): + from ralph.virtual.api import SaveCloudHostSerializer + return SaveCloudHostSerializer + elif isinstance(obj_, Cluster): + from ralph.data_center.api.serializers import ClusterSerializer + return ClusterSerializer + except AssertionError: # for some reason when opening browsable api this raises exception + pass return serializers.DCHostSerializer def get_queryset(self): return ( - self.queryset + self.queryset.dc_hosts() .select_related(*self.select_related) .polymorphic_select_related(Cluster=['type']) .polymorphic_prefetch_related( From b1a59b1f2ed49c7c5cceac25df7d92662554edec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulc?= Date: Thu, 7 Nov 2024 14:17:33 +0100 Subject: [PATCH 03/10] Fix flake --- src/ralph/assets/api/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ralph/assets/api/views.py b/src/ralph/assets/api/views.py index cd90d49a5b..4aa0ac92d4 100644 --- a/src/ralph/assets/api/views.py +++ b/src/ralph/assets/api/views.py @@ -11,7 +11,7 @@ from ralph.assets.api import serializers from ralph.assets.api.filters import NetworkableObjectFilters from ralph.assets.models import BaseObject -from ralph.data_center.models import Cluster, DataCenterAsset, DCHost +from ralph.data_center.models import Cluster, DataCenterAsset from ralph.lib.api.utils import renderer_classes_without_form from ralph.licences.api import BaseObjectLicenceViewSet from ralph.licences.models import BaseObjectLicence @@ -317,7 +317,7 @@ def get_serializer_class(self, *args, **kwargs): elif isinstance(obj_, Cluster): from ralph.data_center.api.serializers import ClusterSerializer return ClusterSerializer - except AssertionError: # for some reason when opening browsable api this raises exception + except AssertionError: # for some reason when opening browsable api this raises pass return serializers.DCHostSerializer From 830dc58c7876db6e24c9bb96119f997ce2c162ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulc?= Date: Fri, 8 Nov 2024 11:09:50 +0100 Subject: [PATCH 04/10] Add 404 if no serializer found for dc-hosts endpoint --- src/ralph/assets/api/views.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ralph/assets/api/views.py b/src/ralph/assets/api/views.py index 4aa0ac92d4..b0c25e25a2 100644 --- a/src/ralph/assets/api/views.py +++ b/src/ralph/assets/api/views.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- import django_filters from django.db.models import Prefetch -from rest_framework.exceptions import ValidationError +from rest_framework.exceptions import NotFound, ValidationError from rest_framework.permissions import SAFE_METHODS from ralph.api import RalphAPIViewSet @@ -317,6 +317,8 @@ def get_serializer_class(self, *args, **kwargs): elif isinstance(obj_, Cluster): from ralph.data_center.api.serializers import ClusterSerializer return ClusterSerializer + else: + raise NotFound() except AssertionError: # for some reason when opening browsable api this raises pass return serializers.DCHostSerializer From 530cac3af8c75ce68fb0f48fac09014cd3a9dc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulc?= Date: Fri, 8 Nov 2024 12:13:35 +0100 Subject: [PATCH 05/10] Updated changelog for 20241108.1 version. --- debian/changelog | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/debian/changelog b/debian/changelog index 03d60072cb..9e9b638351 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,12 @@ +ralph (20241108.1) bionic; urgency=medium + + * Allow updating dchosts via dchost-list endpoint + * Fix browsable api + * Fix flake + * Add 404 if no serializer found for dc-hosts endpoint + + -- Paweł Szulc Fri, 08 Nov 2024 11:09:13 +0000 + ralph (20241104.2) bionic; urgency=medium [ awieckowski ] From 06abac1f43a63eb97c1d8ee45975507bfcfbd6e3 Mon Sep 17 00:00:00 2001 From: Olga Matyla <73503512+matyldv@users.noreply.github.com> Date: Fri, 15 Nov 2024 13:53:27 +0100 Subject: [PATCH 06/10] Add property_of to invoice report (#3864) --- src/ralph/assets/invoice_report.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ralph/assets/invoice_report.py b/src/ralph/assets/invoice_report.py index ee828377d1..bcfbca8c90 100644 --- a/src/ralph/assets/invoice_report.py +++ b/src/ralph/assets/invoice_report.py @@ -27,7 +27,7 @@ class InvoiceReportMixin(object): _invoice_report_select_related = [] _invoice_report_common_fields = [ - 'invoice_no', 'invoice_date', 'provider', 'price_currency' + 'invoice_no', 'invoice_date', 'provider', 'price_currency', 'property_of__name' ] _price_field = 'price' _invoice_report_name = 'invoice' @@ -122,6 +122,7 @@ def _get_report_data(self, request, queryset): 'datetime': datetime.datetime.now().strftime( self._invoice_report_datetime_format ), + 'property_of': first_item.property_of.name, }, 'items': list(map(self._parse_item, queryset)), 'sum_price': str( From a0be4ba69d8438a6ec789497457393f8b1db8374 Mon Sep 17 00:00:00 2001 From: Olga Matyla <73503512+matyldv@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:29:21 +0100 Subject: [PATCH 07/10] Fix selected count (#3865) * Fix selected count for actions * Add default --- src/ralph/admin/templates/admin/actions.html | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ralph/admin/templates/admin/actions.html b/src/ralph/admin/templates/admin/actions.html index a52fcc0f9c..90f4d87299 100644 --- a/src/ralph/admin/templates/admin/actions.html +++ b/src/ralph/admin/templates/admin/actions.html @@ -11,10 +11,7 @@

{% trans "Actions" %}

{% trans "Go" %} {% if actions_selection_counter %} - - {{ selection_note }} + {{ selection_note }} {% endif %} {% endif %} From d3bb22931b3f07ec9ded381205bbe02eebad2037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulc?= Date: Mon, 18 Nov 2024 12:34:56 +0100 Subject: [PATCH 08/10] Fix renderer classes ordering --- src/ralph/assets/tests/test_api.py | 4 ++-- src/ralph/lib/api/utils.py | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ralph/assets/tests/test_api.py b/src/ralph/assets/tests/test_api.py index b30b507be1..9420ba5f7a 100644 --- a/src/ralph/assets/tests/test_api.py +++ b/src/ralph/assets/tests/test_api.py @@ -958,8 +958,8 @@ def test_get_dc_hosts_list(self): VirtualServerFullFactory.create_batch(20, parent=dc_assets[0]) CloudHostFullFactory.create_batch(20, hypervisor=dc_assets[0]) url = reverse('dchost-list') + "?limit=100" - with self.assertNumQueries(31): - response = self.client.get(url, format='json') + with self.assertQueriesMoreOrLess(30, plus_minus=1): + response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['count'], 63) diff --git a/src/ralph/lib/api/utils.py b/src/ralph/lib/api/utils.py index 0bde48d5f3..fa7c652d88 100644 --- a/src/ralph/lib/api/utils.py +++ b/src/ralph/lib/api/utils.py @@ -120,7 +120,10 @@ def render_form_for_serializer(self, serializer): def renderer_classes_without_form(renderer_classes): - return [OnlyRawBrowsableAPIRenderer] + [ - rc for rc in renderer_classes - if not isinstance(rc(), BrowsableAPIRenderer) - ] + def _gen(): + for rc in renderer_classes: + if not isinstance(rc(), BrowsableAPIRenderer): + yield rc + else: + yield OnlyRawBrowsableAPIRenderer + return [rc for rc in _gen()] From 56d692dccd0635a489335105654562b0fa6032b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Szulc?= Date: Mon, 18 Nov 2024 13:49:53 +0100 Subject: [PATCH 09/10] Updated changelog for 20241118.1 version. --- debian/changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/debian/changelog b/debian/changelog index 9e9b638351..f12cd1c70b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +ralph (20241118.1) bionic; urgency=medium + + [ Olga Matyla ] + * Add property_of to invoice report (#3864) + * Fix selected count (#3865) + + [ Paweł Szulc ] + * Fix renderer classes ordering + + -- Paweł Szulc Mon, 18 Nov 2024 12:48:57 +0000 + ralph (20241108.1) bionic; urgency=medium * Allow updating dchosts via dchost-list endpoint From 016e4d7c97bf4957e58d74daba763f125cd104dd Mon Sep 17 00:00:00 2001 From: Olga Matyla <73503512+matyldv@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:10:52 +0100 Subject: [PATCH 10/10] Fix Service environment inline (#3868) --- src/ralph/assets/admin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ralph/assets/admin.py b/src/ralph/assets/admin.py index 20cc176a7a..ddabef29fb 100644 --- a/src/ralph/assets/admin.py +++ b/src/ralph/assets/admin.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from django.db.models import Count +from django.forms import BaseInlineFormSet from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ @@ -129,11 +130,17 @@ class ServiceEnvironmentAdmin(CustomFieldValueAdminMixin, RalphAdmin): fields = ('service', 'environment', 'remarks', 'tags') +class PolymorphicInlineFormset(BaseInlineFormSet): + def get_queryset(self): + return super().get_queryset()[:] + + class ServiceEnvironmentInline(RalphTabularInline): model = ServiceEnvironment raw_id_fields = ['environment'] fields = ('environment',) min_num = 1 + formset = PolymorphicInlineFormset class BaseObjectsList(ScanStatusInTableMixin, Table):