Skip to content

Commit 8a03e34

Browse files
committed
add ProductPolicyViolation model
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 0b3315c commit 8a03e34

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by Django 6.0.6 on 2026-07-07 04:49
2+
3+
import django.db.models.deletion
4+
import dje.models
5+
import uuid
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('dje', '0015_alter_dataspaceconfiguration_purldb_api_key_and_more'),
13+
('policy', '0003_policyrule'),
14+
('product_portfolio', '0017_scancodeproject_import_options'),
15+
]
16+
17+
operations = [
18+
migrations.CreateModel(
19+
name='ProductPolicyViolation',
20+
fields=[
21+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
22+
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, verbose_name='UUID')),
23+
('violation_count', models.PositiveIntegerField(default=0, help_text='Number of objects currently violating the rule.')),
24+
('detected_date', models.DateTimeField(auto_now_add=True, help_text='The date and time when this violation was first detected.')),
25+
('last_checked', models.DateTimeField(auto_now=True, help_text='The date and time of the last evaluation.')),
26+
('resolved', models.BooleanField(default=False, help_text='Indicates whether this violation has been resolved.')),
27+
('resolved_date', models.DateTimeField(blank=True, help_text='The date and time when this violation was resolved.', null=True)),
28+
('dataspace', models.ForeignKey(editable=False, help_text='A Dataspace is an independent, exclusive set of DejaCode data, which can be either nexB master reference data or installation-specific data.', on_delete=django.db.models.deletion.PROTECT, to='dje.dataspace')),
29+
('policy_rule', models.ForeignKey(help_text='The policy rule that triggered this violation.', on_delete=django.db.models.deletion.CASCADE, related_name='product_violations', to='policy.policyrule')),
30+
('product', models.ForeignKey(help_text='The product in the context of which this violation was detected.', on_delete=django.db.models.deletion.CASCADE, related_name='policy_violations', to='product_portfolio.product')),
31+
],
32+
options={
33+
'ordering': ['-detected_date'],
34+
'unique_together': {('dataspace', 'uuid'), ('policy_rule', 'product')},
35+
},
36+
bases=(dje.models.DataspaceForeignKeyValidationMixin, models.Model),
37+
),
38+
]

product_portfolio/models.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from dje.validators import generic_uri_validator
5757
from dje.validators import validate_url_segment
5858
from dje.validators import validate_version
59+
from policy.models import AbstractPolicyViolation
5960
from vulnerabilities.fetch import fetch_for_packages
6061
from vulnerabilities.models import AffectedByVulnerabilityMixin
6162
from vulnerabilities.models import AffectedByVulnerabilityRelationship
@@ -1901,3 +1902,34 @@ def save(self, *args, **kwargs):
19011902
"The 'for_package' cannot be the same as 'resolved_to_package'."
19021903
)
19031904
super().save(*args, **kwargs)
1905+
1906+
1907+
class ProductPolicyViolationQuerySet(ProductSecuredQuerySet):
1908+
def unresolved(self):
1909+
return self.filter(resolved=False)
1910+
1911+
1912+
class ProductPolicyViolation(DataspacedModel, AbstractPolicyViolation):
1913+
"""Concrete policy violation scoped to a product."""
1914+
1915+
product = models.ForeignKey(
1916+
to="product_portfolio.Product",
1917+
on_delete=models.CASCADE,
1918+
related_name="policy_violations",
1919+
help_text=_("The product in the context of which this violation was detected."),
1920+
)
1921+
policy_rule = models.ForeignKey(
1922+
to="policy.PolicyRule",
1923+
on_delete=models.CASCADE,
1924+
related_name="product_violations",
1925+
help_text=_("The policy rule that triggered this violation."),
1926+
)
1927+
1928+
objects = DataspacedManager.from_queryset(ProductPolicyViolationQuerySet)()
1929+
1930+
class Meta:
1931+
unique_together = (("dataspace", "uuid"), ("policy_rule", "product"))
1932+
ordering = ["-detected_date"]
1933+
1934+
def __str__(self):
1935+
return f"{self.policy_rule} / {self.product}: {self.violation_count} violation(s)"

0 commit comments

Comments
 (0)