Skip to content

Commit fcbb801

Browse files
Add initial LicenseDetection Models and UI
Signed-off-by: Ayan Sinha Mahapatra <ayansmahapatra@gmail.com>
1 parent 038db97 commit fcbb801

File tree

9 files changed

+508
-0
lines changed

9 files changed

+508
-0
lines changed

scancodeio/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@
108108
"resource": 100,
109109
"package": 100,
110110
"dependency": 100,
111+
"license": 100,
111112
"relation": 100,
112113
},
113114
)

scanpipe/api/serializers.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from scanpipe.models import CodebaseResource
3232
from scanpipe.models import DiscoveredDependency
3333
from scanpipe.models import DiscoveredPackage
34+
from scanpipe.models import DiscoveredLicense
3435
from scanpipe.models import InputSource
3536
from scanpipe.models import Project
3637
from scanpipe.models import ProjectMessage
@@ -412,6 +413,20 @@ class Meta:
412413
]
413414

414415

416+
class DiscoveredLicenseSerializer(serializers.ModelSerializer):
417+
compliance_alert = serializers.CharField()
418+
419+
class Meta:
420+
model = DiscoveredLicense
421+
fields = [
422+
"detection_count",
423+
"identifier",
424+
"license_expression",
425+
"license_expression_spdx",
426+
"compliance_alert",
427+
]
428+
429+
415430
class CodebaseRelationSerializer(serializers.ModelSerializer):
416431
from_resource = serializers.ReadOnlyField(source="from_resource.path")
417432
to_resource = serializers.ReadOnlyField(source="to_resource.path")
@@ -470,6 +485,7 @@ def get_model_serializer(model_class):
470485
CodebaseResource: CodebaseResourceSerializer,
471486
DiscoveredPackage: DiscoveredPackageSerializer,
472487
DiscoveredDependency: DiscoveredDependencySerializer,
488+
DiscoveredLicense: DiscoveredLicenseSerializer,
473489
CodebaseRelation: CodebaseRelationSerializer,
474490
ProjectMessage: ProjectMessageSerializer,
475491
}.get(model_class, None)

scanpipe/filters.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from scanpipe.models import CodebaseResource
4040
from scanpipe.models import DiscoveredDependency
4141
from scanpipe.models import DiscoveredPackage
42+
from scanpipe.models import DiscoveredLicense
4243
from scanpipe.models import Project
4344
from scanpipe.models import ProjectMessage
4445
from scanpipe.models import Run
@@ -591,6 +592,19 @@ def filter(self, qs, value):
591592
return qs.filter(lookups)
592593

593594

595+
class DiscoveredLicenseSearchFilter(QuerySearchFilter):
596+
def filter(self, qs, value):
597+
if not value:
598+
return qs
599+
600+
search_fields = ["license_expression", "license_expression_spdx"]
601+
lookups = Q()
602+
for field_names in search_fields:
603+
lookups |= Q(**{f"{field_names}__{self.lookup_expr}": value})
604+
605+
return qs.filter(lookups)
606+
607+
594608
class GroupOrderingFilter(django_filters.OrderingFilter):
595609
"""Add the ability to provide a group a fields to order by."""
596610

@@ -755,6 +769,41 @@ class Meta:
755769
]
756770

757771

772+
class LicenseFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
773+
dropdown_widget_fields = [
774+
"compliance_alert",
775+
]
776+
777+
search = DiscoveredLicenseSearchFilter(
778+
label="Search", field_name="name", lookup_expr="icontains"
779+
)
780+
sort = GroupOrderingFilter(
781+
label="Sort",
782+
fields=[
783+
"detection_count",
784+
"identifier",
785+
"license_expression",
786+
"license_expression_spdx",
787+
"compliance_alert",
788+
],
789+
)
790+
license_expression = ParentAllValuesFilter()
791+
compliance_alert = django_filters.ChoiceFilter(
792+
choices=[(EMPTY_VAR, "None")] + CodebaseResource.Compliance.choices,
793+
)
794+
795+
class Meta:
796+
model = DiscoveredLicense
797+
fields = [
798+
"search",
799+
"identifier",
800+
"detection_count",
801+
"license_expression",
802+
"license_expression_spdx",
803+
"compliance_alert",
804+
]
805+
806+
758807
class ProjectMessageFilterSet(FilterSetUtilsMixin, django_filters.FilterSet):
759808
search = QuerySearchFilter(
760809
label="Search", field_name="description", lookup_expr="icontains"
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Generated by Django 5.0.2 on 2024-03-17 12:38
2+
3+
import django.db.models.deletion
4+
import scanpipe.models
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
("scanpipe", "0053_restructure_pipelines_data"),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name="DiscoveredLicense",
17+
fields=[
18+
(
19+
"id",
20+
models.AutoField(
21+
auto_created=True,
22+
primary_key=True,
23+
serialize=False,
24+
verbose_name="ID",
25+
),
26+
),
27+
(
28+
"compliance_alert",
29+
models.CharField(
30+
blank=True,
31+
choices=[
32+
("ok", "Ok"),
33+
("warning", "Warning"),
34+
("error", "Error"),
35+
("missing", "Missing"),
36+
],
37+
editable=False,
38+
help_text="Indicates how the license expression complies with provided policies.",
39+
max_length=10,
40+
),
41+
),
42+
(
43+
"license_expression",
44+
models.TextField(
45+
blank=True,
46+
help_text="A license expression string using the SPDX license expression syntax and ScanCode license keys, the effective license expression for this license detection.",
47+
),
48+
),
49+
(
50+
"license_expression_spdx",
51+
models.TextField(
52+
blank=True,
53+
help_text="SPDX license expression string with SPDX ids.",
54+
),
55+
),
56+
(
57+
"matches",
58+
models.JSONField(
59+
blank=True,
60+
default=list,
61+
help_text="List of license matches combined in this detection.",
62+
),
63+
),
64+
(
65+
"detection_log",
66+
models.JSONField(
67+
blank=True,
68+
default=list,
69+
help_text="A list of detection DetectionRule explaining how this detection was created.",
70+
),
71+
),
72+
(
73+
"identifier",
74+
models.CharField(
75+
blank=True,
76+
help_text="An identifier unique for a license detection, containing the license expression and a UUID crafted from the match contents.",
77+
max_length=1024,
78+
),
79+
),
80+
(
81+
"detection_count",
82+
models.BigIntegerField(
83+
blank=True,
84+
help_text="Total number of this license detection discovered.",
85+
null=True,
86+
),
87+
),
88+
(
89+
"file_regions",
90+
models.JSONField(
91+
blank=True,
92+
default=list,
93+
help_text="A list of file regions with resource path, start and end line details for each place this license detection was discovered at. Also contains whether this license was discovered from a file or from package metadata.",
94+
),
95+
),
96+
(
97+
"project",
98+
models.ForeignKey(
99+
editable=False,
100+
on_delete=django.db.models.deletion.CASCADE,
101+
related_name="%(class)ss",
102+
to="scanpipe.project",
103+
),
104+
),
105+
],
106+
options={
107+
"ordering": ["detection_count", "identifier"],
108+
"indexes": [
109+
models.Index(
110+
fields=["identifier"], name="scanpipe_di_identif_b533f3_idx"
111+
),
112+
models.Index(
113+
fields=["license_expression"],
114+
name="scanpipe_di_license_33d11a_idx",
115+
),
116+
models.Index(
117+
fields=["license_expression_spdx"],
118+
name="scanpipe_di_license_eb5e9d_idx",
119+
),
120+
models.Index(
121+
fields=["detection_count"],
122+
name="scanpipe_di_detecti_d87ff1_idx",
123+
),
124+
],
125+
},
126+
bases=(
127+
scanpipe.models.UpdateMixin,
128+
scanpipe.models.SaveProjectMessageMixin,
129+
scanpipe.models.UpdateFromDataMixin,
130+
models.Model,
131+
),
132+
),
133+
migrations.AddConstraint(
134+
model_name="discoveredlicense",
135+
constraint=models.UniqueConstraint(
136+
condition=models.Q(("identifier", ""), _negated=True),
137+
fields=("project", "identifier"),
138+
name="scanpipe_discoveredlicense_unique_license_id_within_project",
139+
),
140+
),
141+
]

0 commit comments

Comments
 (0)