diff --git a/nautobot_device_lifecycle_mgmt/filter_extensions.py b/nautobot_device_lifecycle_mgmt/filter_extensions.py new file mode 100644 index 00000000..7b6cf9a8 --- /dev/null +++ b/nautobot_device_lifecycle_mgmt/filter_extensions.py @@ -0,0 +1,30 @@ +"""Extensions to core filters.""" + +from django_filters import BooleanFilter + +try: + from nautobot.apps.filters import FilterExtension +except ImportError: + from nautobot.extras.plugins import PluginFilterExtension as FilterExtension + + +def distinct_filter(queryset, _, value): + """Returns distinct Inventory Items by part_id.""" + if value: + return queryset.order_by().distinct("part_id") + return queryset + + +class InventoryItemFilterExtension(FilterExtension): # pylint: disable=too-few-public-methods + """Extends Inventory Item Filters.""" + + model = "dcim.inventoryitem" + + filterset_fields = { + "nautobot_device_lifecycle_mgmt_distinct_part_id": BooleanFilter( + method=distinct_filter, label="_dpid_dlm_app_internal_use_only" + ) + } + + +filter_extensions = [InventoryItemFilterExtension] diff --git a/nautobot_device_lifecycle_mgmt/forms.py b/nautobot_device_lifecycle_mgmt/forms.py index b9a8a94d..49216dfb 100644 --- a/nautobot_device_lifecycle_mgmt/forms.py +++ b/nautobot_device_lifecycle_mgmt/forms.py @@ -28,6 +28,7 @@ add_blank_choice, StaticSelect2Multiple, ) +from nautobot.utilities.forms.fields import DynamicModelChoiceMixin from nautobot_device_lifecycle_mgmt.choices import ( ContractTypeChoices, @@ -65,16 +66,29 @@ def prepare_value(self, value): return super().prepare_value(pk_list) +class HardwareLCMDynamicModelChoiceField(DynamicModelChoiceMixin, forms.ModelChoiceField): + """DynamicModelChoiceField used for 'inventory_item' field in HardwareLCMForm.""" + + def to_python(self, value): + """Overload 'to_python' in forms.ModelChoiceField to force returning 'part_id' as the field value.""" + if value in self.empty_values: + return None + if self.to_field_name == "part_id": + return value + return super().to_python(value) + + class HardwareLCMForm(BootstrapMixin, CustomFieldModelFormMixin, RelationshipModelFormMixin): """Hardware Device Lifecycle creation/edit form.""" - inventory_item = forms.ModelChoiceField( - queryset=InventoryItem.objects.exclude(part_id__exact="") - .distinct() - .order_by("part_id") - .values_list("part_id", flat=True), + device_type = DynamicModelChoiceField(queryset=DeviceType.objects.all(), required=False) + inventory_item = HardwareLCMDynamicModelChoiceField( + queryset=InventoryItem.objects.order_by().distinct("part_id"), + 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", + brief_mode=False, required=False, ) @@ -131,11 +145,11 @@ class HardwareLCMFilterForm(BootstrapMixin, forms.ModelForm): required=False, queryset=DeviceType.objects.all(), to_field_name="slug" ) - inventory_item = forms.ModelMultipleChoiceField( - queryset=HardwareLCM.objects.exclude(inventory_item__isnull=True) - .exclude(inventory_item__exact="") - .values_list("inventory_item", flat=True), + inventory_item = DynamicModelMultipleChoiceField( + queryset=HardwareLCM.objects.exclude(inventory_item__isnull=True).exclude(inventory_item__exact=""), label="Inventory Part ID", + display_field="inventory_item", + to_field_name="inventory_item", required=False, ) diff --git a/tasks.py b/tasks.py index 66e9fdc9..d660e025 100644 --- a/tasks.py +++ b/tasks.py @@ -39,7 +39,7 @@ def is_truthy(arg): namespace.configure( { "nautobot_device_lifecycle_mgmt": { - "nautobot_ver": "1.5.4", + "nautobot_ver": "1.6.26", "project_name": "nautobot_device_lifecycle_mgmt", "python_ver": "3.8", "local": False,