Skip to content
Open
Show file tree
Hide file tree
Changes from 14 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
3 changes: 3 additions & 0 deletions changes/573.added
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
feature:
- "Added multi-tenancy support to ValidatedSoftwareLCM, allowing software validations to be scoped to specific Tenants."
- "Added Tenant filtering to Hardware Notice and Validated Software reports."
25 changes: 25 additions & 0 deletions docs/user/software_lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ When creating the validated software the following fields are available. Fields
| Valid Until | End date when the rules defined by this object stop applying |
| Preferred Version | Whether the Software specified by this Validated Software should be considered a preferred version |
| Devices -> Devices | Devices whose software will be validated by this Validated Software |
| Devices -> Device tenants | Devices assigned to these tenants will have software validated by this Validated Software |
| Devices -> Device types | Devices having these device types will have software validated by this Validated Software |
| Devices -> Device roles | Devices having these device roles will have software validated by this Validated Software |
| Inventory Items -> Inventory items | Inventory items whose software will be validated by this Validated Software |
Expand All @@ -35,6 +36,7 @@ Example of a Validated Software object with most fields filled in:
Validated Software object can be assigned to:

- devices
- device tenants
- device types
- device roles
- inventory items
Expand All @@ -54,7 +56,9 @@ For device, Validated Software will be used if one, or more, of the following, a

- Device is explicitly listed in the Validated Software `devices` attribute.
- Device's device type AND device role match `device_types` AND `device_roles` in Validated Software. This applies only if BOTH are set. See the **Special cases** subsection that follows.
- Device's tenant AND device type match `device_tenants` AND `device_types` in Validated Software. This applies only if BOTH are set. See the **Special cases** subsection that follows.
- Device's device type is listed in the Validated Software `device_types` attribute.
- Device's tenant is listed in the Validated Software `device_tenants` attribute.
- Device's role is listed in the Validated Software `device_roles` attribute.
- Device's tags are listed in the Validated Software `object_tags` attribute.

Expand Down Expand Up @@ -86,6 +90,27 @@ For example, in the below case **Validated Software 4.21M** will apply to **Devi
- device roles: leaf
- software: 4.21M

When a Validated Software object is assigned to both device tenants and device type then these are used in conjunction (logical AND). That is, such an object will apply to devices that are assigned both, specified device tenant AND device type.

This logic is used to allow to specify a subset of the devices of a given type by adding additional constraint in the form of tenant.

For example, in the below case **Validated Software 4.21M** will apply to **Device 1** only since **Device 2** has a match for device type only.

- Device 1
- device type: 7150-S64
- device tenants: tenant_A
- software: 4.21M

- Device 2
- device type: 7150-S64
- device tenants: tenant_B
- software: 4.21M

- Validated Software - 4.21M:
- device types: 7150-S64
- device tenants: tenant_A
- software: 4.21M

### Behavior when using API to retrieve Validated Software list for devices and inventory items

By default when retrieving a list of Validated Software objects it is possible to filter results by assignments used when the object was created.
Expand Down
27 changes: 27 additions & 0 deletions nautobot_device_lifecycle_mgmt/filter_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,32 @@ class TagFilterExtension(FilterExtension):
}


#
# TENANT FILTER EXTENSION
#
class TenantFilterExtension(FilterExtension):
"""Extends Tenant Filters."""

model = "tenancy.tenant"

filterset_fields = {
"nautobot_device_lifecycle_mgmt_validated_software": NaturalKeyOrPKMultipleChoiceFilter(
field_name="validated_software_tenants",
queryset=ValidatedSoftwareLCM.objects.all(),
to_field_name="pk",
label="Validated Software",
),
}

filterform_fields = {
"nautobot_device_lifecycle_mgmt_validated_software": DynamicModelMultipleChoiceField(
queryset=ValidatedSoftwareLCM.objects.all(),
label="Validated Software",
required=False,
),
}


#
# SOFTWAREVERSION FILTER EXTENSION
#
Expand All @@ -217,5 +243,6 @@ class SoftwareVersionFilterExtension(FilterExtension): # pylint: disable=too-fe
RoleFilterExtension,
DeviceTypeFilterExtension,
TagFilterExtension,
TenantFilterExtension,
SoftwareVersionFilterExtension,
]
38 changes: 37 additions & 1 deletion nautobot_device_lifecycle_mgmt/filters.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Filtering implementation for the Lifecycle Management app."""
# pylint: disable=too-many-lines

import datetime

Expand All @@ -7,6 +8,7 @@
from nautobot.apps.filters import NautobotFilterSet, SearchFilter, StatusFilter, StatusModelFilterSetMixin
from nautobot.dcim.models import Device, DeviceType, InventoryItem, Location, Manufacturer, Platform, SoftwareVersion
from nautobot.extras.models import Role, Status, Tag
from nautobot.tenancy.models import Tenant

from nautobot_device_lifecycle_mgmt.choices import CVESeverityChoices
from nautobot_device_lifecycle_mgmt.models import (
Expand Down Expand Up @@ -284,6 +286,7 @@ class ValidatedSoftwareLCMFilterSet(NautobotFilterSet):
"start": {"lookup_expr": "icontains", "preprocessor": str.strip},
"end": {"lookup_expr": "icontains", "preprocessor": str.strip},
"devices__name": {"lookup_expr": "icontains", "preprocessor": str.strip},
"device_tenants__name": {"lookup_expr": "icontains", "preprocessor": str.strip},
"device_types__model": {"lookup_expr": "icontains", "preprocessor": str.strip},
"device_roles__name": {"lookup_expr": "icontains", "preprocessor": str.strip},
"inventory_items__name": {"lookup_expr": "icontains", "preprocessor": str.strip},
Expand Down Expand Up @@ -317,6 +320,17 @@ class ValidatedSoftwareLCMFilterSet(NautobotFilterSet):
to_field_name="model",
label="Device Types (model)",
)
device_tenants_id = django_filters.ModelMultipleChoiceFilter(
field_name="device_tenants",
queryset=Tenant.objects.all(),
label="Tenant",
)
device_tenants = django_filters.ModelMultipleChoiceFilter(
field_name="device_tenants__name",
queryset=Tenant.objects.all(),
to_field_name="name",
label="Tenant (name)",
)
device_roles_id = django_filters.ModelMultipleChoiceFilter(
field_name="device_roles",
queryset=Role.objects.all(),
Expand Down Expand Up @@ -371,7 +385,7 @@ def valid_search(self, queryset, name, value): # pylint: disable=unused-argumen
"""Perform the valid_search search."""
today = datetime.date.today()
if value is True:
qs_filter = Q(start__lte=today, end=None) | Q(start__lte=today, end__gte=today)
qs_filter = Q(start__lte=today, end__isnull=True) | Q(start__lte=today, end__gte=today)
else:
qs_filter = Q(start__gt=today) | Q(end__lt=today)
return queryset.filter(qs_filter)
Expand Down Expand Up @@ -428,6 +442,17 @@ class DeviceHardwareNoticeResultFilterSet(NautobotFilterSet):
queryset=Platform.objects.all(),
label="Platform",
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
field_name="device__tenant",
queryset=Tenant.objects.all(),
label="Tenant",
)
tenant = django_filters.ModelMultipleChoiceFilter(
field_name="device__tenant__name",
queryset=Tenant.objects.all(),
to_field_name="name",
label="Tenant (name)",
)
location_id = django_filters.ModelMultipleChoiceFilter(
field_name="device__location",
queryset=Location.objects.all(),
Expand Down Expand Up @@ -563,6 +588,17 @@ class DeviceSoftwareValidationResultFilterSet(NautobotFilterSet):
queryset=Platform.objects.all(),
label="Platform",
)
tenant_id = django_filters.ModelMultipleChoiceFilter(
field_name="device__tenant",
queryset=Tenant.objects.all(),
label="Tenant",
)
tenant = django_filters.ModelMultipleChoiceFilter(
field_name="device__tenant__name",
queryset=Tenant.objects.all(),
to_field_name="name",
label="Tenant (name)",
)
location_id = django_filters.ModelMultipleChoiceFilter(
field_name="device__location",
queryset=Location.objects.all(),
Expand Down
34 changes: 33 additions & 1 deletion nautobot_device_lifecycle_mgmt/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from nautobot.core.forms.constants import BOOLEAN_WITH_BLANK_CHOICES
from nautobot.dcim.models import Device, DeviceType, InventoryItem, Location, Manufacturer, Platform, SoftwareVersion
from nautobot.extras.models import Role, Status, Tag
from nautobot.tenancy.models import Tenant

from nautobot_device_lifecycle_mgmt.choices import (
ContractTypeChoices,
Expand Down Expand Up @@ -173,6 +174,7 @@ class ValidatedSoftwareLCMForm(NautobotModelForm):

software = DynamicModelChoiceField(queryset=SoftwareVersion.objects.all(), required=True)
devices = DynamicModelMultipleChoiceField(queryset=Device.objects.all(), required=False)
device_tenants = DynamicModelMultipleChoiceField(queryset=Tenant.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
Expand All @@ -192,6 +194,7 @@ class Meta:
fields = [ # pylint: disable=E4271
"software",
"devices",
"device_tenants",
"device_types",
"device_roles",
"inventory_items",
Expand All @@ -211,12 +214,19 @@ def clean(self):
super().clean()

devices = self.cleaned_data.get("devices")
device_tenants = self.cleaned_data.get("device_tenants")
device_types = self.cleaned_data.get("device_types")
device_roles = self.cleaned_data.get("device_roles")
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_tenants, 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 All @@ -242,6 +252,11 @@ class ValidatedSoftwareLCMBulkEditForm(NautobotBulkEditForm):
required=False,
label="Devices",
)
device_tenants = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
required=False,
label="Tenant",
)
device_types = DynamicModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
required=False,
Expand Down Expand Up @@ -285,6 +300,7 @@ class Meta:

nullable_fields = [
"devices",
"device_tenants",
"device_types",
"device_roles",
"inventory_items",
Expand All @@ -308,6 +324,11 @@ class ValidatedSoftwareLCMFilterForm(NautobotFilterForm):
queryset=Device.objects.all(),
required=False,
)
device_tenants = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
to_field_name="name",
required=False,
)
device_types = DynamicModelMultipleChoiceField(
queryset=DeviceType.objects.all(),
to_field_name="model",
Expand Down Expand Up @@ -338,6 +359,7 @@ class Meta:
"q",
"software",
"devices",
"device_tenants",
"device_types",
"device_roles",
"inventory_items",
Expand Down Expand Up @@ -368,6 +390,11 @@ class DeviceHardwareNoticeResultFilterForm(NautobotFilterForm):
label="Platform",
required=False,
)
tenant = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
label="Tenant",
required=False,
)
location = DynamicModelMultipleChoiceField(
queryset=Location.objects.all(),
query_params={"content_type": "dcim.device"},
Expand Down Expand Up @@ -452,6 +479,11 @@ class DeviceSoftwareValidationResultFilterForm(NautobotFilterForm):
label="Platform",
required=False,
)
tenant = DynamicModelMultipleChoiceField(
queryset=Tenant.objects.all(),
label="Tenant",
required=False,
)
valid = forms.BooleanField(
required=False,
widget=StaticSelect2(choices=BOOLEAN_WITH_BLANK_CHOICES),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.26 on 2026-03-13 13:33

from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("tenancy", "0009_update_all_charfields_max_length_to_255"),
("nautobot_device_lifecycle_mgmt", "0029_contractlcm_status"),
]

operations = [
migrations.AddField(
model_name="validatedsoftwarelcm",
name="device_tenants",
field=models.ManyToManyField(blank=True, related_name="validated_software_tenants", to="tenancy.tenant"),
),
]
2 changes: 2 additions & 0 deletions nautobot_device_lifecycle_mgmt/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ class ValidatedSoftwareLCM(PrimaryModel):
)
devices = models.ManyToManyField(to="dcim.Device", related_name="validated_software", blank=True)
device_types = models.ManyToManyField(to="dcim.DeviceType", related_name="validated_software", blank=True)
device_tenants = models.ManyToManyField(to="tenancy.Tenant", related_name="validated_software_tenants", blank=True)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably just have related_name="validated_software"

device_roles = models.ManyToManyField(to="extras.Role", related_name="validated_software", blank=True)
inventory_items = models.ManyToManyField(to="dcim.InventoryItem", related_name="validated_software", blank=True)
object_tags = models.ManyToManyField(to="extras.Tag", related_name="validated_software", blank=True)
Expand All @@ -315,6 +316,7 @@ class Meta:
clone_fields = (
"software",
"devices",
"device_tenants",
"device_types",
"device_roles",
"inventory_items",
Expand Down
57 changes: 49 additions & 8 deletions nautobot_device_lifecycle_mgmt/software_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,31 @@ def __init__(self, qs, item_obj): # pylint: disable=invalid-name

def filter_qs(self):
"""Returns filtered ValidatedSoftwareLCM query set."""
self.validated_software_qs = self.validated_software_qs.filter(
Q(devices=self.item_obj.pk)
| Q(device_types=self.item_obj.device_type.pk, device_roles=self.item_obj.role.pk)
| Q(device_types=self.item_obj.device_type.pk, device_roles=None)
| Q(device_types=None, device_roles=self.item_obj.role.pk)
| Q(object_tags__in=self.item_obj.tags.all())
).distinct()

# 1. tenant relationship exists.
if self.item_obj.tenant:
Copy link
Copy Markdown
Contributor

@progala progala Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking about this more. This is breaking backwards compatibility. We are applying this new logic to any device with a tenant defined. Validation logic will break if an operator has an existing ValidatedSoftware record without a tenant defined (all of them, since this is a new addition) that worked for his devices, whether they have a tenant or not.

I think we'll need a workflow diagram illustrating different use cases. We need to ensure the existing behavior is not broken. This means adding regression tests.

Copy link
Copy Markdown
Contributor Author

@pato23arg pato23arg Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@progala modified the behavior so it is fully backwards compatible, plus added regression tests for scenarios 1-5:

Scenario 1: Device WITH tenant, only global records exist (the upgrade scenario)

Expected Result: Device sees global records

Scenario 2: Device WITHOUT tenant, only global records exist

Expected Result: Device sees global records

Scenario 3: Device WITH tenant, both tenant-specific AND global records exist

Expected Result: Device sees both, tenant-specific weighted higher

Scenario 4: Device WITH Tenant A, records exist for Tenant B only + global

Expected Result: Device sees global only (not Tenant B)

Scenario 5: Device WITH tenant, tenant-specific records exist, no global

Expected Result: Device sees tenant-specific only

Scenario 6: Device directly assigned to ValidatedSoftwareLCM

Expected Result: Direct assignment overrides all (existing behavior preserved)

self.validated_software_qs = self.validated_software_qs.filter(
Q(devices__in=[self.item_obj.pk])
| Q(device_tenants=self.item_obj.tenant, device_types=self.item_obj.device_type.pk)
| Q(device_tenants=self.item_obj.tenant, device_roles=self.item_obj.role.pk)
| Q(device_tenants=self.item_obj.tenant, device_types=None)
| Q(object_tags__in=self.item_obj.tags.all())
).distinct()
Comment on lines +57 to +73
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not include Q(device_tenants=None, device_types=..., device_roles=...), so there is no fallback to the ValidatedSoftware without tenants.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fallback happens in lines 67-77

# 2. No tenant relationship exists, filter based on device type, role, and tags.
else:
self.validated_software_qs = self.validated_software_qs.filter(
Q(devices__in=[self.item_obj.pk])
| Q(
device_types=self.item_obj.device_type.pk,
device_roles=self.item_obj.role.pk,
device_tenants__isnull=True,
)
| Q(device_types=self.item_obj.device_type.pk, device_roles=None, device_tenants__isnull=True)
| Q(device_types=None, device_roles=self.item_obj.role.pk, device_tenants__isnull=True)
| Q(object_tags__in=self.item_obj.tags.all())
).distinct()
# 3. Override qs when direct device assignments exist so no duplicates are returned.
if self.item_obj.validated_software.exists():
self.validated_software_qs = self.validated_software_qs.filter(devices__in=[self.item_obj.pk]).distinct()
Comment on lines +88 to +89
Copy link
Copy Markdown
Contributor

@progala progala Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The queries above already have a distinct() applied to them. This would essentially repeat the computation we've done up to this point.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this essentially overrides the previous queryset, that's why I added distinct(). Computation-wise should be ok as it is all lazy-eval.

self.validated_software_qs = self._add_weights().order_by("weight", "start")

return self.validated_software_qs
Expand All @@ -87,6 +104,30 @@ def _add_weights(self):
When(device_types=self.item_obj.device_type.pk, device_roles=None, preferred=False, then=Value(1030)),
When(device_roles=self.item_obj.role.pk, preferred=True, then=Value(40)),
When(device_roles=self.item_obj.role.pk, preferred=False, then=Value(1040)),
When(
device_tenants=self.item_obj.tenant,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when tenant is not defined for the device?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if device has no tenant, it only sees validated software with no tenant assigned.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, let me rephrase. What happens to this query specifically if the tenant is not defined? Does it work? Does it error out? What will it return?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will work:

device without tenant defined:
(AND: (OR: RelatedIn(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_devices, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_devices.device), [UUID('217b8dda-b719-4680-a2d2-5a3e184c1487')]), (AND: RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_3fbc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_roles.role), UUID('d91b7e68-dadb-4fbb-840c-5b4739575d73')), RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_7ebc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_types.devicetype), UUID('21475479-aa38-40c2-bed7-4e33ac7a4640'))), (AND: RelatedIsNull(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_3fbc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_roles.role), True), RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_7ebc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_types.devicetype), UUID('21475479-aa38-40c2-bed7-4e33ac7a4640'))), (AND: RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_3fbc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_roles.role), UUID('d91b7e68-dadb-4fbb-840c-5b4739575d73')), RelatedIsNull(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_7ebc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_types.devicetype), True)), RelatedIn(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_object_tags, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_object_tags.tag), <django.db.models.sql.query.Query object at 0x7ad37df44500>)), RelatedIn(Col(T10, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_devices.device), [UUID('217b8dda-b719-4680-a2d2-5a3e184c1487')]))

device with tenant defined:
(AND: (OR: RelatedIn(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_devices, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_devices.device), [UUID('e46734cb-cbea-4d9f-ad9e-728af5f584a9')]), (AND: RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_a4e6, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_tenants.tenant), UUID('33b7f29e-ca9c-412f-83d6-c37fc8f96abc')), RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_7ebc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_types.devicetype), UUID('da439205-b475-4cb9-a025-ff01b7223fb0'))), (AND: RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_3fbc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_roles.role), UUID('c018c03a-e764-40c1-b56d-19e327a4b8d0')), RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_a4e6, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_tenants.tenant), UUID('33b7f29e-ca9c-412f-83d6-c37fc8f96abc'))), (AND: RelatedExact(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_a4e6, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_tenants.tenant), UUID('33b7f29e-ca9c-412f-83d6-c37fc8f96abc')), RelatedIsNull(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_device_7ebc, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_device_types.devicetype), True)), RelatedIn(Col(nautobot_device_lifecycle_mgmt_validatedsoftwarelcm_object_tags, nautobot_device_lifecycle_mgmt.ValidatedSoftwareLCM_object_tags.tag), <django.db.models.sql.query.Query object at 0x7ad37de06e90>)))

device_types=self.item_obj.device_type.pk,
preferred=True,
then=Value(50),
),
When(
device_tenants=self.item_obj.tenant,
device_types=self.item_obj.device_type.pk,
Comment on lines +140 to +142
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have (tenant, device_type) and (tenant, role) combinations, or is (tenant, device_type) enough?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not receive role as a requirement, that's why it is not implemented ... do you see it as a potential case? roles are shared across tenants so it did not made a lot of sense to me, but maybe there could be some useful case?

preferred=False,
then=Value(1050),
),
When(
device_tenants=self.item_obj.tenant,
device_roles=self.item_obj.role.pk,
preferred=True,
then=Value(60),
),
When(
device_tenants=self.item_obj.tenant,
device_roles=self.item_obj.role.pk,
preferred=False,
then=Value(1060),
),
When(preferred=True, then=Value(990)),
default=Value(1990),
output_field=IntegerField(),
Expand Down
Loading
Loading