Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enchancement for status field for contracts #388

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/337.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a Status Field to ContractLCM for lifecycle purposes.
2 changes: 1 addition & 1 deletion nautobot_device_lifecycle_mgmt/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ def _sw_missing_only(self, queryset, name, value): # pylint: disable=unused-arg
return queryset


class ContractLCMFilterSet(NautobotFilterSet):
class ContractLCMFilterSet(NautobotFilterSet, StatusModelFilterSetMixin):
"""Filter for ContractLCMFilter."""

q = django_filters.CharFilter(method="search", label="Search")
Expand Down
20 changes: 12 additions & 8 deletions nautobot_device_lifecycle_mgmt/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ def get_form_kwargs(self):
class ContractLCMBulkEditForm(NautobotBulkEditForm):
"""Device Lifecycle Contrcts bulk edit form."""

model = ContractLCM
pk = forms.ModelMultipleChoiceField(queryset=ContractLCM.objects.all(), widget=forms.MultipleHiddenInput)
provider = forms.ModelChoiceField(queryset=ProviderLCM.objects.all(), required=False)
start = forms.DateField(widget=DatePicker(), required=False)
Expand All @@ -635,18 +636,14 @@ class ContractLCMBulkEditForm(NautobotBulkEditForm):
currency = forms.ChoiceField(required=False, choices=CurrencyChoices.CHOICES)
contract_type = forms.ChoiceField(choices=ContractTypeChoices.CHOICES, required=False)
support_level = forms.CharField(required=False)
status = DynamicModelChoiceField(
queryset=Status.objects.all(), required=False, query_params={"content_types": model._meta.label_lower}
)

class Meta:
"""Meta attributes for the ContractLCMBulkEditForm class."""

nullable_fields = [
"start",
"end",
"cost",
"currency",
"support_level",
"contract_type",
]
nullable_fields = ["start", "end", "cost", "currency", "support_level", "contract_type", "status"]


class ContractLCMFilterForm(NautobotFilterForm):
Expand All @@ -663,6 +660,12 @@ class ContractLCMFilterForm(NautobotFilterForm):
)
name = forms.CharField(required=False)
tags = TagFilterField(model)
status = DynamicModelMultipleChoiceField(
queryset=Status.objects.all(),
required=False,
query_params={"content_types": model._meta.label_lower},
to_field_name="name",
)

class Meta:
"""Meta attributes for the ContractLCMFilterForm class."""
Expand All @@ -672,6 +675,7 @@ class Meta:
fields = [
"q",
"provider",
"status",
"name",
"start",
"end",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 3.2.25 on 2024-11-19 18:33

import django.db.models.deletion
import nautobot.extras.models.statuses
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("extras", "0098_rename_data_jobresult_result"),
("nautobot_device_lifecycle_mgmt", "0022_alter_softwareimagelcm_inventory_items_and_more"),
]

operations = [
migrations.AddField(
model_name="contractlcm",
name="status",
field=nautobot.extras.models.statuses.StatusField(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="contracts",
to="extras.status",
),
),
]
7 changes: 7 additions & 0 deletions nautobot_device_lifecycle_mgmt/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ def __str__(self):
"graphql",
"relationships",
"webhooks",
"statuses",
)
class ContractLCM(PrimaryModel):
"""ContractLCM model for app."""
Expand All @@ -442,6 +443,12 @@ class ContractLCM(PrimaryModel):
verbose_name="Contract Type", max_length=CHARFIELD_MAX_LENGTH, blank=True, default=""
)
devices = models.ManyToManyField(to="dcim.Device", related_name="device_contracts", blank=True)
status = StatusField(
null=True,
blank=True,
on_delete=models.PROTECT,
to="extras.status",
)
comments = models.TextField(blank=True, default="")

class Meta:
Expand Down
3 changes: 2 additions & 1 deletion nautobot_device_lifecycle_mgmt/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ class Meta(BaseTable.Meta):
]


class ContractLCMTable(BaseTable):
class ContractLCMTable(StatusTableMixin, BaseTable):
"""Table for list view."""

pk = ToggleColumn()
Expand All @@ -452,6 +452,7 @@ class Meta(BaseTable.Meta):
fields = (
"pk",
"name",
"status",
"start",
"end",
"cost",
Expand Down
81 changes: 80 additions & 1 deletion nautobot_device_lifecycle_mgmt/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
from nautobot.dcim.models import Device, DeviceType, InventoryItem, Location, LocationType, Manufacturer, Platform
from nautobot.extras.models import Role, Status

from nautobot_device_lifecycle_mgmt.models import CVELCM, HardwareLCM, SoftwareLCM, ValidatedSoftwareLCM
from nautobot_device_lifecycle_mgmt.models import (
CVELCM,
ContractLCM,
HardwareLCM,
ProviderLCM,
SoftwareLCM,
ValidatedSoftwareLCM,
)


def create_devices():
Expand Down Expand Up @@ -201,3 +208,75 @@ def create_inventory_item_hardware_notices():
documentation_url="https://test.com",
),
)


def create_contracts():
"""Create DeviceLifecycle Contracts for tests."""
contract_ct = ContentType.objects.get_for_model(ContractLCM)
not_supported = Status.objects.create(
name="End of Support", color="f44336", description="Contract no longer supported."
)
not_supported.content_types.set([contract_ct])
supported = Status.objects.create(name="Active Support", color="4caf50", description="Active Contract.")
supported.content_types.set([contract_ct])
hero_provider = ProviderLCM.objects.create(
name="Skyrim Merchant",
description="Whiteruns Merchant",
country="USA",
)
villain_provider = ProviderLCM.objects.create(
name="Skyrim Villain Merchant",
description="Whiteruns Villain Merchant",
country="USA",
)

return (
ContractLCM.objects.create(
provider=hero_provider,
name="Hero Discounts 1",
number="1234567890",
start="2022-01-01",
end="2022-12-31",
cost=5000.00,
support_level="Silver",
currency="USD",
contract_type="Hardware",
status=not_supported,
),
ContractLCM.objects.create(
provider=hero_provider,
name="Hero Discounts 2",
number="1234567890",
start="2022-01-01",
end="2022-12-31",
cost=10000.00,
support_level="Gold",
currency="USD",
contract_type="Hardware",
status=not_supported,
),
ContractLCM.objects.create(
provider=villain_provider,
name="Villain Discounts 1",
number="1234567890",
start="2021-01-01",
end="2060-12-31",
cost=5000.00,
support_level="Silver",
currency="USD",
contract_type="Hardware",
status=supported,
),
ContractLCM.objects.create(
provider=villain_provider,
name="Villain Discounts 2",
number="1234567890",
start="2021-01-01",
end="2060-12-31",
cost=10000.00,
support_level="Gold",
currency="USD",
contract_type="Hardware",
status=supported,
),
)
91 changes: 90 additions & 1 deletion nautobot_device_lifecycle_mgmt/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from nautobot_device_lifecycle_mgmt.choices import CVESeverityChoices
from nautobot_device_lifecycle_mgmt.filters import (
ContractLCMFilterSet,
CVELCMFilterSet,
DeviceSoftwareValidationResultFilterSet,
HardwareLCMFilterSet,
Expand All @@ -22,6 +23,7 @@
)
from nautobot_device_lifecycle_mgmt.models import (
CVELCM,
ContractLCM,
DeviceSoftwareValidationResult,
HardwareLCM,
InventoryItemSoftwareValidationResult,
Expand All @@ -31,7 +33,7 @@
VulnerabilityLCM,
)

from .conftest import create_cves, create_devices, create_inventory_items, create_softwares
from .conftest import create_contracts, create_cves, create_devices, create_inventory_items, create_softwares


class HardwareLCMTestCase(TestCase):
Expand Down Expand Up @@ -811,3 +813,90 @@ def test_device_types(self):
"""Test device_types filter."""
params = {"device_types": [self.devicetype_2.model]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)


class ContractLCMFilterSetTest(TestCase):
"""Tests for ContractLCMFilterSet."""

queryset = ContractLCM.objects.all()
filterset = ContractLCMFilterSet

def setUp(self):
self.ch1, self.ch2, self.cv1, self.cv2 = create_contracts()

# Test Q
def test_q_multiple_records(self):
"""Test q filter to find multiple records based on name."""
params = {"q": ""}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)

def test_q_name(self):
"""Test q filter to find single record based on name."""
params = {"q": "Hero Discounts"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_q_cost(self):
"""Test q filter to find multiple records based on name."""
params = {"q": "5000"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_q_support_level(self):
"""Test q filter to find multiple records based on name."""
params = {"q": "Gold"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_q_contract_type(self):
"""Test q filter to find multiple records based on name."""
params = {"q": "Hardware"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)

# Test Queries
def test_provider(self):
"""Test provider filter to find two records."""
params = {"provider": [self.ch1.provider]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {"provider": [self.cv1.provider]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_contract_expiration(self):
"""Test expired filter to find two records based on year."""
params = {"expired": True}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {"expired": False}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_contract_start_gte(self):
"""Test start date range filter gte to find records based on year."""
params = {"start__gte": "2022-01-01"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {"start__gte": "2021-01-01"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {"start__gte": "2060-12-31"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)

def test_contract_start_lte(self):
"""Test start date range filter lte to find records based on year."""
params = {"start__lte": "2022-01-01"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {"start__lte": "2021-01-01"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {"start__lte": "2020-01-01"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)

def test_contract_end_gte(self):
"""Test end date range filter gte to find records based on year."""
params = {"end__gte": "2022-12-31"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {"end__gte": "2060-12-31"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {"end__gte": "2070-12-31"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)

def test_contract_end_lte(self):
"""Test end date range filter lte to find records based on year."""
params = {"end__lte": "2060-12-31"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {"end__lte": "2022-12-31"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {"end__lte": "2020-12-31"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 0)
Loading