diff --git a/product_portfolio/tests/test_views.py b/product_portfolio/tests/test_views.py index a6171b00..03c120e3 100644 --- a/product_portfolio/tests/test_views.py +++ b/product_portfolio/tests/test_views.py @@ -56,6 +56,7 @@ from product_portfolio.models import ProductRelationStatus from product_portfolio.models import ProductStatus from product_portfolio.models import ScanCodeProject +from product_portfolio.tests import make_product from product_portfolio.views import ManageComponentGridView from workflow.models import Request from workflow.models import RequestTemplate @@ -271,6 +272,35 @@ def test_product_portfolio_detail_view_tab_dependency_view(self): response = self.client.get(url) self.assertContains(response, "4 results") + def test_product_portfolio_detail_view_tab_vulnerability_view(self): + self.client.login(username="nexb_user", password="secret") + url = self.product1.get_url("tab_vulnerabilities") + + with self.assertMaxQueries(9): + response = self.client.get(url) + self.assertContains(response, "0 results") + + p1 = make_package(self.dataspace, is_vulnerable=True) + p2 = make_package(self.dataspace, is_vulnerable=True) + p3 = make_package(self.dataspace, is_vulnerable=True) + p4 = make_package(self.dataspace, is_vulnerable=True) + product1 = make_product(self.dataspace, inventory=[p1, p2, p3, p4]) + + self.assertEqual(4, product1.packages.count()) + self.assertEqual(4, product1.packages.vulnerable().count()) + + url = product1.get_url("tab_vulnerabilities") + with self.assertMaxQueries(10): + response = self.client.get(url) + self.assertContains(response, "4 results") + + def test_product_portfolio_detail_view_tab_vulnerability_view_filters(self): + self.client.login(username="nexb_user", password="secret") + url = self.product1.get_url("tab_vulnerabilities") + response = self.client.get(url) + expected = "?vulnerabilities-max_score=#vulnerabilities" + self.assertContains(response, expected) + def test_product_portfolio_detail_view_object_type_filter_in_inventory_tab(self): self.client.login(username="nexb_user", password="secret") diff --git a/product_portfolio/views.py b/product_portfolio/views.py index 171bb68a..b43810f2 100644 --- a/product_portfolio/views.py +++ b/product_portfolio/views.py @@ -129,7 +129,7 @@ from product_portfolio.models import ScanCodeProject -class BaseProductView: # TODO: Rename this, it is a mixin +class BaseProductViewMixin: model = Product slug_url_kwarg = ("name", "version") @@ -227,7 +227,7 @@ def get_extra_add_urls(self): class ProductDetailsView( LoginRequiredMixin, - BaseProductView, + BaseProductViewMixin, ObjectDetailsView, ): template_name = "product_portfolio/product_details.html" @@ -685,7 +685,7 @@ def get_context_data(self, **kwargs): class ProductTabInventoryView( LoginRequiredMixin, - BaseProductView, + BaseProductViewMixin, PreviousNextPaginationMixin, TabContentView, ): @@ -915,7 +915,7 @@ def inject_scan_data(scancodeio, feature_grouped, dataspace_uuid): class ProductTabCodebaseView( LoginRequiredMixin, - BaseProductView, + BaseProductViewMixin, PreviousNextPaginationMixin, TabContentView, ): @@ -996,7 +996,7 @@ def has_any_values(field_name): class ProductTabDependenciesView( LoginRequiredMixin, - BaseProductView, + BaseProductViewMixin, PreviousNextPaginationMixin, TableHeaderMixin, TabContentView, @@ -1075,12 +1075,11 @@ def get_context_data(self, **kwargs): class ProductTabVulnerabilitiesView( LoginRequiredMixin, - BaseProductView, + BaseProductViewMixin, PreviousNextPaginationMixin, TableHeaderMixin, TabContentView, ): - # TODO: check queries: assertMax template_name = "product_portfolio/tabs/tab_vulnerabilities.html" paginate_by = 50 query_dict_page_param = "vulnerabilities-page" @@ -1108,7 +1107,6 @@ def get_context_data(self, **kwargs): "-min_score", ) - # TODO: Add missing anchor self.filterset = self.filterset_class( self.request.GET, queryset=vulnerability_qs, @@ -1147,7 +1145,7 @@ def get_context_data(self, **kwargs): class ProductTabImportsView( LoginRequiredMixin, - BaseProductView, + BaseProductViewMixin, TabContentView, ): template_name = "product_portfolio/tabs/tab_imports.html" @@ -1328,7 +1326,7 @@ class ProductAddView( class ProductUpdateView( LicenseDataForBuilderMixin, - BaseProductView, + BaseProductViewMixin, DataspacedUpdateView, ): form_class = ProductForm @@ -1346,7 +1344,7 @@ def get_success_url(self): return super().get_success_url() -class ProductDeleteView(BaseProductView, DataspacedDeleteView): +class ProductDeleteView(BaseProductViewMixin, DataspacedDeleteView): permission_required = "product_portfolio.delete_product" def get_queryset(self): @@ -1583,7 +1581,7 @@ class AttributionView( LoginRequiredMixin, DataspaceScopeMixin, GetDataspacedObjectMixin, - BaseProductView, + BaseProductViewMixin, DetailView, ): template_name = "product_portfolio/attribution/base.html" @@ -1822,15 +1820,15 @@ def get_context_data(self, **kwargs): return context -class ProductSendAboutFilesView(BaseProductView, SendAboutFilesView): +class ProductSendAboutFilesView(BaseProductViewMixin, SendAboutFilesView): pass -class ProductExportSPDXDocumentView(BaseProductView, ExportSPDXDocumentView): +class ProductExportSPDXDocumentView(BaseProductViewMixin, ExportSPDXDocumentView): pass -class ProductExportCycloneDXBOMView(BaseProductView, ExportCycloneDXBOMView): +class ProductExportCycloneDXBOMView(BaseProductViewMixin, ExportCycloneDXBOMView): pass @@ -1925,7 +1923,7 @@ class BaseProductManageGridView( LicenseDataForBuilderMixin, GetDataspacedObjectMixin, PermissionRequiredMixin, - BaseProductView, + BaseProductViewMixin, FormSetView, ): """A base view for managing product relationship through a grid.""" @@ -2248,7 +2246,7 @@ class BaseProductImportFormView( PermissionRequiredMixin, GetDataspacedObjectMixin, DataspacedModelFormMixin, - BaseProductView, + BaseProductViewMixin, FormView, ): permission_required = "product_portfolio.change_product"