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 5 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
73 changes: 61 additions & 12 deletions nautobot_device_lifecycle_mgmt/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,20 @@
add_blank_choice,
)
from nautobot.core.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
from nautobot.dcim.models import Device, DeviceType, InventoryItem, Location, Manufacturer, Platform
from nautobot.extras.forms import CustomFieldModelBulkEditFormMixin, NautobotFilterForm
from nautobot.dcim.models import (
Device,
DeviceType,
InventoryItem,
Location,
Manufacturer,
Platform,
)
from nautobot.extras.forms import (
CustomFieldModelBulkEditFormMixin,
NautobotFilterForm,
StatusModelBulkEditFormMixin,
StatusModelFilterFormMixin,
)
from nautobot.extras.models import Role, Status, Tag

from nautobot_device_lifecycle_mgmt.choices import (
Expand Down Expand Up @@ -75,7 +87,10 @@ class HardwareLCMForm(NautobotModelForm):
device_type = DynamicModelChoiceField(queryset=DeviceType.objects.all(), required=False)
inventory_item = HardwareLCMDynamicModelChoiceField(
queryset=InventoryItem.objects.without_tree_fields().order_by().distinct("part_id"),
query_params={"part_id__nre": "^$", "nautobot_device_lifecycle_mgmt_distinct_part_id": "true"},
query_params={
"part_id__nre": "^$",
"nautobot_device_lifecycle_mgmt_distinct_part_id": "true",
},
label="Inventory Part ID",
display_field="part_id",
to_field_name="part_id",
Expand Down Expand Up @@ -354,7 +369,9 @@ class ValidatedSoftwareLCMForm(NautobotModelForm):
devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False)
device_types = DynamicModelMultipleChoiceField(queryset=DeviceType.objects.all(), required=False)
device_roles = DynamicModelMultipleChoiceField(
queryset=Role.objects.all(), query_params={"content_types": "dcim.device"}, required=False
queryset=Role.objects.all(),
query_params={"content_types": "dcim.device"},
required=False,
)

inventory_items = DynamicModelMultipleChoiceField(queryset=InventoryItem.objects.all(), required=False)
Expand Down Expand Up @@ -383,7 +400,19 @@ def clean(self):
inventory_items = self.cleaned_data.get("inventory_items")
object_tags = self.cleaned_data.get("object_tags")

if sum(obj.count() for obj in (devices, device_types, device_roles, inventory_items, object_tags)) == 0:
if (
sum(
obj.count()
for obj in (
devices,
device_types,
device_roles,
inventory_items,
object_tags,
)
)
== 0
):
msg = "You need to assign to at least one object."
self.add_error(None, msg)

Expand Down Expand Up @@ -482,7 +511,10 @@ class DeviceSoftwareValidationResultFilterForm(NautobotFilterForm):
required=False,
)
device_role = DynamicModelMultipleChoiceField(
queryset=Role.objects.all(), query_params={"content_types": "dcim.device"}, to_field_name="name", required=False
queryset=Role.objects.all(),
query_params={"content_types": "dcim.device"},
to_field_name="name",
required=False,
)
exclude_sw_missing = forms.BooleanField(
required=False,
Expand Down Expand Up @@ -561,7 +593,10 @@ class InventoryItemSoftwareValidationResultFilterForm(NautobotFilterForm):
required=False,
)
device_role = DynamicModelMultipleChoiceField(
queryset=Role.objects.all(), query_params={"content_types": "dcim.device"}, to_field_name="name", required=False
queryset=Role.objects.all(),
query_params={"content_types": "dcim.device"},
to_field_name="name",
required=False,
)
exclude_sw_missing = forms.BooleanField(
required=False,
Expand Down Expand Up @@ -624,9 +659,10 @@ def get_form_kwargs(self):
return {"provider": self.request.GET.get("provider")} # pylint: disable=E1101


class ContractLCMBulkEditForm(NautobotBulkEditForm):
class ContractLCMBulkEditForm(NautobotBulkEditForm, StatusModelBulkEditFormMixin):
"""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 @@ -646,10 +682,11 @@ class Meta:
"currency",
"support_level",
"contract_type",
"status",
]


class ContractLCMFilterForm(NautobotFilterForm):
class ContractLCMFilterForm(NautobotFilterForm, StatusModelFilterFormMixin):
"""Filter form to filter searches."""

model = ContractLCM
Expand All @@ -659,7 +696,9 @@ class ContractLCMFilterForm(NautobotFilterForm):
required=False, choices=CurrencyChoices.CHOICES, widget=StaticSelect2Multiple()
)
contract_type = forms.ChoiceField(
required=False, widget=StaticSelect2, choices=add_blank_choice(ContractTypeChoices.CHOICES)
required=False,
widget=StaticSelect2,
choices=add_blank_choice(ContractTypeChoices.CHOICES),
)
name = forms.CharField(required=False)
tags = TagFilterField(model)
Expand All @@ -672,6 +711,7 @@ class Meta:
fields = [
"q",
"provider",
"status",
"name",
"start",
"end",
Expand Down Expand Up @@ -789,7 +829,14 @@ class ContactLCMBulkEditForm(NautobotBulkEditForm):
class Meta:
"""Meta attributes for the ContactLCMBulkEditForm class."""

nullable_fields = ["address", "phone", "email", "comments", "priority", "contract"]
nullable_fields = [
"address",
"phone",
"email",
"comments",
"priority",
"contract",
]


class ContactLCMFilterForm(NautobotFilterForm):
Expand Down Expand Up @@ -846,7 +893,9 @@ class CVELCMBulkEditForm(NautobotBulkEditForm, CustomFieldModelBulkEditFormMixin
comments = forms.CharField(required=False)
tags = DynamicModelMultipleChoiceField(queryset=Tag.objects.all(), required=False)
status = DynamicModelChoiceField(
queryset=Status.objects.all(), required=False, query_params={"content_types": model._meta.label_lower}
queryset=Status.objects.all(),
required=False,
query_params={"content_types": model._meta.label_lower},
)

class Meta:
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,
),
)
Loading