Skip to content

Commit

Permalink
Merge pull request #217 from nautobot/contract-reporting
Browse files Browse the repository at this point in the history
New reporting tabs and exports for Contract view
  • Loading branch information
bradh11 authored Oct 31, 2023
2 parents 94a0e03 + 481ce14 commit 5d2b7bf
Show file tree
Hide file tree
Showing 28 changed files with 1,893 additions and 1,373 deletions.
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

0 comments on commit 5d2b7bf

Please sign in to comment.