From d836c6e09d0cd205c34bfb6d7d0a86826d6281b2 Mon Sep 17 00:00:00 2001 From: Wqrld Date: Fri, 11 Jul 2025 14:07:16 +0200 Subject: [PATCH 1/4] Replace deprecated is_ajax call. This fixes the broken /project/rating endpoint Change-Id: I6d7b7e4defa06b1927d8d31ec828fdaabca2cf39 Signed-off-by: Wqrld --- cloudkittydashboard/dashboards/project/rating/views.py | 2 +- cloudkittydashboard/tests/test_predictive_pricing.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cloudkittydashboard/dashboards/project/rating/views.py b/cloudkittydashboard/dashboards/project/rating/views.py index 87ee80c..f1e9139 100644 --- a/cloudkittydashboard/dashboards/project/rating/views.py +++ b/cloudkittydashboard/dashboards/project/rating/views.py @@ -52,7 +52,7 @@ def get_data(self): def quote(request): pricing = 0.0 - if request.is_ajax(): + if request.headers.get('x-requested-with') == 'XMLHttpRequest': if request.method == 'POST': json_data = json.loads(request.body) diff --git a/cloudkittydashboard/tests/test_predictive_pricing.py b/cloudkittydashboard/tests/test_predictive_pricing.py index 38c898b..8fad16b 100644 --- a/cloudkittydashboard/tests/test_predictive_pricing.py +++ b/cloudkittydashboard/tests/test_predictive_pricing.py @@ -36,7 +36,7 @@ def setUp(self): def _test_quote_request_not_ajax_post(self, arg): request = mock.MagicMock() if arg == 'ajax': - request.is_ajax.return_value = False + request.headers.get.return_value = None # Not an AJAX request elif arg == 'method': request.method == 'POST' resp = self.quote(request) @@ -57,7 +57,7 @@ def test_quote_does_update_request_dict(self, api_mock): {'other_key': None, 'service': 'test_service'}] request = mock.MagicMock() - request.is_ajax.return_value = True + request.headers.get.return_value = 'XMLHttpRequest' request.method = 'POST' request.body = json.dumps(body) From fb903fcbd3a95b6b9e7686b5a9877360993af929 Mon Sep 17 00:00:00 2001 From: Ivan Anfimov Date: Mon, 25 Aug 2025 00:56:35 +0000 Subject: [PATCH 2/4] tox: Remove basepython and ineffective ignore_basepython_conflict Change-Id: Id041630a143ee07e9bdc1acbb0ab0cea66dc0543 Signed-off-by: Ivan Anfimov --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index a9f506a..696e997 100644 --- a/tox.ini +++ b/tox.ini @@ -2,10 +2,8 @@ minversion = 3.18.0 envlist = py3,pep8 skipsdist = True -ignore_basepython_conflict = True [testenv] -basepython = python3 usedevelop = True install_command = pip install -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -U {opts} {packages} setenv = From 664a4a4b0965c5a098c146b4ae5523ee4433a934 Mon Sep 17 00:00:00 2001 From: Takashi Kajinami Date: Sat, 13 Sep 2025 18:54:55 +0900 Subject: [PATCH 3/4] Use identical ids Use dedicated range for all files to eanble cloudkitty-dashboard to avoid conflict with the other plugins or even core files provided by horizon. 310xx is used by watcher to atm so move it to 320xx . Change-Id: I2e7cca1bb6ab64072b4967b980e661f4415e5704 Signed-off-by: Takashi Kajinami --- .../enabled/{_10_admin_group.py => _32010_admin_group.py} | 0 .../enabled/{_10_project_group.py => _32011_project_group.py} | 0 .../{_11_admin_hashmap_panel.py => _32020_admin_hashmap_panel.py} | 0 .../{_11_admin_rating_panel.py => _32021_admin_rating_panel.py} | 0 .../{_11_admin_summary_panel.py => _32022_admin_summary_panel.py} | 0 ..._11_project_rating_panel.py => _32030_project_rating_panel.py} | 0 ...oject_reporting_panel.py => _32031_project_reporting_panel.py} | 0 ...3_admin_pyscripts_panel.py => _32040_admin_pyscripts_panel.py} | 0 .../enabled/{_31000_cloudkitty.py => _32050_cloudkitty.py} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename cloudkittydashboard/enabled/{_10_admin_group.py => _32010_admin_group.py} (100%) rename cloudkittydashboard/enabled/{_10_project_group.py => _32011_project_group.py} (100%) rename cloudkittydashboard/enabled/{_11_admin_hashmap_panel.py => _32020_admin_hashmap_panel.py} (100%) rename cloudkittydashboard/enabled/{_11_admin_rating_panel.py => _32021_admin_rating_panel.py} (100%) rename cloudkittydashboard/enabled/{_11_admin_summary_panel.py => _32022_admin_summary_panel.py} (100%) rename cloudkittydashboard/enabled/{_11_project_rating_panel.py => _32030_project_rating_panel.py} (100%) rename cloudkittydashboard/enabled/{_12_project_reporting_panel.py => _32031_project_reporting_panel.py} (100%) rename cloudkittydashboard/enabled/{_13_admin_pyscripts_panel.py => _32040_admin_pyscripts_panel.py} (100%) rename cloudkittydashboard/enabled/{_31000_cloudkitty.py => _32050_cloudkitty.py} (100%) diff --git a/cloudkittydashboard/enabled/_10_admin_group.py b/cloudkittydashboard/enabled/_32010_admin_group.py similarity index 100% rename from cloudkittydashboard/enabled/_10_admin_group.py rename to cloudkittydashboard/enabled/_32010_admin_group.py diff --git a/cloudkittydashboard/enabled/_10_project_group.py b/cloudkittydashboard/enabled/_32011_project_group.py similarity index 100% rename from cloudkittydashboard/enabled/_10_project_group.py rename to cloudkittydashboard/enabled/_32011_project_group.py diff --git a/cloudkittydashboard/enabled/_11_admin_hashmap_panel.py b/cloudkittydashboard/enabled/_32020_admin_hashmap_panel.py similarity index 100% rename from cloudkittydashboard/enabled/_11_admin_hashmap_panel.py rename to cloudkittydashboard/enabled/_32020_admin_hashmap_panel.py diff --git a/cloudkittydashboard/enabled/_11_admin_rating_panel.py b/cloudkittydashboard/enabled/_32021_admin_rating_panel.py similarity index 100% rename from cloudkittydashboard/enabled/_11_admin_rating_panel.py rename to cloudkittydashboard/enabled/_32021_admin_rating_panel.py diff --git a/cloudkittydashboard/enabled/_11_admin_summary_panel.py b/cloudkittydashboard/enabled/_32022_admin_summary_panel.py similarity index 100% rename from cloudkittydashboard/enabled/_11_admin_summary_panel.py rename to cloudkittydashboard/enabled/_32022_admin_summary_panel.py diff --git a/cloudkittydashboard/enabled/_11_project_rating_panel.py b/cloudkittydashboard/enabled/_32030_project_rating_panel.py similarity index 100% rename from cloudkittydashboard/enabled/_11_project_rating_panel.py rename to cloudkittydashboard/enabled/_32030_project_rating_panel.py diff --git a/cloudkittydashboard/enabled/_12_project_reporting_panel.py b/cloudkittydashboard/enabled/_32031_project_reporting_panel.py similarity index 100% rename from cloudkittydashboard/enabled/_12_project_reporting_panel.py rename to cloudkittydashboard/enabled/_32031_project_reporting_panel.py diff --git a/cloudkittydashboard/enabled/_13_admin_pyscripts_panel.py b/cloudkittydashboard/enabled/_32040_admin_pyscripts_panel.py similarity index 100% rename from cloudkittydashboard/enabled/_13_admin_pyscripts_panel.py rename to cloudkittydashboard/enabled/_32040_admin_pyscripts_panel.py diff --git a/cloudkittydashboard/enabled/_31000_cloudkitty.py b/cloudkittydashboard/enabled/_32050_cloudkitty.py similarity index 100% rename from cloudkittydashboard/enabled/_31000_cloudkitty.py rename to cloudkittydashboard/enabled/_32050_cloudkitty.py From d2c85051c548ac598e3a77d26a3a2a6e74a88c6d Mon Sep 17 00:00:00 2001 From: Leonie Chamberlin-Medd Date: Wed, 13 Aug 2025 14:00:36 +0000 Subject: [PATCH 4/4] Add support for custom forms Adds two custom forms: DateForm and CheckBoxForm, to be used by future patches for improving dashboard features. These follow the same format as in the Horizon OpenStack Dashboard [1]. 1. https://opendev.org/openstack/horizon/src/branch/master/horizon/forms Change-Id: I3e708cb7e69778a4632b19cb355c7f595caaf21a Signed-off-by: Leonie Chamberlin-Medd --- cloudkittydashboard/forms/__init__.py | 28 ++++++++++++++++ cloudkittydashboard/forms/base.py | 48 +++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 cloudkittydashboard/forms/__init__.py create mode 100644 cloudkittydashboard/forms/base.py diff --git a/cloudkittydashboard/forms/__init__.py b/cloudkittydashboard/forms/__init__.py new file mode 100644 index 0000000..0514cd6 --- /dev/null +++ b/cloudkittydashboard/forms/__init__.py @@ -0,0 +1,28 @@ +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# Importing non-modules that are not used explicitly +from django.forms.fields import BooleanField +from django.forms.fields import DateField +from django.forms.forms import Form + +# Convenience imports for public API components. +from cloudkittydashboard.forms.base import CheckBoxForm +from cloudkittydashboard.forms.base import DateForm + +__all__ = [ + "DateForm", + "CheckBoxForm", + 'DateField', 'BooleanField', + 'Form', +] diff --git a/cloudkittydashboard/forms/base.py b/cloudkittydashboard/forms/base.py new file mode 100644 index 0000000..ee5bfb8 --- /dev/null +++ b/cloudkittydashboard/forms/base.py @@ -0,0 +1,48 @@ +# Copyright 2012 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Copyright 2012 Nebula, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from django import forms + + +class DateForm(forms.Form): + """A simple form for selecting a range of time.""" + start = forms.DateField(input_formats=("%Y-%m-%d",)) + end = forms.DateField(input_formats=("%Y-%m-%d",)) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields['start'].widget.attrs['data-date-format'] = "yyyy-mm-dd" + self.fields['end'].widget.attrs['data-date-format'] = "yyyy-mm-dd" + + +class CheckBoxForm(forms.Form): + """A form for selecting fields to group by in the rating summary.""" + checkbox_fields = ["type", "id", "user_id"] + for field in checkbox_fields: + locals()[field] = forms.BooleanField(required=False) + + def get_selected_fields(self): + """Return list of selected groupby fields.""" + if not self.is_valid(): + return [] + # Get all selected checkbox fields + selected = [ + field for field in self.checkbox_fields + if self.cleaned_data.get(field) + ] + return selected