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

New reporting tabs and exports for Contract view #217

Merged
merged 12 commits into from
Oct 31, 2023
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ jobs:
fail-fast: true
matrix:
python-version: ["3.8", "3.9", "3.10"]
nautobot-version: ["1.4.10", "stable"]
nautobot-version: ["1.4.10", "1.6.3"]
env:
INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}"
INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
Expand Down Expand Up @@ -147,7 +147,7 @@ jobs:
fail-fast: true
matrix:
python-version: ["3.8", "3.9", "3.10"]
nautobot-version: ["1.4.10", "stable"]
nautobot-version: ["1.4.10", "1.6.3"]
runs-on: "ubuntu-20.04"
env:
INVOKE_NAUTOBOT_DEVICE_LIFECYCLE_MGMT_PYTHON_VER: "${{ matrix.python-version }}"
Expand Down
47 changes: 24 additions & 23 deletions nautobot_device_lifecycle_mgmt/filters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pylint: disable=unsupported-binary-operation
"""Filtering implementation for the Lifecycle Management plugin."""
import datetime

Expand Down Expand Up @@ -81,15 +82,15 @@ class Meta:
"expired",
]

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset

qs_filter = Q(end_of_sale__icontains=value) | Q(end_of_support__icontains=value)
return queryset.filter(qs_filter)

def expired_search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def expired_search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
today = datetime.datetime.today().date()
lookup = "gte" if not value else "lt"
Expand Down Expand Up @@ -129,7 +130,7 @@ class Meta:
"pre_release",
]

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
Expand Down Expand Up @@ -213,15 +214,15 @@ class Meta:
"default_image",
]

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset

qs_filter = Q(image_file_name__icontains=value) | Q(software__version__icontains=value)
return queryset.filter(qs_filter)

def device(self, queryset, name, value): # pylint: disable=no-self-use
def device(self, queryset, name, value):
"""Search for software image for a given device."""
value = value.strip()
if not value:
Expand All @@ -241,7 +242,7 @@ def device(self, queryset, name, value): # pylint: disable=no-self-use

return queryset.filter(id__in=SoftwareImageLCM.objects.get_for_object(device).values("id"))

def inventory_item(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def inventory_item(self, queryset, name, value): # pylint: disable=unused-argument
"""Search for software image for a given inventory item."""
value = value.strip()
if not value:
Expand Down Expand Up @@ -349,15 +350,15 @@ class Meta:
"valid",
]

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset

qs_filter = Q(start__icontains=value) | Q(end__icontains=value)
return queryset.filter(qs_filter)

def valid_search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def valid_search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the valid_search search."""
today = datetime.date.today()
if value is True:
Expand All @@ -366,7 +367,7 @@ def valid_search(self, queryset, name, value): # pylint: disable=unused-argumen
qs_filter = Q(start__gt=today) | Q(end__lt=today)
return queryset.filter(qs_filter)

def device(self, queryset, name, value): # pylint: disable=no-self-use
def device(self, queryset, name, value):
"""Search for validated software for a given device."""
value = value.strip()
if not value:
Expand All @@ -386,7 +387,7 @@ def device(self, queryset, name, value): # pylint: disable=no-self-use

return ValidatedSoftwareLCM.objects.get_for_object(device)

def inventory_item(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def inventory_item(self, queryset, name, value): # pylint: disable=unused-argument
"""Search for validated software for a given inventory item."""
value = value.strip()
if not value:
Expand Down Expand Up @@ -503,21 +504,21 @@ class Meta:
"sw_missing_only",
]

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
qs_filter = Q(device__name__icontains=value) | Q(software__version__icontains=value)
return queryset.filter(qs_filter)

def _exclude_sw_missing(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def _exclude_sw_missing(self, queryset, name, value): # pylint: disable=unused-argument
"""Exclude devices with missing software."""
if value:
return queryset.filter(~Q(software=None))

return queryset

def _sw_missing_only(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def _sw_missing_only(self, queryset, name, value): # pylint: disable=unused-argument
"""Only show devices with missing software."""
if value:
return queryset.filter(Q(software=None))
Expand Down Expand Up @@ -640,7 +641,7 @@ class Meta:
"sw_missing_only",
]

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
Expand All @@ -651,21 +652,21 @@ def search(self, queryset, name, value): # pylint: disable=unused-argument, no-
)
return queryset.filter(qs_filter)

def search_part_id(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search_part_id(self, queryset, name, value): # pylint: disable=unused-argument
"""Filter on the inventory item part ID."""
if not value.strip():
return queryset
qs_filter = Q(inventory_item__part_id__icontains=value)
return queryset.filter(qs_filter)

def _exclude_sw_missing(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def _exclude_sw_missing(self, queryset, name, value): # pylint: disable=unused-argument
"""Exclude devices with missing software."""
if value:
return queryset.filter(~Q(software=None))

return queryset

def _sw_missing_only(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def _sw_missing_only(self, queryset, name, value): # pylint: disable=unused-argument
"""Only show devices with missing software."""
if value:
return queryset.filter(Q(software=None))
Expand Down Expand Up @@ -710,7 +711,7 @@ class Meta:
"currency",
]

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
Expand All @@ -723,7 +724,7 @@ def search(self, queryset, name, value): # pylint: disable=unused-argument, no-
)
return queryset.filter(qs_filter)

def expired_search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def expired_search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
today = datetime.datetime.today().date()
lookup = "gte" if not value else "lt"
Expand All @@ -744,7 +745,7 @@ class Meta:

fields = ProviderLCM.csv_headers

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
Expand All @@ -771,7 +772,7 @@ class Meta:

fields = ContactLCM.csv_headers

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
Expand Down Expand Up @@ -814,7 +815,7 @@ class Meta:

fields = CVELCM.csv_headers

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
Expand Down Expand Up @@ -843,7 +844,7 @@ class Meta:

fields = VulnerabilityLCM.csv_headers

def search(self, queryset, name, value): # pylint: disable=unused-argument, no-self-use
def search(self, queryset, name, value): # pylint: disable=unused-argument
"""Perform the filtered search."""
if not value.strip():
return queryset
Expand Down
1 change: 0 additions & 1 deletion nautobot_device_lifecycle_mgmt/jobs/cve_tracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def run(self, data, commit): # pylint: disable=too-many-locals
self.log_info(obj=cve, message="Generating vulnerabilities for CVE {cve}")
software_rels = RelationshipAssociation.objects.filter(relationship__slug="soft_cve", destination_id=cve.id)
for soft_rel in software_rels:

# Loop through any device relationships
device_rels = soft_rel.source.get_relationships()["source"][
Relationship.objects.get(slug="device_soft")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


class Migration(migrations.Migration):

initial = True

dependencies = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


class Migration(migrations.Migration):

initial = True

dependencies = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


class Migration(migrations.Migration):

dependencies = [
("nautobot_device_lifecycle_mgmt", "0002_softwarelcm"),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


class Migration(migrations.Migration):

dependencies = [
("dcim", "0005_device_local_context_schema"),
("extras", "0013_default_fallback_value_computedfield"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


class Migration(migrations.Migration):

dependencies = [
("dcim", "0005_device_local_context_schema"),
("extras", "0013_default_fallback_value_computedfield"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@


class Migration(migrations.Migration):

dependencies = [
("extras", "0013_default_fallback_value_computedfield"),
("dcim", "0005_device_local_context_schema"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@


class Migration(migrations.Migration):

dependencies = [
("dcim", "0005_device_local_context_schema"),
("extras", "0013_default_fallback_value_computedfield"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ def migrate_software_images(apps, schema_editor):


class Migration(migrations.Migration):

dependencies = [
("nautobot_device_lifecycle_mgmt", "0007_softwareimagelcm"),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


class Migration(migrations.Migration):

dependencies = [
("nautobot_device_lifecycle_mgmt", "0008_software_image_data_migration"),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


class Migration(migrations.Migration):

dependencies = [
("nautobot_device_lifecycle_mgmt", "0009_software_remove_image_fields"),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@


class Migration(migrations.Migration):

dependencies = [
("nautobot_device_lifecycle_mgmt", "0010_softwareimagelcm_hash_algorithm"),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@


class Migration(migrations.Migration):

dependencies = [
("nautobot_device_lifecycle_mgmt", "0011_add_valid_software_field_to_result"),
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@


class Migration(migrations.Migration):

dependencies = [
("dcim", "0016_device_components__timestamp_data_migration"),
("nautobot_device_lifecycle_mgmt", "0012_add_related_name_to_results_model"),
Expand Down
4 changes: 2 additions & 2 deletions nautobot_device_lifecycle_mgmt/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ def delete_inventory_item_software_relationship(sender, instance, **kwargs): #
@receiver(pre_delete, sender="nautobot_device_lifecycle_mgmt.SoftwareLCM")
def delete_software_to_cve_relationships(sender, instance, **kwargs): # pylint: disable=unused-argument
"""Delete all SoftwareLCM relationships to CVELCM objects."""
soft_relationships = Relationship.objects.filter(slug__in=("cve_soft"))
soft_relationships = Relationship.objects.filter(slug__in=("cve_soft",))
RelationshipAssociation.objects.filter(relationship__in=soft_relationships, source_id=instance.pk).delete()


@receiver(pre_delete, sender="nautobot_device_lifecycle_mgmt.CVELCM")
def delete_cve_to_software_relationships(sender, instance, **kwargs): # pylint: disable=unused-argument
"""Delete all CVELCM relationships to SoftwareLCM objects."""
soft_relationships = Relationship.objects.filter(slug__in=("cve_soft"))
soft_relationships = Relationship.objects.filter(slug__in=("cve_soft",))
RelationshipAssociation.objects.filter(relationship__in=soft_relationships, source_id=instance.pk).delete()
1 change: 1 addition & 0 deletions nautobot_device_lifecycle_mgmt/software_filters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pylint: disable=unsupported-binary-operation
"""Filters for Software Lifecycle QuerySets."""

from django.contrib.contenttypes.models import ContentType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
<h2>Contract: {% block title %}{{ object }}{% endblock %}</h2>
{% endblock masthead %}

{% block extra_nav_tabs %}
<li role="presentation"{% if active_tab == 'devices-under-contract' %} class="active"{% endif %}>
<a href="{% url 'plugins:nautobot_device_lifecycle_mgmt:contract_devices' pk=object.pk %}">Contract devices</a>
</li>
<li role="presentation"{% if active_tab == 'inventoryitems-under-contract' %} class="active"{% endif %}>
<a href="{% url 'plugins:nautobot_device_lifecycle_mgmt:contract_inventoryitems' pk=object.pk %}">Contract inventory items</a>
</li>
{% endblock %}

{% block content_left_page %}
<div class="panel panel-default">
<div class="panel-heading">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{% extends 'nautobot_device_lifecycle_mgmt/contractlcm.html' %}
{% block title %}{{ block.super }} - Devices{% endblock %}

{% block content %}
<div class="col-md-12">
{% include table_template|default:'responsive_table.html' %}
<div class="pull-left noprint">
<a href="{% url 'plugins:nautobot_device_lifecycle_mgmt:contractlcm' pk=object.pk %}?export_contract=devices" class="btn btn-success btn-xs">
<span class="mdi mdi-database-export" aria-hidden="true">Export</span>
</a>
</div>
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
<div class="clearfix"></div>
</div>
{% endblock %}
Loading
Loading