Skip to content

Commit

Permalink
Merge pull request #248 from nautobot/contract-fixes
Browse files Browse the repository at this point in the history
Fixes and updates for Contract model
  • Loading branch information
bradh11 authored Jan 9, 2024
2 parents 652af94 + 5b46edb commit c4c42e8
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 8 deletions.
1 change: 1 addition & 0 deletions nautobot_device_lifecycle_mgmt/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class Meta: # pylint: disable=too-few-public-methods
"id",
"provider",
"name",
"number",
"start",
"end",
"cost",
Expand Down
5 changes: 4 additions & 1 deletion nautobot_device_lifecycle_mgmt/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,8 +682,10 @@ class ContractLCMFilterSet(NautobotFilterSet):
provider = django_filters.ModelMultipleChoiceFilter(
queryset=ProviderLCM.objects.all(),
label="Provider",
field_name="provider__name",
to_field_name="name",
)

number = django_filters.CharFilter()
expired = django_filters.BooleanFilter(method="expired_search", label="Expired")

start = django_filters.DateFilter()
Expand All @@ -702,6 +704,7 @@ class Meta:
fields = [
"provider",
"name",
"number",
"start",
"end",
"cost",
Expand Down
4 changes: 3 additions & 1 deletion nautobot_device_lifecycle_mgmt/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,14 +816,15 @@ class ContractLCMFilterForm(BootstrapMixin, forms.ModelForm):
"""Filter form to filter searches."""

q = forms.CharField(required=False, label="Search")
provider = forms.ModelMultipleChoiceField(required=False, queryset=ProviderLCM.objects.all(), to_field_name="pk")
provider = forms.ModelMultipleChoiceField(required=False, queryset=ProviderLCM.objects.all(), to_field_name="name")
currency = forms.MultipleChoiceField(
required=False, choices=CurrencyChoices.CHOICES, widget=StaticSelect2Multiple()
)
contract_type = forms.ChoiceField(
required=False, widget=StaticSelect2, choices=add_blank_choice(ContractTypeChoices.CHOICES)
)
name = forms.CharField(required=False)
number = forms.CharField(required=False)

class Meta:
"""Meta attributes for the ContractLCMFilterForm class."""
Expand All @@ -834,6 +835,7 @@ class Meta:
"q",
"provider",
"name",
"number",
"start",
"end",
"cost",
Expand Down
8 changes: 8 additions & 0 deletions nautobot_device_lifecycle_mgmt/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,14 @@ def clean(self):
if self.end and self.start:
if self.end <= self.start:
raise ValidationError("End date must be after the start date of the contract.")
if not self.provider:
raise ValidationError("Contract must have a provider.")
if not self.contract_type:
raise ValidationError("Contract must have a contract type.")
if self.contract_type not in choices.ContractTypeChoices.values():
raise ValidationError(
f"Contract type {self.contract_type} is not on the list of defined contract type choices."
)

def to_csv(self):
"""Return fields for bulk view."""
Expand Down
1 change: 1 addition & 0 deletions nautobot_device_lifecycle_mgmt/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ class Meta(BaseTable.Meta): # pylint: disable=too-few-public-methods
fields = (
"pk",
"name",
"number",
"start",
"end",
"cost",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ <h2>Contract: {% block title %}{{ object }}{% endblock %}</h2>
<td>Name</td>
<td>{{ object.name }}</td>
</tr>
<tr>
<td>Number</td>
<td>{% if object.number %} {{ object.number }} {% else %} &mdash; {% endif %}</td>
</tr>
<tr>
<td>Provider</td>
<td>
Expand Down
4 changes: 3 additions & 1 deletion nautobot_device_lifecycle_mgmt/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ class ContractLCMAPITest(APIViewTestCases.APIViewTestCase):
"""Test the ContractLCM API."""

model = ContractLCM
bulk_update_data = {"documentation_url": "https://cisco.com/eox"}
brief_fields = [
"contract_type",
"cost",
Expand Down Expand Up @@ -209,20 +208,23 @@ def setUpTestData(cls):
number="MERK00001",
start=datetime.date(2021, 4, 1),
end=datetime.date(2022, 4, 1),
contract_type="Hardware",
provider=provider,
)
ContractLCM.objects.create(
name="Meraki Software Support",
number="MERK00002",
start=datetime.date(2021, 4, 1),
end=datetime.date(2022, 4, 1),
contract_type="Software",
provider=provider,
)
ContractLCM.objects.create(
name="Juniper Hardware Support",
number="CSCO0000001",
start=datetime.date(2021, 4, 1),
end=datetime.date(2022, 4, 1),
contract_type="Hardware",
provider=provider,
)

Expand Down
112 changes: 111 additions & 1 deletion nautobot_device_lifecycle_mgmt/tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from nautobot.dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site, Platform
from nautobot.extras.models import Status

from nautobot_device_lifecycle_mgmt.choices import CVESeverityChoices
from nautobot_device_lifecycle_mgmt.choices import ContractTypeChoices, CurrencyChoices, CVESeverityChoices
from nautobot_device_lifecycle_mgmt.models import (
HardwareLCM,
SoftwareLCM,
Expand All @@ -18,6 +18,8 @@
CVELCM,
VulnerabilityLCM,
SoftwareImageLCM,
ContractLCM,
ProviderLCM,
)
from nautobot_device_lifecycle_mgmt.filters import (
HardwareLCMFilterSet,
Expand All @@ -28,6 +30,7 @@
CVELCMFilterSet,
VulnerabilityLCMFilterSet,
SoftwareImageLCMFilterSet,
ContractLCMFilterSet,
)
from .conftest import create_devices, create_inventory_items, create_cves, create_softwares

Expand Down Expand Up @@ -778,3 +781,110 @@ 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 ContractLCMFilterSetTestCase(TestCase):
"""Tests for ContractLCMFilterSet."""

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

def setUp(self):
self.provider_cisco = ProviderLCM.objects.create(name="Cisco")
self.provider_arista = ProviderLCM.objects.create(name="Arista")

self.contracts = (
ContractLCM.objects.create(
name="CiscoHardware",
provider=self.provider_cisco,
number="CSCO0000001",
start=date(2022, 5, 10),
end=date(2029, 11, 8),
cost=1_000_000,
support_level="24-7",
contract_type=ContractTypeChoices.HARDWARE,
currency=CurrencyChoices.GBP,
),
ContractLCM.objects.create(
name="CiscoSoftware",
provider=self.provider_cisco,
number="CSCO0000002",
start=date(2023, 7, 17),
end=date(2030, 6, 12),
cost=2_000_000,
support_level="12-5",
contract_type=ContractTypeChoices.SOFTWARE,
currency=CurrencyChoices.GBP,
),
ContractLCM.objects.create(
name="AristaHardware",
provider=self.provider_arista,
number="ARISTA0000001",
start=date(2023, 1, 1),
end=date(2023, 8, 30),
cost=6_000_000,
support_level="24-7",
contract_type=ContractTypeChoices.HARDWARE,
currency=CurrencyChoices.USD,
),
)

def test_q_one_name(self):
"""Test q filter to find single record based on name."""
params = {"q": "CiscoHardware"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_q_one_cost(self):
"""Test q filter to find single record based on cost."""
params = {"q": "6000000"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_q_two_contract_type(self):
"""Test q filter to find two records with Hardware contract type."""
params = {"q": "Hardware"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_start_gte(self):
"""Test start__gte filter."""
params = {"start__gte": "2023-07-01"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_end_lte(self):
"""Test end__lte filter."""
params = {"end__lte": "2024-01-01"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_provider(self):
"""Test provider filter."""
params = {"provider": ["Cisco"]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_number(self):
"""Test number filter."""
params = {"number": "ARISTA0000001"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_cost(self):
"""Test cost filter."""
params = {"cost": "1000000"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_support_level(self):
"""Test support_level filter."""
params = {"support_level": "12-5"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_contract_type(self):
"""Test contract_type filter."""
params = {"contract_type": "Hardware"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

def test_expired(self):
"""Test expired filter."""
params = {"expired": True}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)

def test_currency(self):
"""Test currency filter."""
params = {"currency": "USD"}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
43 changes: 43 additions & 0 deletions nautobot_device_lifecycle_mgmt/tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from nautobot.extras.choices import RelationshipTypeChoices
from nautobot.extras.models import Relationship, RelationshipAssociation, Status, Tag

from nautobot_device_lifecycle_mgmt.choices import ContractTypeChoices, CurrencyChoices
from nautobot_device_lifecycle_mgmt.models import (
HardwareLCM,
InventoryItemSoftwareValidationResult,
Expand Down Expand Up @@ -689,3 +690,45 @@ def test_provider_assignment(self):
self.assertEqual(cisco_contract.currency, "USD")
self.assertEqual(cisco_contract.contract_type, "Hardware")
self.assertEqual(cisco_contract.comments, "Cisco gave us discount")


class ContractLCMTestCase(TestCase):
"""Tests for the ContractLCM model."""

def setUp(self):
"""Set up base objects."""
self.provider_cisco = ProviderLCM.objects.create(name="Cisco")

def test_create_contractlcm_required_only(self):
"""Successfully create ContractLCM with required fields only."""
contractlcm = ContractLCM.objects.create(
name="Cisco Contract", provider=self.provider_cisco, contract_type=ContractTypeChoices.HARDWARE
)
self.assertEqual(contractlcm.name, "Cisco Contract")
self.assertEqual(contractlcm.provider, self.provider_cisco)
self.assertEqual(contractlcm.contract_type, "Hardware")

def test_create_contractlcm_all(self):
"""Successfully create ContractLCM with all fields."""
contractlcm_full = ContractLCM.objects.create(
name="Cisco Hardware Contract",
provider=self.provider_cisco,
number="CSCO0000001",
start=date(2022, 5, 10),
end=date(2029, 11, 8),
cost=1_000_000,
support_level="24-7",
contract_type=ContractTypeChoices.HARDWARE,
currency=CurrencyChoices.GBP,
)

self.assertEqual(contractlcm_full.name, "Cisco Hardware Contract")
self.assertEqual(contractlcm_full.provider, self.provider_cisco)
self.assertEqual(contractlcm_full.number, "CSCO0000001")
self.assertEqual(str(contractlcm_full.start), "2022-05-10")
self.assertEqual(str(contractlcm_full.end), "2029-11-08")
self.assertEqual(contractlcm_full.cost, 1_000_000)
self.assertEqual(contractlcm_full.support_level, "24-7")
self.assertEqual(contractlcm_full.contract_type, "Hardware")
self.assertEqual(contractlcm_full.currency, "GBP")
self.assertEqual(str(contractlcm_full), f"{contractlcm_full.name}")
62 changes: 58 additions & 4 deletions nautobot_device_lifecycle_mgmt/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,6 @@ def setUpTestData(cls):
destination=inventoryitems[2],
relationship=inv_item_contract_rel,
)
# breakpoint()

def test_contract_devices_export(self):
"""Test CSV export for Devices connected to Contract."""
Expand All @@ -586,8 +585,6 @@ def test_contract_devices_export(self):
obj_perm.users.add(self.user)
obj_perm.object_types.add(ContentType.objects.get_for_model(ContractLCM))
obj_perm.object_types.add(ContentType.objects.get_for_model(Device))
# obj_perm.object_types.add(ContentType.objects.get_for_model(DeviceType))
# obj_perm.object_types.add(ContentType.objects.get_for_model(Manufacturer))

contract1 = ContractLCM.objects.filter(name="CiscoHW1").first()
response = self.client.get(f"{contract1.get_absolute_url()}?export_contract=devices")
Expand Down Expand Up @@ -620,7 +617,6 @@ def test_contract_inventoryitems_export(self):
obj_perm.users.add(self.user)
obj_perm.object_types.add(ContentType.objects.get_for_model(ContractLCM))
obj_perm.object_types.add(ContentType.objects.get_for_model(InventoryItem))
# obj_perm.object_types.add(ContentType.objects.get_for_model(Manufacturer))

contract1 = ContractLCM.objects.filter(name="CiscoHW1").first()
response = self.client.get(f"{contract1.get_absolute_url()}?export_contract=inventoryitems")
Expand All @@ -644,3 +640,61 @@ def test_contract_inventoryitems_export(self):
"\nAristaHW1,Hardware,100GBASE-SR4 QSFP Transceiver,QSFP-100G-SR4-S,,Cisco,sw2,Test 1"
"\nAristaHW1,Hardware,48x RJ-45 Line Card,WS-X6548-GE-TX,,Cisco,sw3,Test 1",
)


class ContractLCMViewTest(ViewTestCases.PrimaryObjectViewTestCase):
"""Test the ContractLCM views."""

model = ContractLCM

def _get_base_url(self):
return "plugins:{}:{}_{{}}".format( # pylint: disable=consider-using-f-string
self.model._meta.app_label, self.model._meta.model_name
)

@classmethod
def setUpTestData(cls):
"""Set up test data."""
provider = ProviderLCM.objects.create(name="Cisco")

ContractLCM.objects.create(name="Cisco Support - Hardware Routers", provider=provider, contract_type="Hardware")
ContractLCM.objects.create(
name="Cisco Support - Hardware Switches", provider=provider, contract_type="Hardware"
)
ContractLCM.objects.create(name="Cisco Support - Software", provider=provider, contract_type="Software")

cls.form_data = {
"name": "Cisco Support - Software - Line Cards",
"provider": provider.id,
"contract_type": "Software",
}
cls.csv_data = (
"name,provider,contract_type",
f"Cisco Support - Hardware1, {provider.id}, Hardware",
f"Cisco Support - Hardware2, {provider.id}, Hardware",
f"Cisco Support - Software, {provider.id}, Software",
)

def test_has_advanced_tab(self):
pass

def test_get_object_notes(self):
pass

def test_list_objects_with_permission(self):
pass

def test_bulk_import_objects_with_permission_csv_file(self):
pass

def test_bulk_import_objects_with_constrained_permission(self):
pass

def test_bulk_import_objects_with_permission(self):
pass

def test_bulk_edit_objects_with_constrained_permission(self):
pass

def test_bulk_edit_objects_with_permission(self):
pass

0 comments on commit c4c42e8

Please sign in to comment.