diff --git a/dejacode/settings.py b/dejacode/settings.py index 9cf65496..0c6a81e0 100644 --- a/dejacode/settings.py +++ b/dejacode/settings.py @@ -371,6 +371,7 @@ def gettext_noop(s): # An integer specifying how many objects should be displayed per page. PAGINATE_BY = env.int("PAGINATE_BY", default=None) +TAB_PAGINATE_BY = env.int("TAB_PAGINATE_BY", default=100) ADMIN_FORMS_CONFIGURATION = env.dict("ADMIN_FORMS_CONFIGURATION", default={}) diff --git a/product_portfolio/views.py b/product_portfolio/views.py index 96252c10..09a69116 100644 --- a/product_portfolio/views.py +++ b/product_portfolio/views.py @@ -14,6 +14,7 @@ from operator import attrgetter from urllib.parse import unquote_plus +from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin @@ -64,6 +65,7 @@ from dje.templatetags.dje_tags import urlize_target_blank from dje.utils import chunked from dje.utils import get_object_compare_diff +from dje.utils import group_by_simple from dje.utils import is_uuid4 from dje.views import DataspacedCreateView from dje.views import DataspacedDeleteView @@ -418,63 +420,75 @@ def tab_hierarchy(self): return {"fields": [(None, context, None, template)]} def tab_inventory(self): - productcomponent_qs = self.filter_productcomponent.qs.order_by( - "feature", "component", "name", "version" - ).group_by("feature") - - productpackage_qs = self.filter_productpackage.qs.order_by( - "feature", "package__type", "package__namespace", "package__name" - ).group_by("feature") - user = self.request.user dataspace = user.dataspace - scancodeio = ScanCodeIO(user) - display_scan_features = all( - [ - scancodeio.is_configured(), - dataspace.enable_package_scanning, - productpackage_qs, - ] + productcomponent_qs = self.filter_productcomponent.qs.order_by( + "feature", + "component", + "component__name", + "component__version", + "name", + "version", ) - if display_scan_features: - self.display_scan_features = True - injected_feature_grouped = self.inject_scan_data( - scancodeio, productpackage_qs, dataspace.uuid - ) - # Do not override the original data if ScanCode.io couldn't be reach - if injected_feature_grouped: - productpackage_qs = injected_feature_grouped + productpackage_qs = self.filter_productpackage.qs.order_by( + "feature", + "package__type", + "package__namespace", + "package__name", + "package__version", + "package__filename", + ) - inventory_items = defaultdict(list) - for relation_qs in [productcomponent_qs, productpackage_qs]: - for feature, relation in relation_qs.items(): - inventory_items[feature].extend(relation) + # 1. Combine components and packages into a single list of object + all_inventory_items = list(productcomponent_qs) + list(productpackage_qs) display_tab = any( [ - inventory_items, + all_inventory_items, self.filter_productcomponent.is_active(), self.filter_productpackage.is_active(), ] ) - if not display_tab: return - count = sum((len(items) for items in inventory_items.values())) - label = f'Inventory {count}' - inventory_items = dict(sorted(inventory_items.items())) + # 2. Paginate the inventory list + page_number = self.request.GET.get("inventory-page", 2) + paginator = Paginator(all_inventory_items, settings.TAB_PAGINATE_BY) + object_list = paginator.page(page_number).object_list + + # 3. Group objects by features + objects_by_feature = defaultdict(list) + for feature, items in group_by_simple(object_list, "feature").items(): + objects_by_feature[feature].extend(items) + + count = len(all_inventory_items) + label = f'Inventory {count}' tab_context = { - "inventory_items": inventory_items, + "inventory_items": dict(objects_by_feature.items()), } + # 4. Inject the Scan data when activated + scancodeio = ScanCodeIO(user) + display_scan_features = all( + [ + scancodeio.is_configured(), + dataspace.enable_package_scanning, + productpackage_qs, + ] + ) + if display_scan_features: + self.display_scan_features = True + self.inject_scan_data(scancodeio, objects_by_feature, dataspace.uuid) + + # 5. Display the compliance alert based on license policies if self.show_licenses_policy: compliance_alerts = set( - inventory_item.inventory_item_compliance_alert - for inventory_group in inventory_items.values() - for inventory_item in inventory_group + alert + for inventory_item in all_inventory_items + for alert in inventory_item.compliance_alerts ) if "error" in compliance_alerts: @@ -484,6 +498,7 @@ def tab_inventory(self): f' title="Compliance errors"> {label}' ) + # 6. Add vulnerability data vulnerablecode = VulnerableCode(user) enable_vulnerabilities = all( [ @@ -491,13 +506,11 @@ def tab_inventory(self): vulnerablecode.is_configured(), ] ) - if enable_vulnerabilities: # Re-use the inventory mapping to prevent duplicated queries packages = [ inventory_item.package - for inventory_group in inventory_items.values() - for inventory_item in inventory_group + for inventory_item in object_list if isinstance(inventory_item, ProductPackage) ] @@ -516,6 +529,7 @@ def inject_scan_data(scancodeio, feature_grouped, dataspace_uuid): product_package.package.download_url for product_packages in feature_grouped.values() for product_package in product_packages + if isinstance(product_package, ProductPackage) ] # WARNING: Do not trigger a Request for an empty list of download_urls