14
14
from operator import attrgetter
15
15
from urllib .parse import unquote_plus
16
16
17
+ from django .conf import settings
17
18
from django .contrib import messages
18
19
from django .contrib .auth .decorators import login_required
19
20
from django .contrib .auth .mixins import LoginRequiredMixin
64
65
from dje .templatetags .dje_tags import urlize_target_blank
65
66
from dje .utils import chunked
66
67
from dje .utils import get_object_compare_diff
68
+ from dje .utils import group_by_simple
67
69
from dje .utils import is_uuid4
68
70
from dje .views import DataspacedCreateView
69
71
from dje .views import DataspacedDeleteView
@@ -418,63 +420,75 @@ def tab_hierarchy(self):
418
420
return {"fields" : [(None , context , None , template )]}
419
421
420
422
def tab_inventory (self ):
421
- productcomponent_qs = self .filter_productcomponent .qs .order_by (
422
- "feature" , "component" , "name" , "version"
423
- ).group_by ("feature" )
424
-
425
- productpackage_qs = self .filter_productpackage .qs .order_by (
426
- "feature" , "package__type" , "package__namespace" , "package__name"
427
- ).group_by ("feature" )
428
-
429
423
user = self .request .user
430
424
dataspace = user .dataspace
431
425
432
- scancodeio = ScanCodeIO ( user )
433
- display_scan_features = all (
434
- [
435
- scancodeio . is_configured () ,
436
- dataspace . enable_package_scanning ,
437
- productpackage_qs ,
438
- ]
426
+ productcomponent_qs = self . filter_productcomponent . qs . order_by (
427
+ "feature" ,
428
+ "component" ,
429
+ "component__name" ,
430
+ "component__version" ,
431
+ "name" ,
432
+ "version" ,
439
433
)
440
434
441
- if display_scan_features :
442
- self . display_scan_features = True
443
- injected_feature_grouped = self . inject_scan_data (
444
- scancodeio , productpackage_qs , dataspace . uuid
445
- )
446
- # Do not override the original data if ScanCode.io couldn't be reach
447
- if injected_feature_grouped :
448
- productpackage_qs = injected_feature_grouped
435
+ productpackage_qs = self . filter_productpackage . qs . order_by (
436
+ "feature" ,
437
+ "package__type" ,
438
+ "package__namespace" ,
439
+ "package__name" ,
440
+ "package__version" ,
441
+ "package__filename" ,
442
+ )
449
443
450
- inventory_items = defaultdict (list )
451
- for relation_qs in [productcomponent_qs , productpackage_qs ]:
452
- for feature , relation in relation_qs .items ():
453
- inventory_items [feature ].extend (relation )
444
+ # 1. Combine components and packages into a single list of object
445
+ all_inventory_items = list (productcomponent_qs ) + list (productpackage_qs )
454
446
455
447
display_tab = any (
456
448
[
457
- inventory_items ,
449
+ all_inventory_items ,
458
450
self .filter_productcomponent .is_active (),
459
451
self .filter_productpackage .is_active (),
460
452
]
461
453
)
462
-
463
454
if not display_tab :
464
455
return
465
456
466
- count = sum ((len (items ) for items in inventory_items .values ()))
467
- label = f'Inventory <span class="badge text-bg-primary">{ count } </span>'
468
- inventory_items = dict (sorted (inventory_items .items ()))
457
+ # 2. Paginate the inventory list
458
+ page_number = self .request .GET .get ("inventory-page" , 2 )
459
+ paginator = Paginator (all_inventory_items , settings .TAB_PAGINATE_BY )
460
+ object_list = paginator .page (page_number ).object_list
461
+
462
+ # 3. Group objects by features
463
+ objects_by_feature = defaultdict (list )
464
+ for feature , items in group_by_simple (object_list , "feature" ).items ():
465
+ objects_by_feature [feature ].extend (items )
466
+
467
+ count = len (all_inventory_items )
468
+ label = f'Inventory <span class="badge badge-primary">{ count } </span>'
469
469
tab_context = {
470
- "inventory_items" : inventory_items ,
470
+ "inventory_items" : dict ( objects_by_feature . items ()) ,
471
471
}
472
472
473
+ # 4. Inject the Scan data when activated
474
+ scancodeio = ScanCodeIO (user )
475
+ display_scan_features = all (
476
+ [
477
+ scancodeio .is_configured (),
478
+ dataspace .enable_package_scanning ,
479
+ productpackage_qs ,
480
+ ]
481
+ )
482
+ if display_scan_features :
483
+ self .display_scan_features = True
484
+ self .inject_scan_data (scancodeio , objects_by_feature , dataspace .uuid )
485
+
486
+ # 5. Display the compliance alert based on license policies
473
487
if self .show_licenses_policy :
474
488
compliance_alerts = set (
475
- inventory_item . inventory_item_compliance_alert
476
- for inventory_group in inventory_items . values ()
477
- for inventory_item in inventory_group
489
+ alert
490
+ for inventory_item in all_inventory_items
491
+ for alert in inventory_item . compliance_alerts
478
492
)
479
493
480
494
if "error" in compliance_alerts :
@@ -484,20 +498,19 @@ def tab_inventory(self):
484
498
f' title="Compliance errors"></i> { label } '
485
499
)
486
500
501
+ # 6. Add vulnerability data
487
502
vulnerablecode = VulnerableCode (user )
488
503
enable_vulnerabilities = all (
489
504
[
490
505
user .dataspace .enable_vulnerablecodedb_access ,
491
506
vulnerablecode .is_configured (),
492
507
]
493
508
)
494
-
495
509
if enable_vulnerabilities :
496
510
# Re-use the inventory mapping to prevent duplicated queries
497
511
packages = [
498
512
inventory_item .package
499
- for inventory_group in inventory_items .values ()
500
- for inventory_item in inventory_group
513
+ for inventory_item in object_list
501
514
if isinstance (inventory_item , ProductPackage )
502
515
]
503
516
@@ -516,6 +529,7 @@ def inject_scan_data(scancodeio, feature_grouped, dataspace_uuid):
516
529
product_package .package .download_url
517
530
for product_packages in feature_grouped .values ()
518
531
for product_package in product_packages
532
+ if isinstance (product_package , ProductPackage )
519
533
]
520
534
521
535
# WARNING: Do not trigger a Request for an empty list of download_urls
0 commit comments