From d48a68317d395538924c6d63473acb4ec0700be1 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Oct 2021 08:41:28 -0400 Subject: [PATCH 01/27] Fixes #7612: Strip HTML from custom field descriptions --- docs/release-notes/version-3.0.md | 4 ++++ netbox/extras/models/customfields.py | 3 ++- netbox/templates/inc/custom_fields_panel.html | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 69d8b8456..476c185ae 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -2,6 +2,10 @@ ## v3.0.9 (FUTURE) +### Bug Fixes + +* [#7612](https://github.com/netbox-community/netbox/issues/7612) - Strip HTML from custom field descriptions + --- ## v3.0.8 (2021-10-20) diff --git a/netbox/extras/models/customfields.py b/netbox/extras/models/customfields.py index d8e2e11c9..2f5f2fc6b 100644 --- a/netbox/extras/models/customfields.py +++ b/netbox/extras/models/customfields.py @@ -7,6 +7,7 @@ from django.contrib.postgres.fields import ArrayField from django.core.validators import RegexValidator, ValidationError from django.db import models from django.urls import reverse +from django.utils.html import strip_tags from django.utils.safestring import mark_safe from extras.choices import * @@ -287,7 +288,7 @@ class CustomField(ChangeLoggedModel): field.model = self field.label = str(self) if self.description: - field.help_text = self.description + field.help_text = strip_tags(self.description) return field diff --git a/netbox/templates/inc/custom_fields_panel.html b/netbox/templates/inc/custom_fields_panel.html index fd0379961..ed673db08 100644 --- a/netbox/templates/inc/custom_fields_panel.html +++ b/netbox/templates/inc/custom_fields_panel.html @@ -8,7 +8,7 @@ {% for field, value in custom_fields.items %} - + + + + @@ -45,18 +42,25 @@ {% endif %} + + +
{{ field }}{{ field }} {% if field.type == 'boolean' and value == True %} From b56cae24c5e00a8861b97f13afb6e12e7b19f53f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Oct 2021 09:04:18 -0400 Subject: [PATCH 02/27] Fixes #7628: Fix load_yaml method for custom scripts --- docs/release-notes/version-3.0.md | 1 + netbox/extras/scripts.py | 8 +++-- netbox/extras/tests/test_scripts.py | 46 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 476c185ae..7de296936 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -5,6 +5,7 @@ ### Bug Fixes * [#7612](https://github.com/netbox-community/netbox/issues/7612) - Strip HTML from custom field descriptions +* [#7628](https://github.com/netbox-community/netbox/issues/7628) - Fix `load_yaml` method for custom scripts --- diff --git a/netbox/extras/scripts.py b/netbox/extras/scripts.py index fd84747b9..9c46278ae 100644 --- a/netbox/extras/scripts.py +++ b/netbox/extras/scripts.py @@ -4,7 +4,6 @@ import logging import os import pkgutil import traceback -import warnings from collections import OrderedDict import yaml @@ -345,9 +344,14 @@ class BaseScript: """ Return data from a YAML file """ + try: + from yaml import CLoader as Loader + except ImportError: + from yaml import Loader + file_path = os.path.join(settings.SCRIPTS_ROOT, filename) with open(file_path, 'r') as datafile: - data = yaml.load(datafile) + data = yaml.load(datafile, Loader=Loader) return data diff --git a/netbox/extras/tests/test_scripts.py b/netbox/extras/tests/test_scripts.py index 4518548d3..64971f1dc 100644 --- a/netbox/extras/tests/test_scripts.py +++ b/netbox/extras/tests/test_scripts.py @@ -1,3 +1,5 @@ +import tempfile + from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from netaddr import IPAddress, IPNetwork @@ -11,6 +13,50 @@ CHOICES = ( ('0000ff', 'Blue') ) +YAML_DATA = """ +Foo: 123 +Bar: 456 +Baz: + - A + - B + - C +""" + +JSON_DATA = """ +{ + "Foo": 123, + "Bar": 456, + "Baz": ["A", "B", "C"] +} +""" + + +class ScriptTest(TestCase): + + def test_load_yaml(self): + datafile = tempfile.NamedTemporaryFile() + datafile.write(bytes(YAML_DATA, 'UTF-8')) + datafile.seek(0) + + data = Script().load_yaml(datafile.name) + self.assertEqual(data, { + 'Foo': 123, + 'Bar': 456, + 'Baz': ['A', 'B', 'C'], + }) + + def test_load_json(self): + datafile = tempfile.NamedTemporaryFile() + datafile.write(bytes(JSON_DATA, 'UTF-8')) + datafile.seek(0) + + data = Script().load_json(datafile.name) + self.assertEqual(data, { + 'Foo': 123, + 'Bar': 456, + 'Baz': ['A', 'B', 'C'], + }) + class ScriptVariablesTest(TestCase): From 87779b7b88ff09b6e803a440b7a486f673c28d4c Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Oct 2021 09:44:15 -0400 Subject: [PATCH 03/27] Fixes #7644: Prevent inadvertent deletion of prior change records when deleting objects (#7333 revisited) --- docs/release-notes/version-3.0.md | 1 + netbox/netbox/models.py | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 7de296936..ffaa75d77 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -6,6 +6,7 @@ * [#7612](https://github.com/netbox-community/netbox/issues/7612) - Strip HTML from custom field descriptions * [#7628](https://github.com/netbox-community/netbox/issues/7628) - Fix `load_yaml` method for custom scripts +* [#7644](https://github.com/netbox-community/netbox/issues/7644) - Prevent inadvertent deletion of prior change records when deleting objects (#7333 revisited) --- diff --git a/netbox/netbox/models.py b/netbox/netbox/models.py index 317548921..3045e1fce 100644 --- a/netbox/netbox/models.py +++ b/netbox/netbox/models.py @@ -40,11 +40,6 @@ class ChangeLoggingMixin(models.Model): blank=True, null=True ) - object_changes = GenericRelation( - to='extras.ObjectChange', - content_type_field='changed_object_type', - object_id_field='changed_object_id' - ) class Meta: abstract = True From 98ca4f5b5a3b8098f6bde208b9807d508bc941bb Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Oct 2021 10:02:36 -0400 Subject: [PATCH 04/27] Fixes #7643: Fix circuit assignment when creating multiple terminations simultaneously --- docs/release-notes/version-3.0.md | 1 + netbox/circuits/signals.py | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index ffaa75d77..5b6bf9855 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -6,6 +6,7 @@ * [#7612](https://github.com/netbox-community/netbox/issues/7612) - Strip HTML from custom field descriptions * [#7628](https://github.com/netbox-community/netbox/issues/7628) - Fix `load_yaml` method for custom scripts +* [#7643](https://github.com/netbox-community/netbox/issues/7643) - Fix circuit assignment when creating multiple terminations simultaneously * [#7644](https://github.com/netbox-community/netbox/issues/7644) - Prevent inadvertent deletion of prior change records when deleting objects (#7333 revisited) --- diff --git a/netbox/circuits/signals.py b/netbox/circuits/signals.py index a12cef671..6ec9cc6c3 100644 --- a/netbox/circuits/signals.py +++ b/netbox/circuits/signals.py @@ -11,6 +11,7 @@ def update_circuit(instance, **kwargs): When a CircuitTermination has been modified, update its parent Circuit. """ termination_name = f'termination_{instance.term_side.lower()}' + instance.circuit.refresh_from_db() setattr(instance.circuit, termination_name, instance) instance.circuit.save() From e84f2e3ad2934bbbb7810908394d4ee2e0e9b71d Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Oct 2021 10:10:14 -0400 Subject: [PATCH 05/27] Fixes #7601: Correct devices count for locations within global search results --- docs/release-notes/version-3.0.md | 1 + netbox/netbox/constants.py | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 5b6bf9855..cba7e56f1 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#7601](https://github.com/netbox-community/netbox/issues/7601) - Correct devices count for locations within global search results * [#7612](https://github.com/netbox-community/netbox/issues/7612) - Strip HTML from custom field descriptions * [#7628](https://github.com/netbox-community/netbox/issues/7628) - Fix `load_yaml` method for custom scripts * [#7643](https://github.com/netbox-community/netbox/issues/7643) - Fix circuit assignment when creating multiple terminations simultaneously diff --git a/netbox/netbox/constants.py b/netbox/netbox/constants.py index ec6daa021..3e935e722 100644 --- a/netbox/netbox/constants.py +++ b/netbox/netbox/constants.py @@ -69,7 +69,13 @@ SEARCH_TYPES = OrderedDict(( }), ('location', { 'queryset': Location.objects.add_related_count( - Location.objects.all(), + Location.objects.add_related_count( + Location.objects.all(), + Device, + 'location', + 'device_count', + cumulative=True + ), Rack, 'location', 'rack_count', From dfdeac496832820917aa18993ee52e7a44fe00b5 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Wed, 27 Oct 2021 10:20:17 -0400 Subject: [PATCH 06/27] Fixes #7647: Require interface assignment when designating IP address as primary for device/VM during CSV import --- docs/release-notes/version-3.0.md | 1 + netbox/ipam/forms/bulk_import.py | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index cba7e56f1..32d40ada3 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -9,6 +9,7 @@ * [#7628](https://github.com/netbox-community/netbox/issues/7628) - Fix `load_yaml` method for custom scripts * [#7643](https://github.com/netbox-community/netbox/issues/7643) - Fix circuit assignment when creating multiple terminations simultaneously * [#7644](https://github.com/netbox-community/netbox/issues/7644) - Prevent inadvertent deletion of prior change records when deleting objects (#7333 revisited) +* [#7647](https://github.com/netbox-community/netbox/issues/7647) - Require interface assignment when designating IP address as primary for device/VM during CSV import --- diff --git a/netbox/ipam/forms/bulk_import.py b/netbox/ipam/forms/bulk_import.py index 49d5014f9..3d7c829d6 100644 --- a/netbox/ipam/forms/bulk_import.py +++ b/netbox/ipam/forms/bulk_import.py @@ -257,11 +257,18 @@ class IPAddressCSVForm(CustomFieldModelCSVForm): device = self.cleaned_data.get('device') virtual_machine = self.cleaned_data.get('virtual_machine') + interface = self.cleaned_data.get('interface') is_primary = self.cleaned_data.get('is_primary') # Validate is_primary if is_primary and not device and not virtual_machine: - raise forms.ValidationError("No device or virtual machine specified; cannot set as primary IP") + raise forms.ValidationError({ + "is_primary": "No device or virtual machine specified; cannot set as primary IP" + }) + if is_primary and not interface: + raise forms.ValidationError({ + "is_primary": "No interface specified; cannot set as primary IP" + }) def save(self, *args, **kwargs): From a09095591824891f8f608daeedc1cf5426516515 Mon Sep 17 00:00:00 2001 From: thatmattlove Date: Wed, 27 Oct 2021 11:34:21 -0700 Subject: [PATCH 07/27] Fixes #7599: Improve color mode handling --- docs/release-notes/version-3.0.md | 1 + netbox/templates/base/base.html | 121 ++++++++++++++++++------------ 2 files changed, 73 insertions(+), 49 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 32d40ada3..772435f4c 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -4,6 +4,7 @@ ### Bug Fixes +* [#7599](https://github.com/netbox-community/netbox/issues/7599) - Improve color mode preference handling * [#7601](https://github.com/netbox-community/netbox/issues/7601) - Correct devices count for locations within global search results * [#7612](https://github.com/netbox-community/netbox/issues/7612) - Strip HTML from custom field descriptions * [#7628](https://github.com/netbox-community/netbox/issues/7628) - Fix `load_yaml` method for custom scripts diff --git a/netbox/templates/base/base.html b/netbox/templates/base/base.html index 78dc2b744..43e5c2dfb 100644 --- a/netbox/templates/base/base.html +++ b/netbox/templates/base/base.html @@ -27,55 +27,78 @@ {% block title %}Home{% endblock %} | NetBox {# Static resources #} From 0a62f75a401a5282b6a77140c7a6270088c320e1 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Thu, 28 Oct 2021 15:14:42 -0500 Subject: [PATCH 08/27] #6529 - Add CLI to run scripts --- .../extras/management/commands/runscript.py | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 netbox/extras/management/commands/runscript.py diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py new file mode 100644 index 000000000..36da9d719 --- /dev/null +++ b/netbox/extras/management/commands/runscript.py @@ -0,0 +1,160 @@ +import json +import logging +import sys +import traceback +import uuid + +from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.core.management.base import BaseCommand +from django.db import transaction + +from extras.api.serializers import ScriptOutputSerializer +from extras.choices import JobResultStatusChoices +from extras.context_managers import change_logging +from extras.models import JobResult +from extras.scripts import get_scripts +from utilities.exceptions import AbortTransaction +from utilities.utils import NetBoxFakeRequest + + +class Command(BaseCommand): + help = "Run a script in Netbox" + + def add_arguments(self, parser): + parser.add_argument( + '--loglevel', + help="Logging Level (default: info)", + dest='loglevel', + default='info', + choices=['debug', 'info', 'warning', 'error']) + parser.add_argument('--script', help="Script to run", dest='script') + parser.add_argument('--user', help="Data as a json blob", dest='user') + parser.add_argument('data', help="Data as a json blob") + + @staticmethod + def _get_script(module, name): + scripts = get_scripts() + return scripts[module][name]() + + def handle(self, *args, **options): + def _run_script(): + """ + Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with + the change_logging context manager (which is bypassed if commit == False). + """ + try: + with transaction.atomic(): + script.output = script.run(data=data, commit=commit) + job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED) + + if not commit: + raise AbortTransaction() + + except AbortTransaction: + script.log_info("Database changes have been reverted automatically.") + + except Exception as e: + stacktrace = traceback.format_exc() + script.log_failure( + f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```" + ) + script.log_info("Database changes have been reverted due to error.") + logger.error(f"Exception raised during script execution: {e}") + job_result.set_status(JobResultStatusChoices.STATUS_ERRORED) + + finally: + job_result.data = ScriptOutputSerializer(script).data + job_result.save() + + logger.info(f"Script completed in {job_result.duration}") + + # Params + script = options['script'] + loglevel = options['loglevel'] + data = json.loads(options['data']) + + module, name = script.split('.', 1) + + # Take user from command line if provided and exists, other + if options['user']: + try: + user = User.objects.get(username=options['user']) + except User.DoesNotExist: + user = User.objects.filter(is_superuser=True).order_by('pk')[0] + else: + user = User.objects.filter(is_superuser=True).order_by('pk')[0] + + # Setup logging to Stdout + formatter = logging.Formatter(f'[%(asctime)s][%(levelname)s] - %(message)s') + stdouthandler = logging.StreamHandler(sys.stdout) + stdouthandler.setLevel(logging.DEBUG) + stdouthandler.setFormatter(formatter) + + logger = logging.getLogger(f"netbox.scripts.{module}.{name}") + logger.addHandler(stdouthandler) + + if loglevel == 'debug': + logger.setLevel(logging.DEBUG) + elif loglevel == 'info': + logger.setLevel(logging.INFO) + elif loglevel == 'warning': + logger.setLevel(logging.WARNING) + elif loglevel == 'error': + logger.setLevel(logging.ERROR) + else: + logger.setLevel(logging.INFO) + + # Get the script + script = self._get_script(module, name) + # Parse the parameters + form = script.as_form(data, None) + + script_content_type = ContentType.objects.get(app_label='extras', model='script') + + # Create the job result + job_result = JobResult.objects.create( + name=script.full_name, + obj_type=script_content_type, + user=User.objects.filter(is_superuser=True).order_by('pk')[0], + job_id=uuid.uuid4() + ) + + request = NetBoxFakeRequest({ + 'META': {}, + 'POST': data, + 'GET': {}, + 'FILES': {}, + 'user': user, + 'path': '', + 'id': job_result.job_id + }) + + if form.is_valid(): + job_result.status = JobResultStatusChoices.STATUS_RUNNING + job_result.save() + + commit = form.cleaned_data.pop('_commit') + + logger.info(f"Running script (commit={commit})") + script.request = request + + # Execute the script. If commit is True, wrap it with the change_logging context manager to ensure we process + # change logging, webhooks, etc. + if commit: + with change_logging(request): + _run_script() + else: + _run_script() + + # Delete any previous terminal state results + JobResult.objects.filter( + obj_type=job_result.obj_type, + name=job_result.name, + status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES + ).exclude( + pk=job_result.pk + ).delete() + else: + job_result.status = JobResultStatusChoices.STATUS_ERRORED + job_result.save() From 24d6941cc40e4a537878c83d73d4c45db6dc69ad Mon Sep 17 00:00:00 2001 From: Rhys Barrie Date: Fri, 29 Oct 2021 14:07:02 -0400 Subject: [PATCH 09/27] netbox-community/netbox#7668: Add 'View Elevations' button to location detail page --- netbox/templates/dcim/location.html | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index b062ddcb5..0184947f6 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -9,6 +9,12 @@ {% endfor %} {% endblock %} +{% block extra_controls %} + +  View Elevations + +{% endblock %} + {% block content %}
From b5e81577001f04f489e9fb000d4229e7b7cc9733 Mon Sep 17 00:00:00 2001 From: bluikko <14869000+bluikko@users.noreply.github.com> Date: Mon, 1 Nov 2021 20:13:18 +0700 Subject: [PATCH 10/27] Fix #7685: Doc image links (#7698) * Fix image link in custom-script.md * Fix image link in cable.md * Fix image link in power.md --- docs/core-functionality/power.md | 2 +- docs/customization/custom-scripts.md | 2 +- docs/models/dcim/cable.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core-functionality/power.md b/docs/core-functionality/power.md index bdefb2afd..4d7d5f0ab 100644 --- a/docs/core-functionality/power.md +++ b/docs/core-functionality/power.md @@ -5,4 +5,4 @@ # Example Power Topology -![Power distribution model](/media/power_distribution.png) +![Power distribution model](../media/power_distribution.png) diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md index cf052f918..9263f387c 100644 --- a/docs/customization/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -240,7 +240,7 @@ An IPv4 or IPv6 network with a mask. Returns a `netaddr.IPNetwork` object. Two a !!! note To run a custom script, a user must be assigned the `extras.run_script` permission. This is achieved by assigning the user (or group) a permission on the Script object and specifying the `run` action in the admin UI as shown below. - ![Adding the run action to a permission](/media/admin_ui_run_permission.png) + ![Adding the run action to a permission](../media/admin_ui_run_permission.png) ### Via the Web UI diff --git a/docs/models/dcim/cable.md b/docs/models/dcim/cable.md index 18ac1002e..87ec68e03 100644 --- a/docs/models/dcim/cable.md +++ b/docs/models/dcim/cable.md @@ -25,7 +25,7 @@ A cable may be traced from either of its endpoints by clicking the "trace" butto In the example below, three individual cables comprise a path between devices A and D: -![Cable path](/media/models/dcim_cable_trace.png) +![Cable path](../media/models/dcim_cable_trace.png) Traced from Interface 1 on Device A, NetBox will show the following path: From d528614cbf5673918956b6c597a1910f6adbd3bd Mon Sep 17 00:00:00 2001 From: Rhys Barrie Date: Mon, 1 Nov 2021 11:17:15 -0400 Subject: [PATCH 11/27] netbox-community/netbox#7668: Relocate elevations button to location details table --- netbox/templates/dcim/location.html | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index 0184947f6..0c254e413 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -9,12 +9,6 @@ {% endfor %} {% endblock %} -{% block extra_controls %} - -  View Elevations - -{% endblock %} - {% block content %}
@@ -27,14 +21,17 @@
Name {{ object.name }}
Description {{ object.description|placeholder }}
Site {{ object.site }}
Parent
Racks {{ rack_count }} + + + +
Devices {{ device_count }}
From a5ec0ee27794290a4b1219310b1e69f02c221e3d Mon Sep 17 00:00:00 2001 From: cybarox Date: Tue, 2 Nov 2021 15:14:08 +0100 Subject: [PATCH 12/27] Closes #7717 Missing tags column in IP range table --- netbox/ipam/tables/ip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index ddad6c573..1323f551f 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -269,7 +269,7 @@ class IPRangeTable(BaseTable): model = IPRange fields = ( 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', - 'utilization', + 'utilization','tags', ) default_columns = ( 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', From b7c0e8b71f42a8bf5477c429c78bce75d7ae1ca5 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Tue, 2 Nov 2021 13:12:12 -0500 Subject: [PATCH 13/27] #6529 - Streamline code and resolve some issues --- .../extras/management/commands/runscript.py | 71 +++++++++---------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/netbox/extras/management/commands/runscript.py b/netbox/extras/management/commands/runscript.py index 36da9d719..8a6dbc989 100644 --- a/netbox/extras/management/commands/runscript.py +++ b/netbox/extras/management/commands/runscript.py @@ -6,14 +6,14 @@ import uuid from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand, CommandError from django.db import transaction from extras.api.serializers import ScriptOutputSerializer from extras.choices import JobResultStatusChoices from extras.context_managers import change_logging from extras.models import JobResult -from extras.scripts import get_scripts +from extras.scripts import get_script from utilities.exceptions import AbortTransaction from utilities.utils import NetBoxFakeRequest @@ -27,15 +27,11 @@ class Command(BaseCommand): help="Logging Level (default: info)", dest='loglevel', default='info', - choices=['debug', 'info', 'warning', 'error']) - parser.add_argument('--script', help="Script to run", dest='script') - parser.add_argument('--user', help="Data as a json blob", dest='user') - parser.add_argument('data', help="Data as a json blob") - - @staticmethod - def _get_script(module, name): - scripts = get_scripts() - return scripts[module][name]() + choices=['debug', 'info', 'warning', 'error', 'critical']) + parser.add_argument('--script', help="Script to run", dest='script', required=True) + parser.add_argument('--commit', help="Commit this script to database", dest='commit') + parser.add_argument('--user', help="User script is running as", dest='user') + parser.add_argument('data', help="Data as a JSON blob") def handle(self, *args, **options): def _run_script(): @@ -72,7 +68,8 @@ class Command(BaseCommand): # Params script = options['script'] loglevel = options['loglevel'] - data = json.loads(options['data']) + data = json.loads(options['data']) if options['data'] is not None else None + commit = True if options['commit'] in ['1', 'true', 'True'] else False module, name = script.split('.', 1) @@ -94,24 +91,32 @@ class Command(BaseCommand): logger = logging.getLogger(f"netbox.scripts.{module}.{name}") logger.addHandler(stdouthandler) - if loglevel == 'debug': - logger.setLevel(logging.DEBUG) - elif loglevel == 'info': - logger.setLevel(logging.INFO) - elif loglevel == 'warning': - logger.setLevel(logging.WARNING) - elif loglevel == 'error': - logger.setLevel(logging.ERROR) - else: - logger.setLevel(logging.INFO) + try: + logger.setLevel({ + 'critical': logging.CRITICAL, + 'debug': logging.DEBUG, + 'error': logging.ERROR, + 'fatal': logging.FATAL, + 'info': logging.INFO, + 'warning': logging.WARNING, + }[loglevel]) + except KeyError: + raise CommandError(f"Invalid log level: {loglevel}") # Get the script - script = self._get_script(module, name) + script = get_script(module, name)() # Parse the parameters form = script.as_form(data, None) script_content_type = ContentType.objects.get(app_label='extras', model='script') + # Delete any previous terminal state results + JobResult.objects.filter( + obj_type=script_content_type, + name=script.full_name, + status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES + ).delete() + # Create the job result job_result = JobResult.objects.create( name=script.full_name, @@ -134,27 +139,17 @@ class Command(BaseCommand): job_result.status = JobResultStatusChoices.STATUS_RUNNING job_result.save() - commit = form.cleaned_data.pop('_commit') - logger.info(f"Running script (commit={commit})") script.request = request # Execute the script. If commit is True, wrap it with the change_logging context manager to ensure we process # change logging, webhooks, etc. - if commit: - with change_logging(request): - _run_script() - else: + with change_logging(request): _run_script() - - # Delete any previous terminal state results - JobResult.objects.filter( - obj_type=job_result.obj_type, - name=job_result.name, - status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES - ).exclude( - pk=job_result.pk - ).delete() else: + logger.error('Data is not valid:') + for field, errors in form.errors.get_json_data().items(): + for error in errors: + logger.error(f'\t{field}: {error.get("message")}') job_result.status = JobResultStatusChoices.STATUS_ERRORED job_result.save() From 3a192223a38a8d6c4a833405b0ae73579585b4b9 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 2 Nov 2021 15:58:14 -0400 Subject: [PATCH 14/27] Changelog & cleanup for #7668, #7717 --- docs/release-notes/version-3.0.md | 5 +++++ netbox/ipam/tables/ip.py | 2 +- netbox/templates/dcim/location.html | 17 +++++++---------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index 772435f4c..c9f58d98f 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -2,6 +2,10 @@ ## v3.0.9 (FUTURE) +### Enhancements + +* [#7668](https://github.com/netbox-community/netbox/issues/7668) - Add "view elevations" button to location view + ### Bug Fixes * [#7599](https://github.com/netbox-community/netbox/issues/7599) - Improve color mode preference handling @@ -11,6 +15,7 @@ * [#7643](https://github.com/netbox-community/netbox/issues/7643) - Fix circuit assignment when creating multiple terminations simultaneously * [#7644](https://github.com/netbox-community/netbox/issues/7644) - Prevent inadvertent deletion of prior change records when deleting objects (#7333 revisited) * [#7647](https://github.com/netbox-community/netbox/issues/7647) - Require interface assignment when designating IP address as primary for device/VM during CSV import +* [#7717](https://github.com/netbox-community/netbox/issues/7717) - Restore missing tags column on IP range table --- diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 1323f551f..85f5936e1 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -269,7 +269,7 @@ class IPRangeTable(BaseTable): model = IPRange fields = ( 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', - 'utilization','tags', + 'utilization', 'tags', ) default_columns = ( 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', diff --git a/netbox/templates/dcim/location.html b/netbox/templates/dcim/location.html index 0c254e413..7d5598bbc 100644 --- a/netbox/templates/dcim/location.html +++ b/netbox/templates/dcim/location.html @@ -21,17 +21,14 @@ Name {{ object.name }} - Description {{ object.description|placeholder }} - Site {{ object.site }} - Parent @@ -42,25 +39,25 @@ {% endif %} - Racks + {% if rack_count %} +
+ + + +
+ {% endif %} {{ rack_count }} - - - - - Devices {{ device_count }} - From cef0d168a5f0021219d0c8e34675ec05dc0fc83a Mon Sep 17 00:00:00 2001 From: Rhys Barrie Date: Tue, 2 Nov 2021 16:21:34 -0400 Subject: [PATCH 15/27] Closes #6930: Add 'ID' column to object tables (#7673) * netbox-community/netbox#6930: Add ID column to devices, device types, and components * netbox-community/netbox#6930: Add ID column to sites, racks, and tenants * netbox-community/netbox#6930: Add ID column to power, providers, TODO circuits * netbox-community/netbox#6930: Add ID column to virtualization tables * netbox-community/netbox#6930: Add ID column to IPAM tables * netbox-community/netbox#6930: Add ID column to 'extras' tables * netbox-community/netbox#6930: Move ID column to BaseTable class * netbox-community/netbox#6930: Don't linkify ID in device component template tables * netbox-community/netbox#6930: Don't show ID column in interface/console/power connections tables * netbox-community/netbox#6930: Don't show ID column in device component template tables * netbox-community/netbox#6930: Add ID column to ObjectJournal, DeviceImport, and Circuit tables * Exclude ID column from selected tables * netbox-community/netbox#6930:revert default columns on ObjectChangeTable, not configurable * netbox-community/netbox#6930: Add object ID to tagged objects table in tag detail view Co-authored-by: Jeremy Stretch --- netbox/circuits/tables.py | 10 +++---- netbox/dcim/tables/__init__.py | 3 ++ netbox/dcim/tables/cables.py | 4 --- netbox/dcim/tables/devices.py | 46 +++++++++++++++---------------- netbox/dcim/tables/devicetypes.py | 27 ++++++++++++------ netbox/dcim/tables/power.py | 4 +-- netbox/dcim/tables/racks.py | 6 ++-- netbox/dcim/tables/sites.py | 8 +++--- netbox/extras/tables.py | 36 ++++++++++++++++-------- netbox/ipam/tables/ip.py | 14 ++++++---- netbox/ipam/tables/services.py | 2 +- netbox/ipam/tables/vlans.py | 7 +++-- netbox/ipam/tables/vrfs.py | 4 +-- netbox/tenancy/tables.py | 4 +-- netbox/utilities/tables.py | 5 ++++ netbox/virtualization/tables.py | 12 ++++---- 16 files changed, 111 insertions(+), 81 deletions(-) diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index 2e31237b6..847d44ffd 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -44,8 +44,8 @@ class ProviderTable(BaseTable): class Meta(BaseTable.Meta): model = Provider fields = ( - 'pk', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count', 'comments', - 'tags', + 'pk', 'id', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count', + 'comments', 'tags', ) default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count') @@ -69,7 +69,7 @@ class ProviderNetworkTable(BaseTable): class Meta(BaseTable.Meta): model = ProviderNetwork - fields = ('pk', 'name', 'provider', 'description', 'comments', 'tags') + fields = ('pk', 'id', 'name', 'provider', 'description', 'comments', 'tags') default_columns = ('pk', 'name', 'provider', 'description') @@ -89,7 +89,7 @@ class CircuitTypeTable(BaseTable): class Meta(BaseTable.Meta): model = CircuitType - fields = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions') + fields = ('pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'actions') default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions') @@ -124,7 +124,7 @@ class CircuitTable(BaseTable): class Meta(BaseTable.Meta): model = Circuit fields = ( - 'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date', + 'pk', 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date', 'commit_rate', 'description', 'comments', 'tags', ) default_columns = ( diff --git a/netbox/dcim/tables/__init__.py b/netbox/dcim/tables/__init__.py index 8a861c8c2..825e60d57 100644 --- a/netbox/dcim/tables/__init__.py +++ b/netbox/dcim/tables/__init__.py @@ -43,6 +43,7 @@ class ConsoleConnectionTable(BaseTable): class Meta(BaseTable.Meta): model = ConsolePort fields = ('device', 'name', 'console_server', 'console_server_port', 'reachable') + exclude = ('id', ) class PowerConnectionTable(BaseTable): @@ -73,6 +74,7 @@ class PowerConnectionTable(BaseTable): class Meta(BaseTable.Meta): model = PowerPort fields = ('device', 'name', 'pdu', 'outlet', 'reachable') + exclude = ('id', ) class InterfaceConnectionTable(BaseTable): @@ -106,3 +108,4 @@ class InterfaceConnectionTable(BaseTable): class Meta(BaseTable.Meta): model = Interface fields = ('device_a', 'interface_a', 'device_b', 'interface_b', 'reachable') + exclude = ('id', ) diff --git a/netbox/dcim/tables/cables.py b/netbox/dcim/tables/cables.py index 14cf34505..5533c4528 100644 --- a/netbox/dcim/tables/cables.py +++ b/netbox/dcim/tables/cables.py @@ -16,10 +16,6 @@ __all__ = ( class CableTable(BaseTable): pk = ToggleColumn() - id = tables.Column( - linkify=True, - verbose_name='ID' - ) termination_a_parent = tables.TemplateColumn( template_code=CABLE_TERMINATION_PARENT, accessor=Accessor('termination_a'), diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index c22e673b7..675f7d777 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -88,7 +88,7 @@ class DeviceRoleTable(BaseTable): class Meta(BaseTable.Meta): model = DeviceRole - fields = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'actions') + fields = ('pk', 'id', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'actions') default_columns = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'actions') @@ -116,7 +116,7 @@ class PlatformTable(BaseTable): class Meta(BaseTable.Meta): model = Platform fields = ( - 'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args', + 'pk', 'id', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args', 'description', 'actions', ) default_columns = ( @@ -196,7 +196,7 @@ class DeviceTable(BaseTable): class Meta(BaseTable.Meta): model = Device fields = ( - 'pk', 'name', 'status', 'tenant', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial', + 'pk', 'id', 'name', 'status', 'tenant', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', ) @@ -227,7 +227,7 @@ class DeviceImportTable(BaseTable): class Meta(BaseTable.Meta): model = Device - fields = ('name', 'status', 'tenant', 'site', 'rack', 'position', 'device_role', 'device_type') + fields = ('id', 'name', 'status', 'tenant', 'site', 'rack', 'position', 'device_role', 'device_type') empty_text = False @@ -290,7 +290,7 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', + 'pk', 'id', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description') @@ -311,7 +311,7 @@ class DeviceConsolePortTable(ConsolePortTable): class Meta(DeviceComponentTable.Meta): model = ConsolePort fields = ( - 'pk', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', + 'pk', 'id', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'actions' ) default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions') @@ -334,7 +334,7 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort fields = ( - 'pk', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', + 'pk', 'id', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description') @@ -356,7 +356,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable): class Meta(DeviceComponentTable.Meta): model = ConsoleServerPort fields = ( - 'pk', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', + 'pk', 'id', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'actions', ) default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions') @@ -379,7 +379,7 @@ class PowerPortTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = PowerPort fields = ( - 'pk', 'name', 'device', 'label', 'type', 'description', 'mark_connected', 'maximum_draw', 'allocated_draw', + 'pk', 'id', 'name', 'device', 'label', 'type', 'description', 'mark_connected', 'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'name', 'device', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description') @@ -401,7 +401,7 @@ class DevicePowerPortTable(PowerPortTable): class Meta(DeviceComponentTable.Meta): model = PowerPort fields = ( - 'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected', 'cable', + 'pk', 'id', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'actions', ) default_columns = ( @@ -430,7 +430,7 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable): class Meta(DeviceComponentTable.Meta): model = PowerOutlet fields = ( - 'pk', 'name', 'device', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected', 'cable', + 'pk', 'id', 'name', 'device', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', ) default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description') @@ -451,7 +451,7 @@ class DevicePowerOutletTable(PowerOutletTable): class Meta(DeviceComponentTable.Meta): model = PowerOutlet fields = ( - 'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', + 'pk', 'id', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'actions', ) default_columns = ( @@ -492,7 +492,7 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable class Meta(DeviceComponentTable.Meta): model = Interface fields = ( - 'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', + 'pk', 'id', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', ) @@ -524,7 +524,7 @@ class DeviceInterfaceTable(InterfaceTable): class Meta(DeviceComponentTable.Meta): model = Interface fields = ( - 'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address', + 'pk', 'id', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions', ) @@ -561,7 +561,7 @@ class FrontPortTable(DeviceComponentTable, CableTerminationTable): class Meta(DeviceComponentTable.Meta): model = FrontPort fields = ( - 'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', + 'pk', 'id', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'tags', ) default_columns = ( @@ -585,7 +585,7 @@ class DeviceFrontPortTable(FrontPortTable): class Meta(DeviceComponentTable.Meta): model = FrontPort fields = ( - 'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', 'cable', + 'pk', 'id', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'tags', 'actions', ) default_columns = ( @@ -612,7 +612,7 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable): class Meta(DeviceComponentTable.Meta): model = RearPort fields = ( - 'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable', + 'pk', 'id', 'name', 'device', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'tags', ) default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description') @@ -634,7 +634,7 @@ class DeviceRearPortTable(RearPortTable): class Meta(DeviceComponentTable.Meta): model = RearPort fields = ( - 'pk', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', 'cable_color', + 'pk', 'id', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'tags', 'actions', ) default_columns = ( @@ -664,7 +664,7 @@ class DeviceBayTable(DeviceComponentTable): class Meta(DeviceComponentTable.Meta): model = DeviceBay - fields = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description', 'tags') + fields = ('pk', 'id', 'name', 'device', 'label', 'status', 'installed_device', 'description', 'tags') default_columns = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description') @@ -684,7 +684,7 @@ class DeviceDeviceBayTable(DeviceBayTable): class Meta(DeviceComponentTable.Meta): model = DeviceBay fields = ( - 'pk', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions', + 'pk', 'id', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions', ) default_columns = ( 'pk', 'name', 'label', 'status', 'installed_device', 'description', 'actions', @@ -710,7 +710,7 @@ class InventoryItemTable(DeviceComponentTable): class Meta(BaseTable.Meta): model = InventoryItem fields = ( - 'pk', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', + 'pk', 'id', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered', 'tags', ) default_columns = ('pk', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag') @@ -731,7 +731,7 @@ class DeviceInventoryItemTable(InventoryItemTable): class Meta(BaseTable.Meta): model = InventoryItem fields = ( - 'pk', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered', + 'pk', 'id', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered', 'tags', 'actions', ) default_columns = ( @@ -763,5 +763,5 @@ class VirtualChassisTable(BaseTable): class Meta(BaseTable.Meta): model = VirtualChassis - fields = ('pk', 'name', 'domain', 'master', 'member_count', 'tags') + fields = ('pk', 'id', 'name', 'domain', 'master', 'member_count', 'tags') default_columns = ('pk', 'name', 'domain', 'master', 'member_count') diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 3b11a180b..d176d3ff6 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -46,6 +46,9 @@ class ManufacturerTable(BaseTable): class Meta(BaseTable.Meta): model = Manufacturer fields = ( + 'pk', 'id', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', 'actions', + ) + default_columns = ( 'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', 'actions', ) @@ -76,7 +79,7 @@ class DeviceTypeTable(BaseTable): class Meta(BaseTable.Meta): model = DeviceType fields = ( - 'pk', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', + 'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role', 'comments', 'instance_count', 'tags', ) default_columns = ( @@ -90,10 +93,16 @@ class DeviceTypeTable(BaseTable): class ComponentTemplateTable(BaseTable): pk = ToggleColumn() + id = tables.Column( + verbose_name='ID' + ) name = tables.Column( order_by=('_name',) ) + class Meta(BaseTable.Meta): + exclude = ('id', ) + class ConsolePortTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( @@ -102,7 +111,7 @@ class ConsolePortTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_consoleports' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = ConsolePortTemplate fields = ('pk', 'name', 'label', 'type', 'description', 'actions') empty_text = "None" @@ -115,7 +124,7 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_consoleserverports' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = ConsoleServerPortTemplate fields = ('pk', 'name', 'label', 'type', 'description', 'actions') empty_text = "None" @@ -128,7 +137,7 @@ class PowerPortTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_powerports' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = PowerPortTemplate fields = ('pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'actions') empty_text = "None" @@ -141,7 +150,7 @@ class PowerOutletTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_poweroutlets' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = PowerOutletTemplate fields = ('pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'actions') empty_text = "None" @@ -157,7 +166,7 @@ class InterfaceTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_interfaces' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = InterfaceTemplate fields = ('pk', 'name', 'label', 'mgmt_only', 'type', 'description', 'actions') empty_text = "None" @@ -174,7 +183,7 @@ class FrontPortTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_frontports' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = FrontPortTemplate fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions') empty_text = "None" @@ -188,7 +197,7 @@ class RearPortTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_rearports' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = RearPortTemplate fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions') empty_text = "None" @@ -201,7 +210,7 @@ class DeviceBayTemplateTable(ComponentTemplateTable): return_url_extra='%23tab_devicebays' ) - class Meta(BaseTable.Meta): + class Meta(ComponentTemplateTable.Meta): model = DeviceBayTemplate fields = ('pk', 'name', 'label', 'description', 'actions') empty_text = "None" diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index b8e032e7f..955b27941 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -33,7 +33,7 @@ class PowerPanelTable(BaseTable): class Meta(BaseTable.Meta): model = PowerPanel - fields = ('pk', 'name', 'site', 'location', 'powerfeed_count', 'tags') + fields = ('pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'tags') default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count') @@ -70,7 +70,7 @@ class PowerFeedTable(CableTerminationTable): class Meta(BaseTable.Meta): model = PowerFeed fields = ( - 'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', + 'pk', 'id', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'max_utilization', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'available_power', 'comments', 'tags', ) diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index fcc3ed4d2..f3d1cb7f8 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -28,7 +28,7 @@ class RackRoleTable(BaseTable): class Meta(BaseTable.Meta): model = RackRole - fields = ('pk', 'name', 'rack_count', 'color', 'description', 'slug', 'actions') + fields = ('pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'actions') default_columns = ('pk', 'name', 'rack_count', 'color', 'description', 'actions') @@ -76,7 +76,7 @@ class RackTable(BaseTable): class Meta(BaseTable.Meta): model = Rack fields = ( - 'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type', + 'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags', ) default_columns = ( @@ -115,7 +115,7 @@ class RackReservationTable(BaseTable): class Meta(BaseTable.Meta): model = RackReservation fields = ( - 'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags', + 'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags', 'actions', ) default_columns = ( diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py index 37fa019a1..56180236d 100644 --- a/netbox/dcim/tables/sites.py +++ b/netbox/dcim/tables/sites.py @@ -33,7 +33,7 @@ class RegionTable(BaseTable): class Meta(BaseTable.Meta): model = Region - fields = ('pk', 'name', 'slug', 'site_count', 'description', 'actions') + fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'actions') default_columns = ('pk', 'name', 'site_count', 'description', 'actions') @@ -55,7 +55,7 @@ class SiteGroupTable(BaseTable): class Meta(BaseTable.Meta): model = SiteGroup - fields = ('pk', 'name', 'slug', 'site_count', 'description', 'actions') + fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'actions') default_columns = ('pk', 'name', 'site_count', 'description', 'actions') @@ -84,7 +84,7 @@ class SiteTable(BaseTable): class Meta(BaseTable.Meta): model = Site fields = ( - 'pk', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn', 'time_zone', 'description', + 'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn', 'time_zone', 'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name', 'contact_phone', 'contact_email', 'comments', 'tags', ) @@ -120,5 +120,5 @@ class LocationTable(BaseTable): class Meta(BaseTable.Meta): model = Location - fields = ('pk', 'name', 'site', 'rack_count', 'device_count', 'description', 'slug', 'actions') + fields = ('pk', 'id', 'name', 'site', 'rack_count', 'device_count', 'description', 'slug', 'actions') default_columns = ('pk', 'name', 'site', 'rack_count', 'device_count', 'description', 'actions') diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index 20a6ffd8a..266f2089a 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -57,8 +57,8 @@ class CustomFieldTable(BaseTable): class Meta(BaseTable.Meta): model = CustomField fields = ( - 'pk', 'name', 'content_types', 'label', 'type', 'required', 'weight', 'default', 'description', - 'filter_logic', 'choices', + 'pk', 'id', 'name', 'content_types', 'label', 'type', 'required', 'weight', 'default', + 'description', 'filter_logic', 'choices', ) default_columns = ('pk', 'name', 'content_types', 'label', 'type', 'required', 'description') @@ -78,7 +78,8 @@ class CustomLinkTable(BaseTable): class Meta(BaseTable.Meta): model = CustomLink fields = ( - 'pk', 'name', 'content_type', 'link_text', 'link_url', 'weight', 'group_name', 'button_class', 'new_window', + 'pk', 'id', 'name', 'content_type', 'link_text', 'link_url', 'weight', 'group_name', + 'button_class', 'new_window', ) default_columns = ('pk', 'name', 'content_type', 'group_name', 'button_class', 'new_window') @@ -98,7 +99,7 @@ class ExportTemplateTable(BaseTable): class Meta(BaseTable.Meta): model = ExportTemplate fields = ( - 'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment', + 'pk', 'id', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment', ) default_columns = ( 'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment', @@ -132,7 +133,7 @@ class WebhookTable(BaseTable): class Meta(BaseTable.Meta): model = Webhook fields = ( - 'pk', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method', + 'pk', 'id', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method', 'payload_url', 'secret', 'ssl_validation', 'ca_file_path', ) default_columns = ( @@ -155,10 +156,16 @@ class TagTable(BaseTable): class Meta(BaseTable.Meta): model = Tag - fields = ('pk', 'name', 'items', 'slug', 'color', 'description', 'actions') + fields = ('pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'actions') + default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description', 'actions') class TaggedItemTable(BaseTable): + id = tables.Column( + verbose_name='ID', + linkify=lambda record: record.content_object.get_absolute_url(), + accessor='content_object__id' + ) content_type = ContentTypeColumn( verbose_name='Type' ) @@ -170,7 +177,7 @@ class TaggedItemTable(BaseTable): class Meta(BaseTable.Meta): model = TaggedItem - fields = ('content_type', 'content_object') + fields = ('id', 'content_type', 'content_object') class ConfigContextTable(BaseTable): @@ -185,8 +192,8 @@ class ConfigContextTable(BaseTable): class Meta(BaseTable.Meta): model = ConfigContext fields = ( - 'pk', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles', 'platforms', - 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', + 'pk', 'id', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles', + 'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', ) default_columns = ('pk', 'name', 'weight', 'is_active', 'description') @@ -211,7 +218,7 @@ class ObjectChangeTable(BaseTable): class Meta(BaseTable.Meta): model = ObjectChange - fields = ('time', 'user_name', 'action', 'changed_object_type', 'object_repr', 'request_id') + fields = ('id', 'time', 'user_name', 'action', 'changed_object_type', 'object_repr', 'request_id') class ObjectJournalTable(BaseTable): @@ -232,7 +239,7 @@ class ObjectJournalTable(BaseTable): class Meta(BaseTable.Meta): model = JournalEntry - fields = ('created', 'created_by', 'kind', 'comments', 'actions') + fields = ('id', 'created', 'created_by', 'kind', 'comments', 'actions') class JournalEntryTable(ObjectJournalTable): @@ -250,5 +257,10 @@ class JournalEntryTable(ObjectJournalTable): class Meta(BaseTable.Meta): model = JournalEntry fields = ( - 'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments', 'actions' + 'pk', 'id', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', + 'comments', 'actions' + ) + default_columns = ( + 'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', + 'comments', 'actions' ) diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 85f5936e1..4ecc8f006 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -89,7 +89,7 @@ class RIRTable(BaseTable): class Meta(BaseTable.Meta): model = RIR - fields = ('pk', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'actions') + fields = ('pk', 'id', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'actions') default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description', 'actions') @@ -121,7 +121,7 @@ class AggregateTable(BaseTable): class Meta(BaseTable.Meta): model = Aggregate - fields = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags') + fields = ('pk', 'id', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags') default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description') @@ -148,7 +148,7 @@ class RoleTable(BaseTable): class Meta(BaseTable.Meta): model = Role - fields = ('pk', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'actions') + fields = ('pk', 'id', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'actions') default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description', 'actions') @@ -230,7 +230,7 @@ class PrefixTable(BaseTable): class Meta(BaseTable.Meta): model = Prefix fields = ( - 'pk', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', + 'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', ) default_columns = ( @@ -268,7 +268,7 @@ class IPRangeTable(BaseTable): class Meta(BaseTable.Meta): model = IPRange fields = ( - 'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', + 'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description', 'utilization', 'tags', ) default_columns = ( @@ -326,7 +326,7 @@ class IPAddressTable(BaseTable): class Meta(BaseTable.Meta): model = IPAddress fields = ( - 'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name', 'description', + 'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name', 'description', 'tags', ) default_columns = ( @@ -350,6 +350,7 @@ class IPAddressAssignTable(BaseTable): class Meta(BaseTable.Meta): model = IPAddress fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'description') + exclude = ('id', ) orderable = False @@ -374,3 +375,4 @@ class InterfaceIPAddressTable(BaseTable): class Meta(BaseTable.Meta): model = IPAddress fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description') + exclude = ('id', ) diff --git a/netbox/ipam/tables/services.py b/netbox/ipam/tables/services.py index 58c8ea49e..ff6b766f7 100644 --- a/netbox/ipam/tables/services.py +++ b/netbox/ipam/tables/services.py @@ -31,5 +31,5 @@ class ServiceTable(BaseTable): class Meta(BaseTable.Meta): model = Service - fields = ('pk', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags') + fields = ('pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags') default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description') diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index fd1e92be8..84b250f87 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -81,7 +81,7 @@ class VLANGroupTable(BaseTable): class Meta(BaseTable.Meta): model = VLANGroup - fields = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'actions') + fields = ('pk', 'id', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'actions') default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions') @@ -119,7 +119,7 @@ class VLANTable(BaseTable): class Meta(BaseTable.Meta): model = VLAN - fields = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags') + fields = ('pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags') default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description') row_attrs = { 'class': lambda record: 'success' if not isinstance(record, VLAN) else '', @@ -149,6 +149,7 @@ class VLANDevicesTable(VLANMembersTable): class Meta(BaseTable.Meta): model = Interface fields = ('device', 'name', 'tagged', 'actions') + exclude = ('id', ) class VLANVirtualMachinesTable(VLANMembersTable): @@ -160,6 +161,7 @@ class VLANVirtualMachinesTable(VLANMembersTable): class Meta(BaseTable.Meta): model = VMInterface fields = ('virtual_machine', 'name', 'tagged', 'actions') + exclude = ('id', ) class InterfaceVLANTable(BaseTable): @@ -187,6 +189,7 @@ class InterfaceVLANTable(BaseTable): class Meta(BaseTable.Meta): model = VLAN fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description') + exclude = ('id', ) def __init__(self, interface, *args, **kwargs): self.interface = interface diff --git a/netbox/ipam/tables/vrfs.py b/netbox/ipam/tables/vrfs.py index 3a351a856..1264368f4 100644 --- a/netbox/ipam/tables/vrfs.py +++ b/netbox/ipam/tables/vrfs.py @@ -47,7 +47,7 @@ class VRFTable(BaseTable): class Meta(BaseTable.Meta): model = VRF fields = ( - 'pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags', + 'pk', 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags', ) default_columns = ('pk', 'name', 'rd', 'tenant', 'description') @@ -68,5 +68,5 @@ class RouteTargetTable(BaseTable): class Meta(BaseTable.Meta): model = RouteTarget - fields = ('pk', 'name', 'tenant', 'description', 'tags') + fields = ('pk', 'id', 'name', 'tenant', 'description', 'tags') default_columns = ('pk', 'name', 'tenant', 'description') diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index f39ca1b18..a7bb087d8 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -55,7 +55,7 @@ class TenantGroupTable(BaseTable): class Meta(BaseTable.Meta): model = TenantGroup - fields = ('pk', 'name', 'tenant_count', 'description', 'slug', 'actions') + fields = ('pk', 'id', 'name', 'tenant_count', 'description', 'slug', 'actions') default_columns = ('pk', 'name', 'tenant_count', 'description', 'actions') @@ -78,5 +78,5 @@ class TenantTable(BaseTable): class Meta(BaseTable.Meta): model = Tenant - fields = ('pk', 'name', 'slug', 'group', 'description', 'comments', 'tags') + fields = ('pk', 'id', 'name', 'slug', 'group', 'description', 'comments', 'tags') default_columns = ('pk', 'name', 'group', 'description') diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index 42c9ffb56..8258bd5e8 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -133,6 +133,11 @@ class BaseTable(tables.Table): self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk')) return self._objects_count + id = tables.Column( + linkify=True, + verbose_name='ID' + ) + # # Table columns diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index b0e922e71..cb6e64043 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -44,7 +44,7 @@ class ClusterTypeTable(BaseTable): class Meta(BaseTable.Meta): model = ClusterType - fields = ('pk', 'name', 'slug', 'cluster_count', 'description', 'actions') + fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'actions') default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions') @@ -64,7 +64,7 @@ class ClusterGroupTable(BaseTable): class Meta(BaseTable.Meta): model = ClusterGroup - fields = ('pk', 'name', 'slug', 'cluster_count', 'description', 'actions') + fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'actions') default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions') @@ -100,7 +100,7 @@ class ClusterTable(BaseTable): class Meta(BaseTable.Meta): model = Cluster - fields = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'device_count', 'vm_count', 'tags') + fields = ('pk', 'id', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'device_count', 'vm_count', 'tags') default_columns = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count') @@ -140,7 +140,7 @@ class VirtualMachineTable(BaseTable): class Meta(BaseTable.Meta): model = VirtualMachine fields = ( - 'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4', + 'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'tags', ) default_columns = ( @@ -170,7 +170,7 @@ class VMInterfaceTable(BaseInterfaceTable): class Meta(BaseTable.Meta): model = VMInterface fields = ( - 'pk', 'name', 'virtual_machine', 'enabled', 'parent', 'mac_address', 'mtu', 'mode', 'description', 'tags', + 'pk', 'id', 'name', 'virtual_machine', 'enabled', 'parent', 'mac_address', 'mtu', 'mode', 'description', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', ) default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'parent', 'description') @@ -186,7 +186,7 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable): class Meta(BaseTable.Meta): model = VMInterface fields = ( - 'pk', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags', 'ip_addresses', + 'pk', 'id', 'name', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions', ) default_columns = ( From d0b85586b96e1c78ee0aa7eba29bf5470d12728f Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 2 Nov 2021 16:24:28 -0400 Subject: [PATCH 16/27] Changelog & cleanup for #6930 --- docs/release-notes/version-3.0.md | 1 + netbox/circuits/tables.py | 2 +- netbox/utilities/tables.py | 9 ++++----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/release-notes/version-3.0.md b/docs/release-notes/version-3.0.md index c9f58d98f..18af70e7f 100644 --- a/docs/release-notes/version-3.0.md +++ b/docs/release-notes/version-3.0.md @@ -4,6 +4,7 @@ ### Enhancements +* [#6930](https://github.com/netbox-community/netbox/issues/6930) - Add an optional "ID" column to all tables * [#7668](https://github.com/netbox-community/netbox/issues/7668) - Add "view elevations" button to location view ### Bug Fixes diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index 847d44ffd..1748e6110 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -101,7 +101,7 @@ class CircuitTable(BaseTable): pk = ToggleColumn() cid = tables.Column( linkify=True, - verbose_name='ID' + verbose_name='Circuit ID' ) provider = tables.Column( linkify=True diff --git a/netbox/utilities/tables.py b/netbox/utilities/tables.py index 8258bd5e8..b92cde47c 100644 --- a/netbox/utilities/tables.py +++ b/netbox/utilities/tables.py @@ -23,6 +23,10 @@ class BaseTable(tables.Table): :param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed. """ + id = tables.Column( + linkify=True, + verbose_name='ID' + ) class Meta: attrs = { @@ -133,11 +137,6 @@ class BaseTable(tables.Table): self._objects_count = sum(1 for obj in self.data if hasattr(obj, 'pk')) return self._objects_count - id = tables.Column( - linkify=True, - verbose_name='ID' - ) - # # Table columns From 7c3318df923558574b6fe5c244122287e5a6bcf7 Mon Sep 17 00:00:00 2001 From: Daniel Sheppard Date: Tue, 2 Nov 2021 15:56:42 -0500 Subject: [PATCH 17/27] #6529 - Adjusted the arguments. Added documentation --- docs/customization/custom-scripts.md | 14 ++++++++++++++ netbox/extras/management/commands/runscript.py | 10 +++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md index cf052f918..ef74ed7ee 100644 --- a/docs/customization/custom-scripts.md +++ b/docs/customization/custom-scripts.md @@ -259,6 +259,20 @@ http://netbox/api/extras/scripts/example.MyReport/ \ --data '{"data": {"foo": "somevalue", "bar": 123}, "commit": true}' ``` +### Via the CLI + +Scripts can be run on the CLI by invoking the management command: + +``` +python3 manage.py runscript [--commit] [--loglevel {debug,info,warning,error,critical}] --script .