diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml
index 37848a318..43ab47c9d 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yaml
@@ -23,7 +23,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
- placeholder: v3.6.9
+ placeholder: v3.7.2
validations:
required: true
- type: dropdown
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index e6a5e76c2..2ad52023e 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -7,6 +7,9 @@ contact_links:
- name: ❓ Discussion
url: https://github.com/netbox-community/netbox/discussions
about: "If you're just looking for help, try starting a discussion instead."
+ - name: 🌎 Correct a Translation
+ url: https://explore.transifex.com/netbox-community/netbox/
+ about: "Spot an incorrect translation? You can propose a fix on Transifex."
- name: 💡 Plugin Idea
url: https://plugin-ideas.netbox.dev
about: "Have an idea for a plugin? Head over to the ideas board!"
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml
index 006fb64fc..a198fd731 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yaml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yaml
@@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
- placeholder: v3.6.9
+ placeholder: v3.7.2
validations:
required: true
- type: dropdown
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 45d9ec850..af3d303b2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -68,6 +68,9 @@ jobs:
- name: Collect static files
run: python netbox/manage.py collectstatic --no-input
+ - name: Check for missing migrations
+ run: python netbox/manage.py makemigrations --check
+
- name: Check PEP8 compliance
run: pycodestyle --ignore=W504,E501 --exclude=node_modules netbox/
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
index a3e66a429..ad3bf5d75 100644
--- a/.github/workflows/lock.yml
+++ b/.github/workflows/lock.yml
@@ -9,13 +9,15 @@ on:
permissions:
issues: write
pull-requests: write
+ discussions: write
jobs:
lock:
runs-on: ubuntu-latest
steps:
- - uses: dessant/lock-threads@v4
+ - uses: dessant/lock-threads@v5
with:
issue-inactive-days: 90
pr-inactive-days: 30
+ discussion-inactive-days: 180
issue-lock-reason: 'resolved'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 471846427..f94893021 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -86,12 +86,16 @@ intake policy](https://github.com/netbox-community/netbox/wiki/Issue-Intake-Poli
* In most cases, it is not necessary to add a changelog entry: A maintainer will take care of this when the PR is merged. (This helps avoid merge conflicts resulting from multiple PRs being submitted simultaneously.)
-* All code submissions should meet the following criteria (CI will enforce these checks):
+* All code submissions must meet the following criteria (CI will enforce these checks where feasible):
+ * Consist entirely of original work
* Python syntax is valid
* All tests pass when run with `./manage.py test`
* PEP 8 compliance is enforced, with the exception that lines may be
greater than 80 characters in length
+> [!CAUTION]
+> Any contributions which include AI-generated or reproduced content will be rejected.
+
* Some other tips to keep in mind:
* If you'd like to volunteer for someone else's issue, please post a comment on that issue letting us know. (This will allow the maintainers to assign it to you.)
* Check out our [developer docs](https://docs.netbox.dev/en/stable/development/getting-started/) for tips on setting up your development environment.
@@ -117,8 +121,6 @@ We're always looking for motivated individuals to join the maintainers team and
We generally ask that maintainers dedicate around four hours of work to the project each week on average, which includes both hands-on development and project management tasks such as issue triage. Maintainers are also encouraged (but not required) to attend our bi-weekly Zoom call to catch up on recent items.
-Many maintainers petition their employer to grant some of their paid time to work on NetBox. In doing so, your employer becomes eligible to be featured as a [NetBox sponsor](https://github.com/netbox-community/netbox/wiki/Sponsorship).
-
Interested? You can contact our lead maintainer, Jeremy Stretch, at jeremy@netbox.dev or on the [NetDev Community Slack](https://netdev.chat/). We'd love to have you on the team!
## :heart: Other Ways to Contribute
diff --git a/README.md b/README.md
index 6e50e5687..f166919c4 100644
--- a/README.md
+++ b/README.md
@@ -1,86 +1,129 @@

-
The premier source of truth powering network automation
-

+
The cornerstone of every automated network
+

+

+

+

+

+
-NetBox is the leading solution for modeling and documenting modern networks. By
-combining the traditional disciplines of IP address management (IPAM) and
-datacenter infrastructure management (DCIM) with powerful APIs and extensions,
-NetBox provides the ideal "source of truth" to power network automation.
-Available as open source software under the Apache 2.0 license, NetBox serves
-as the cornerstone for network automation in thousands of organizations.
+NetBox exists to empower network engineers. Since its release in 2016, it has become the go-to solution for modeling and documenting network infrastructure for thousands of organizations worldwide. As a successor to legacy IPAM and DCIM applications, NetBox provides a cohesive, extensive, and accessible data model for all things networked. By providing a single robust user interface and programmable APIs for everything from cable maps to device configurations, NetBox serves as the central source of truth for the modern network.
-* **Physical infrastructure:** Accurately model the physical world, from global regions down to individual racks of gear. Then connect everything - network, console, and power!
-* **Modern IPAM:** All the standard IPAM functionality you expect, plus VRF import/export tracking, VLAN management, and overlay support.
-* **Data circuits:** Confidently manage the delivery of critical circuits from various service providers, modeled seamlessly alongside your own infrastructure.
-* **Power tracking:** Map the distribution of power from upstream sources to individual feeds and outlets.
-* **Organization:** Manage tenant and contact assignments natively.
-* **Powerful search:** Easily find anything you need using a single global search function.
-* **Comprehensive logging:** Leverage both automatic change logging and user-submitted journal entries to track your network's growth over time.
-* **Endless customization:** Custom fields, custom links, tags, export templates, custom validation, reports, scripts, and more!
-* **Flexible permissions:** An advanced permissions systems enables very flexible delegation of permissions.
-* **Integrations:** Easily connect NetBox to your other tooling via its REST & GraphQL APIs.
-* **Plugins:** Not finding what you need in the core application? Try one of many community plugins - or build your own!
+
+ NetBox's Role |
+ Why NetBox? |
+ Getting Started |
+ Get Involved |
+ Project Stats |
+ Screenshots
+
-
+
+
+
+
+## NetBox's Role
+
+NetBox functions as the **source of truth** for your network infrastructure. Its job is to define and validate the _intended state_ of all network components and resources. NetBox does not interact with network nodes directly; rather, it makes this data available programmatically to purpose-built automation, monitoring, and assurance tools. This separation of duties enables the construction of a robust yet flexible automation system.
+
+
+
+
+
+The diagram above illustrates the recommended deployment architecture for an automated network, leveraging NetBox as the central authority for network state. This approach allows your team to swap out individual tools to meet changing needs while retaining a predictable, modular workflow.
+
+## Why NetBox?
+
+### Comprehensive Data Model
+
+Racks, devices, cables, IP addresses, VLANs, circuits, power, VPNs, and lots more: NetBox is built for networks. Its comprehensive and thoroughly inter-linked data model provides for natural and highly structured modeling of myriad network primitives that just isn't possible using general-purpose tools. And there's no need to waste time contemplating how to build out a database: Everything is ready to go upon installation.
+
+### Focused Development
+
+NetBox strives to meet a singular goal: Provide the best available solution for making network infrastructure programmatically accessible. Unlike "all-in-one" tools which awkwardly bolt on half-baked features in an attempt to check every box, NetBox is committed to its core function. NetBox provides the best possible solution for modeling network infrastructure, and provides rich APIs for integrating with tools that excel in other areas of network automation.
+
+### Extensible and Customizable
+
+No two networks are exactly the same. Users are empowered to extend NetBox's native data model with custom fields and tags to best suit their unique needs. You can even write your own plugins to introduce entirely new objects and functionality!
+
+### Flexible Permissions
+
+NetBox includes a fully customizable permission system, which affords administrators incredible granularity when assigning roles to users and groups. Want to restrict certain users to working only with cabling and not be able to change IP addresses? Or maybe each team should have access only to a particular tenant? NetBox enables you to craft roles as you see fit.
+
+### Custom Validation & Protection Rules
+
+The data you put into NetBox is crucial to network operations. In addition to its robust native validation rules, NetBox provides mechanisms for administrators to define their own custom validation rules for objects. Custom validation can be used both to ensure new or modified objects adhere to a set of rules, and to prevent the deletion of objects which don't meet certain criteria. (For example, you might want to prevent the deletion of a device with an "active" status.)
+
+### Device Configuration Rendering
+
+NetBox can render user-created Jinja2 templates to generate device configurations from its own data. Configuration templates can be uploaded individually or pulled automatically from an external source, such as a git repository. Rendered configurations can be retrieved via the REST API for application directly to network devices via a provisioning tool such as Ansible or Salt.
+
+### Custom Scripts
+
+Complex workflows, such as provisioning a new branch office, can be tedious to carry out via the user interface. NetBox allows you to write and upload custom scripts that can be run directly from the UI. Scripts prompt users for input and then automate the necessary tasks to greatly simplify otherwise burdensome processes.
+
+### Automated Events
+
+Users can define event rules to automatically trigger a custom script or outbound webhook in response to a NetBox event. For example, you might want to automatically update a network monitoring service whenever a new device is added to NetBox, or update a DHCP server when an IP range is allocated.
+
+### Comprehensive Change Logging
+
+NetBox automatically logs the creation, modification, and deletion of all managed objects, providing a thorough change history. Changes can be attributed to the executing user, and related changes are grouped automatically by request ID.
+
+> [!NOTE]
+> A complete list of NetBox's myriad features can be found in [the introductory documentation](https://docs.netbox.dev/en/stable/introduction/).
## Getting Started
-
-
- [](https://github.com/netbox-community/netbox)
-
- [](https://github.com/netbox-community/netbox-docker)
-
- [](https://netboxlabs.com/netbox-cloud/)
-
-
-
* Just want to explore? Check out [our public demo](https://demo.netbox.dev/) right now!
* The [official documentation](https://docs.netbox.dev) offers a comprehensive introduction.
* Check out [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) for even more projects to get the most out of NetBox!
+
+ 
+ Looking for an enterprise solution? Check out NetBox Cloud!
+
+
## Get Involved
* Follow [@NetBoxOfficial](https://twitter.com/NetBoxOfficial) on Twitter!
* Join the conversation on [the discussion forum](https://github.com/netbox-community/netbox/discussions) and [Slack](https://netdev.chat/)!
* Already a power user? You can [suggest a feature](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+feature&template=feature_request.yaml) or [report a bug](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+bug&template=bug_report.yaml) on GitHub.
* Contributions from the community are encouraged and appreciated! Check out our [contributing guide](CONTRIBUTING.md) to get started.
+* [Share your idea](https://plugin-ideas.netbox.dev/) for a new plugin, or [learn how to build one](https://github.com/netbox-community/netbox-plugin-tutorial) yourself!
## Project Stats
-
-
-## Sponsors
-
-
-
- [](https://netboxlabs.com)
-
- [](https://try.digitalocean.com/developer-cloud)
-
- [](https://sentry.io)
-
- [](https://metal.equinix.com)
-
- [](https://onemindservices.com)
-
-
+
## Screenshots
-")
-
-
-
-
-
-
+
+ NetBox Dashboard (Light Mode)
+
+
+
+ NetBox Dashboard (Dark Mode)
+
+
+
+ Prefixes List
+
+
+
+ Rack View
+
+
+
+ Cable Trace
+
+
diff --git a/docs/administration/authentication/microsoft-azure-ad.md b/docs/administration/authentication/microsoft-azure-ad.md
index ee24e8232..17b130818 100644
--- a/docs/administration/authentication/microsoft-azure-ad.md
+++ b/docs/administration/authentication/microsoft-azure-ad.md
@@ -73,7 +73,7 @@ You should be redirected to Microsoft's authentication portal. Enter the usernam
If successful, you will be redirected back to the NetBox UI, and will be logged in as the AD user. You can verify this by navigating to your profile (using the button at top right).
-This user account has been replicated locally to NetBox, and can now be assigned groups and permissions within the NetBox admin UI.
+This user account has been replicated locally to NetBox, and can now be assigned groups and permissions.
## Troubleshooting
diff --git a/docs/administration/authentication/okta.md b/docs/administration/authentication/okta.md
index ff552d730..2cab186ee 100644
--- a/docs/administration/authentication/okta.md
+++ b/docs/administration/authentication/okta.md
@@ -67,4 +67,4 @@ You should be redirected to Okta's authentication portal. Enter the username/ema
If successful, you will be redirected back to the NetBox UI, and will be logged in as the Okta user. You can verify this by navigating to your profile (using the button at top right).
-This user account has been replicated locally to NetBox, and can now be assigned groups and permissions within the NetBox admin UI.
+This user account has been replicated locally to NetBox, and can now be assigned groups and permissions.
diff --git a/docs/administration/authentication/overview.md b/docs/administration/authentication/overview.md
index 8a8b8f60b..3a3b9efc2 100644
--- a/docs/administration/authentication/overview.md
+++ b/docs/administration/authentication/overview.md
@@ -2,9 +2,9 @@
## Local Authentication
-Local user accounts and groups can be created in NetBox under the "Authentication and Authorization" section of the administrative user interface. This interface is available only to users with the "staff" permission enabled.
+Local user accounts and groups can be created in NetBox under the "Authentication" section in the "Admin" menu. This section is available only to users with the "staff" permission enabled.
-At a minimum, each user account must have a username and password set. User accounts may also denote a first name, last name, and email address. [Permissions](../permissions.md) may also be assigned to users and/or groups within the admin UI.
+At a minimum, each user account must have a username and password set. User accounts may also denote a first name, last name, and email address. [Permissions](../permissions.md) may also be assigned to individual users and/or groups as needed.
## Remote Authentication
diff --git a/docs/configuration/date-time.md b/docs/configuration/date-time.md
index ab8b5ad13..a23053e08 100644
--- a/docs/configuration/date-time.md
+++ b/docs/configuration/date-time.md
@@ -10,6 +10,9 @@ The time zone NetBox will use when dealing with dates and times. It is recommend
You may define custom formatting for date and times. For detailed instructions on writing format strings, please see [the Django documentation](https://docs.djangoproject.com/en/stable/ref/templates/builtins/#date). Default formats are listed below.
+!!! note
+ These system defaults will be overridden by a user's selected language/locale when [localization](./system.md#enable_localization) is enabled.
+
```python
DATE_FORMAT = 'N j, Y' # June 26, 2016
SHORT_DATE_FORMAT = 'Y-m-d' # 2016-06-26
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 70466d029..6a2ecdc7f 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -46,4 +46,4 @@ The configuration file may be modified at any time. However, the WSGI service (e
$ sudo systemctl restart netbox
```
-Configuration parameters which are set via the admin UI (those listed under "dynamic settings") take effect immediately.
+Dynamic configuration parameters (those which can be modified via the UI) take effect immediately.
diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md
index 4d4ca189e..7e68bcee7 100644
--- a/docs/configuration/miscellaneous.md
+++ b/docs/configuration/miscellaneous.md
@@ -99,6 +99,14 @@ The maximum size (in bytes) of an incoming HTTP request (i.e. `GET` or `POST` da
---
+## DJANGO_ADMIN_ENABLED
+
+Default: False
+
+Setting this to True installs the `django.contrib.admin` app and enables the [Django admin UI](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/). This may be necessary to support older plugins which do not integrate with the native NetBox interface.
+
+---
+
## ENFORCE_GLOBAL_UNIQUE
!!! tip "Dynamic Configuration Parameter"
diff --git a/docs/configuration/system.md b/docs/configuration/system.md
index 7fbf9ec54..806839778 100644
--- a/docs/configuration/system.md
+++ b/docs/configuration/system.md
@@ -69,15 +69,7 @@ Email is sent from NetBox only for critical events or if configured for [logging
Default: False
-Determines if localization features are enabled or not. This should only be enabled for development or testing purposes as netbox is not yet fully localized. Turning this on will localize numeric and date formats (overriding what is set for DATE_FORMAT) based on the browser locale as well as translate certain strings from third party modules.
-
----
-
-## GIT_PATH
-
-Default: `git`
-
-The system path to the `git` executable, used by the synchronization backend for remote git repositories.
+Determines if localization features are enabled or not. This should only be enabled for development or testing purposes as netbox is not yet fully localized. Turning this on will localize numeric and date formats (overriding any configured [system defaults](./date-time.md#date-and-time-formatting)) based on the browser locale as well as translate certain strings from third party modules.
---
diff --git a/docs/customization/custom-scripts.md b/docs/customization/custom-scripts.md
index 0b1ed11df..96423a94b 100644
--- a/docs/customization/custom-scripts.md
+++ b/docs/customization/custom-scripts.md
@@ -5,8 +5,17 @@ Custom scripting was introduced to provide a way for users to execute custom log
* Automatically populate new devices and cables in preparation for a new site deployment
* Create a range of new reserved prefixes or IP addresses
* Fetch data from an external source and import it to NetBox
+* Update objects with invalid or incomplete data
-Custom scripts are Python code and exist outside of the official NetBox code base, so they can be updated and changed without interfering with the core NetBox installation. And because they're completely custom, there is no inherent limitation on what a script can accomplish.
+They can also be used as a mechanism for validating the integrity of data within NetBox. Script authors can define test to check object against specific rules and conditions. For example, you can write script to check that:
+
+* All top-of-rack switches have a console connection
+* Every router has a loopback interface with an IP address assigned
+* Each interface description conforms to a standard format
+* Every site has a minimum set of VLANs defined
+* All IP addresses have a parent prefix
+
+Custom scripts are Python code which exists outside the NetBox code base, so they can be updated and changed without interfering with the core NetBox installation. And because they're completely custom, there is no inherent limitation on what a script can accomplish.
## Writing Custom Scripts
@@ -135,13 +144,73 @@ These two methods will load data in YAML or JSON format, respectively, from file
The Script object provides a set of convenient functions for recording messages at different severity levels:
-* `log_debug`
-* `log_success`
-* `log_info`
-* `log_warning`
-* `log_failure`
+* `log_debug(message, object=None)`
+* `log_success(message, object=None)`
+* `log_info(message, object=None)`
+* `log_warning(message, object=None)`
+* `log_failure(message, object=None)`
-Log messages are returned to the user upon execution of the script. Markdown rendering is supported for log messages.
+Log messages are returned to the user upon execution of the script. Markdown rendering is supported for log messages. A message may optionally be associated with a particular object by passing it as the second argument to the logging method.
+
+## Test Methods
+
+A script can define one or more test methods to report on certain conditions. All test methods must have a name beginning with `test_` and accept no arguments beyond `self`.
+
+These methods are detected and run automatically when the script is executed, unless its `run()` method has been overridden. (When overriding `run()`, `run_tests()` can be called to run all test methods present in the script.)
+
+!!! info
+ This functionality was ported from [legacy reports](./reports.md) in NetBox v4.0.
+
+### Example
+
+```
+from dcim.choices import DeviceStatusChoices
+from dcim.models import ConsolePort, Device, PowerPort
+from extras.scripts import Script
+
+
+class DeviceConnectionsReport(Script):
+ description = "Validate the minimum physical connections for each device"
+
+ def test_console_connection(self):
+
+ # Check that every console port for every active device has a connection defined.
+ active = DeviceStatusChoices.STATUS_ACTIVE
+ for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active):
+ if not console_port.connected_endpoints:
+ self.log_failure(
+ f"No console connection defined for {console_port.name}",
+ console_port.device,
+ )
+ elif not console_port.connection_status:
+ self.log_warning(
+ f"Console connection for {console_port.name} marked as planned",
+ console_port.device,
+ )
+ else:
+ self.log_success("Passed", console_port.device)
+
+ def test_power_connections(self):
+
+ # Check that every active device has at least two connected power supplies.
+ for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE):
+ connected_ports = 0
+ for power_port in PowerPort.objects.filter(device=device):
+ if power_port.connected_endpoints:
+ connected_ports += 1
+ if not power_port.path.is_active:
+ self.log_warning(
+ f"Power connection for {power_port.name} marked as planned",
+ device,
+ )
+ if connected_ports < 2:
+ self.log_failure(
+ f"{connected_ports} connected power supplies found (2 needed)",
+ device,
+ )
+ else:
+ self.log_success("Passed", device)
+```
## Change Logging
@@ -288,9 +357,9 @@ An IPv4 or IPv6 network with a mask. Returns a `netaddr.IPNetwork` object. Two a
## Running Custom Scripts
!!! note
- To run a custom script, a user must be assigned via permissions for `Extras > Script`, `Extras > ScriptModule`, and `Core > ManagedFile` objects. They must also 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.
+ To run a custom script, a user must be assigned permissions for `Extras > Script`, `Extras > Script Module`, and `Core > Managed File` objects. They must also 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 "Permissions" as shown below.
- 
+ 
### Via the Web UI
diff --git a/docs/customization/reports.md b/docs/customization/reports.md
index a821c5da7..f7eef506d 100644
--- a/docs/customization/reports.md
+++ b/docs/customization/reports.md
@@ -1,167 +1,63 @@
# NetBox Reports
-A NetBox report is a mechanism for validating the integrity of data within NetBox. Running a report allows the user to verify that the objects defined within NetBox meet certain arbitrary conditions. For example, you can write reports to check that:
-
-* All top-of-rack switches have a console connection
-* Every router has a loopback interface with an IP address assigned
-* Each interface description conforms to a standard format
-* Every site has a minimum set of VLANs defined
-* All IP addresses have a parent prefix
-
-...and so on. Reports are completely customizable, so there's practically no limit to what you can test for.
-
-## Writing Reports
-
-Reports must be saved as files in the [`REPORTS_ROOT`](../configuration/system.md#reports_root) path (which defaults to `netbox/reports/`). Each file created within this path is considered a separate module. Each module holds one or more reports (Python classes), each of which performs a certain function. The logic of each report is broken into discrete test methods, each of which applies a small portion of the logic comprising the overall test.
-
!!! warning
- The reports path includes a file named `__init__.py`, which registers the path as a Python module. Do not delete this file.
+ Reports are deprecated beginning with NetBox v4.0, and their functionality has been merged with [custom scripts](./custom-scripts.md). While backward compatibility has been maintained, users are advised to convert legacy reports into custom scripts soon, as support for legacy reports will be removed in a future release.
-For example, we can create a module named `devices.py` to hold all of our reports which pertain to devices in NetBox. Within that module, we might define several reports. Each report is defined as a Python class inheriting from `extras.reports.Report`.
+## Converting Reports to Scripts
-```
+### Step 1: Update Class Definition
+
+Change the parent class from `Report` to `Script`:
+
+```python title="Old code"
from extras.reports import Report
-class DeviceConnectionsReport(Report):
- description = "Validate the minimum physical connections for each device"
-
-class DeviceIPsReport(Report):
- description = "Check that every device has a primary IP address assigned"
+class MyReport(Report):
```
-Within each report class, we'll create a number of test methods to execute our report's logic. In DeviceConnectionsReport, for instance, we want to ensure that every live device has a console connection, an out-of-band management connection, and two power connections.
+```python title="New code"
+from extras.scripts import Script
-```
-from dcim.choices import DeviceStatusChoices
-from dcim.models import ConsolePort, Device, PowerPort
-from extras.reports import Report
-
-
-class DeviceConnectionsReport(Report):
- description = "Validate the minimum physical connections for each device"
-
- def test_console_connection(self):
-
- # Check that every console port for every active device has a connection defined.
- active = DeviceStatusChoices.STATUS_ACTIVE
- for console_port in ConsolePort.objects.prefetch_related('device').filter(device__status=active):
- if not console_port.connected_endpoints:
- self.log_failure(
- console_port.device,
- "No console connection defined for {}".format(console_port.name)
- )
- elif not console_port.connection_status:
- self.log_warning(
- console_port.device,
- "Console connection for {} marked as planned".format(console_port.name)
- )
- else:
- self.log_success(console_port.device)
-
- def test_power_connections(self):
-
- # Check that every active device has at least two connected power supplies.
- for device in Device.objects.filter(status=DeviceStatusChoices.STATUS_ACTIVE):
- connected_ports = 0
- for power_port in PowerPort.objects.filter(device=device):
- if power_port.connected_endpoints:
- connected_ports += 1
- if not power_port.path.is_active:
- self.log_warning(
- device,
- "Power connection for {} marked as planned".format(power_port.name)
- )
- if connected_ports < 2:
- self.log_failure(
- device,
- "{} connected power supplies found (2 needed)".format(connected_ports)
- )
- else:
- self.log_success(device)
+class MyReport(Script):
```
-As you can see, reports are completely customizable. Validation logic can be as simple or as complex as needed. Also note that the `description` attribute support markdown syntax. It will be rendered in the report list page.
+### Step 2: Update Logging Calls
-!!! warning
- Reports should never alter data: If you find yourself using the `create()`, `save()`, `update()`, or `delete()` methods on objects within reports, stop and re-evaluate what you're trying to accomplish. Note that there are no safeguards against the accidental alteration or destruction of data.
+Reports and scripts both provide logging methods, however their signatures differ. All script logging methods accept a message as the first parameter, and accept an object as an optional second parameter.
-## Report Attributes
+Additionally, the Report class' generic `log()` method is **not** available on Script. Users are advised to replace calls of this method with `log_info()`.
-### `description`
+Use the table below as a reference when updating these methods.
-A human-friendly description of what your report does.
+| Report (old) | Script (New) |
+|-------------------------------|-----------------------------|
+| `log(message)` | `log_info(message)` |
+| `log_debug(obj, message)`[^1] | `log_debug(message, obj)` |
+| `log_info(obj, message)` | `log_info(message, obj)` |
+| `log_success(obj, message)` | `log_success(message, obj)` |
+| `log_warning(obj, message)` | `log_warning(message, obj)` |
+| `log_failure(obj, message)` | `log_failure(message, obj)` |
-### `scheduling_enabled`
+[^1]: `log_debug()` was added to the Report class in v4.0 to avoid confusion with the same method on Script
-By default, a report can be scheduled for execution at a later time. Setting `scheduling_enabled` to False disables this ability: Only immediate execution will be possible. (This also disables the ability to set a recurring execution interval.)
-
-### `job_timeout`
-
-Set the maximum allowed runtime for the report. If not set, `RQ_DEFAULT_TIMEOUT` will be used.
-
-## Logging
-
-The following methods are available to log results within a report:
-
-* log(message)
-* log_success(object, message=None)
-* log_info(object, message)
-* log_warning(object, message)
-* log_failure(object, message)
-
-The recording of one or more failure messages will automatically flag a report as failed. It is advised to log a success for each object that is evaluated so that the results will reflect how many objects are being reported on. (The inclusion of a log message is optional for successes.) Messages recorded with `log()` will appear in a report's results but are not associated with a particular object or status. Log messages also support using markdown syntax and will be rendered on the report result page.
-
-To perform additional tasks, such as sending an email or calling a webhook, before or after a report is run, extend the `pre_run()` and/or `post_run()` methods, respectively.
-
-By default, reports within a module are ordered alphabetically in the reports list page. To return reports in a specific order, you can define the `report_order` variable at the end of your module. The `report_order` variable is a tuple which contains each Report class in the desired order. Any reports that are omitted from this list will be listed last.
-
-```
-from extras.reports import Report
-
-class DeviceConnectionsReport(Report)
- pass
-
-class DeviceIPsReport(Report)
- pass
-
-report_order = (DeviceIPsReport, DeviceConnectionsReport)
+```python title="Old code"
+self.log_failure(
+ console_port.device,
+ f"No console connection defined for {console_port.name}"
+)
```
-Once you have created a report, it will appear in the reports list. Initially, reports will have no results associated with them. To generate results, run the report.
-
-## Running Reports
-
-!!! note
- To run a report, a user must be assigned via permissions for `Extras > Report`, `Extras > ReportModule`, and `Core > ManagedFile` objects. They must also be assigned the `extras.run_report` permission. This is achieved by assigning the user (or group) a permission on the Report object and specifying the `run` action in the admin UI as shown below.
-
- 
-
-### Via the Web UI
-
-Reports can be run via the web UI by navigating to the report and clicking the "run report" button at top right. Once a report has been run, its associated results will be included in the report view. It is possible to schedule a report to be executed at specified time in the future. A scheduled report can be canceled by deleting the associated job result object.
-
-### Via the API
-
-To run a report via the API, simply issue a POST request to its `run` endpoint. Reports are identified by their module and class name.
-
-```
- POST /api/extras/reports/./run/
+```python title="New code"
+self.log_failure(
+ f"No console connection defined for {console_port.name}",
+ obj=console_port.device,
+)
```
-Our example report above would be called as:
+### Other Notes
-```
- POST /api/extras/reports/devices.DeviceConnectionsReport/run/
-```
+Existing reports will be converted to scripts automatically upon upgrading to NetBox v4.0, and previous job history will be retained. However, users are advised to convert legacy reports into custom scripts at the earliest opportunity, as support for legacy reports will be removed in a future release.
-Optionally `schedule_at` can be passed in the form data with a datetime string to schedule a script at the specified date and time.
+The `pre_run()` and `post_run()` Report methods have been carried over to Script. These are called automatically by Script's `run()` method. (Note that if you opt to override this method, you are responsible for calling `pre_run()` and `post_run()` where applicable.)
-### Via the CLI
-
-Reports can be run on the CLI by invoking the management command:
-
-```
-python3 manage.py runreport
-```
-
-where ```` is the name of the python file in the ``reports`` directory without the ``.py`` extension. One or more report modules may be specified.
+The `is_valid()` method on Report is no longer needed and has been removed.
diff --git a/docs/development/release-checklist.md b/docs/development/release-checklist.md
index 68b777111..2af640546 100644
--- a/docs/development/release-checklist.md
+++ b/docs/development/release-checklist.md
@@ -80,6 +80,18 @@ Run the following command to update the device type definition validation schema
This will automatically update the schema file at `contrib/generated_schema.json`.
+### Update & Compile Translations
+
+Log into [Transifex](https://app.transifex.com/netbox-community/netbox/dashboard/) to download the updated string maps. Download the resource (portable object, or `.po`) file for each language and save them to `netbox/translations/$lang/LC_MESSAGES/django.po`, overwriting the current files. (Be sure to click the **Download for use** link.)
+
+
+
+Once the resource files for all languages have been updated, compile the machine object (`.mo`) files using the `compilemessages` management command:
+
+```nohighlight
+./manage.py compilemessages
+```
+
### Update Version and Changelog
* Update the `VERSION` constant in `settings.py` to the new release version.
@@ -90,7 +102,7 @@ Commit these changes to the `develop` branch and push upstream.
### Verify CI Build Status
-Ensure that continuous integration testing on the `develop` branch is completing successfully. If it fails, take action to correct the failure before proceding with the release.
+Ensure that continuous integration testing on the `develop` branch is completing successfully. If it fails, take action to correct the failure before proceeding with the release.
### Submit a Pull Request
diff --git a/docs/development/translations.md b/docs/development/translations.md
new file mode 100644
index 000000000..e40f996c5
--- /dev/null
+++ b/docs/development/translations.md
@@ -0,0 +1,30 @@
+# Translations
+
+NetBox coordinates all translation work using the [Transifex](https://explore.transifex.com/netbox-community/netbox/) platform. Signing up for a Transifex account is free.
+
+All language translations in NetBox are generated from the source file found at `netbox/translations/en/LC_MESSAGES/django.po`. This file contains the original English strings with empty mappings, and is generated as part of NetBox's release process. Transifex updates source strings from this file on a recurring basis, so new translation strings will appear in the platform automatically as it is updated in the code base.
+
+Reviewers log into Transifex and navigate to their designated language(s) to translate strings. The initial translation for most strings will be machine-generated via the AWS Translate service. Human reviewers are responsible for reviewing these translations and making corrections where necessary.
+
+Immediately prior to each NetBox release, the translation maps for all completed languages will be downloaded from Transifex, compiled, and checked into the NetBox code base by a maintainer.
+
+## Updating Translation Sources
+
+To update the English `.po` file from which all translations are derived, use the `makemessages` management command:
+
+```nohighlight
+./manage.py makemessages -l en
+```
+
+Then, commit the change and push to the `develop` branch on GitHub. After some time, any new strings will appear for translation on Transifex automatically.
+
+## Proposing New Languages
+
+If you'd like to add support for a new language to NetBox, the first step is to [submit a GitHub issue](https://github.com/netbox-community/netbox/issues/new?assignees=&labels=type%3A+translation&projects=&template=translation.yaml) to capture the proposal. While we'd like to add as many languages as possible, we do need to limit the rate at which new languages are added. New languages will be selected according to community interest and the number of volunteers who sign up as translators.
+
+Once a proposed language has been approved, a NetBox maintainer will:
+
+* Add it to the Transifex platform
+* Designate one or more reviewers
+* Create the initial machine-generated translations for review
+* Add it to the list of supported languages
diff --git a/docs/features/configuration-rendering.md b/docs/features/configuration-rendering.md
index a87a6eae4..44cacc684 100644
--- a/docs/features/configuration-rendering.md
+++ b/docs/features/configuration-rendering.md
@@ -39,7 +39,7 @@ When rendered for a specific NetBox device, the template's `device` variable wil
### Context Data
-The objet for which the configuration is being rendered is made available as template context as `device` or `virtualmachine` for devices and virtual machines, respectively. Additionally, NetBox model classes can be accessed by the app or plugin in which they reside. For example:
+The object for which the configuration is being rendered is made available as template context as `device` or `virtualmachine` for devices and virtual machines, respectively. Additionally, NetBox model classes can be accessed by the app or plugin in which they reside. For example:
```
There are {{ dcim.Site.objects.count() }} sites.
@@ -70,6 +70,11 @@ This request will trigger resolution of the device's preferred config template i
If no config template has been assigned to any of these three objects, the request will fail.
+The configuration can be rendered as JSON or as plaintext by setting the `Accept:` HTTP header. For example:
+
+* `Accept: application/json`
+* `Accept: text/plain`
+
### General Purpose Use
NetBox config templates can also be rendered without being tied to any specific device, using a separate general purpose REST API endpoint. Any data included with a POST request to this endpoint will be passed as context data for the template.
diff --git a/docs/features/event-rules.md b/docs/features/event-rules.md
index 0e9535223..158dc111a 100644
--- a/docs/features/event-rules.md
+++ b/docs/features/event-rules.md
@@ -28,4 +28,4 @@ For more detail, see the reference documentation for NetBox's [conditional logic
## Event Rule Processing
-When a change is detected, any resulting events are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing event(s) to be processed. The events are then extracted from the queue by the `rqworker` process. The current event queue and any failed events can be inspected in the admin UI under System > Background Tasks.
+When a change is detected, any resulting events are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing event(s) to be processed. The events are then extracted from the queue by the `rqworker` process. The current event queue and any failed events can be inspected under System > Background Tasks.
diff --git a/docs/features/synchronized-data.md b/docs/features/synchronized-data.md
index a070d0ce1..8c95c8779 100644
--- a/docs/features/synchronized-data.md
+++ b/docs/features/synchronized-data.md
@@ -1,6 +1,6 @@
# Synchronized Data
-Several models in NetBox support the automatic synchronization of local data from a designated remote source. For example, [configuration templates](./configuration-rendering.md) defined in NetBox can source their content from text files stored in a remote git repository. This accomplished using the core [data source](../models/core/datasource.md) and [data file](../models/core/datafile.md) models.
+Several models in NetBox support the automatic synchronization of local data from a designated remote source. For example, [configuration templates](./configuration-rendering.md) defined in NetBox can source their content from text files stored in a remote git repository. This is accomplished using the core [data source](../models/core/datasource.md) and [data file](../models/core/datafile.md) models.
To enable remote data synchronization, the NetBox administrator first designates one or more remote data sources. NetBox currently supports the following source types:
diff --git a/docs/index.md b/docs/index.md
index 84334337b..5ef650ca6 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -4,7 +4,7 @@
NetBox is the leading solution for modeling and documenting modern networks. By combining the traditional disciplines of IP address management (IPAM) and datacenter infrastructure management (DCIM) with powerful APIs and extensions, NetBox provides the ideal "source of truth" to power network automation. Read on to discover why thousands of organizations worldwide put NetBox at the heart of their infrastructure.
-[](./media/screenshots/netbox-ui.png)
+[](./media/screenshots/home-light.png)
## :material-server-network: Built for Networks
diff --git a/docs/installation/4-gunicorn.md b/docs/installation/4-gunicorn.md
index 1183a9123..e31c48466 100644
--- a/docs/installation/4-gunicorn.md
+++ b/docs/installation/4-gunicorn.md
@@ -58,3 +58,6 @@ You should see output similar to the following:
If the NetBox service fails to start, issue the command `journalctl -eu netbox` to check for log messages that may indicate the problem.
Once you've verified that the WSGI workers are up and running, move on to HTTP server setup.
+
+!!! note
+ There is a bug in the current stable release of gunicorn (v21.2.0) where automatic restarts of the worker processes can result in 502 errors under heavy load. (See [gunicorn bug #3038](https://github.com/benoitc/gunicorn/issues/3038) for more detail.) Users who encounter this issue may opt to downgrade to an earlier, unaffected release of gunicorn (`pip install gunicorn==20.1.0`). Note, however, that this earlier release does not officially support Python 3.11.
diff --git a/docs/integrations/webhooks.md b/docs/integrations/webhooks.md
index 8913fd99c..bfe9ddb2d 100644
--- a/docs/integrations/webhooks.md
+++ b/docs/integrations/webhooks.md
@@ -73,9 +73,9 @@ If no body template is specified, the request body will be populated with a JSON
## Webhook Processing
-Using [Event Rules](../features/event-rules.md), when a change is detected, any resulting webhooks are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing webhook(s) to be processed. The webhooks are then extracted from the queue by the `rqworker` process and HTTP requests are sent to their respective destinations. The current webhook queue and any failed webhooks can be inspected in the admin UI under System > Background Tasks.
+Using [Event Rules](../features/event-rules.md), when a change is detected, any resulting webhooks are placed into a Redis queue for processing. This allows the user's request to complete without needing to wait for the outgoing webhook(s) to be processed. The webhooks are then extracted from the queue by the `rqworker` process and HTTP requests are sent to their respective destinations. The current webhook queue and any failed webhooks can be inspected under System > Background Tasks.
-A request is considered successful if the response has a 2XX status code; otherwise, the request is marked as having failed. Failed requests may be retried manually via the admin UI.
+A request is considered successful if the response has a 2XX status code; otherwise, the request is marked as having failed. Failed requests may be requeued manually under System > Background Tasks.
## Troubleshooting
@@ -106,6 +106,6 @@ Content-Type: application/x-www-form-urlencoded
------------
```
-Note that `webhook_receiver` does not actually _do_ anything with the information received: It merely prints the request headers and body for inspection.
+Note that `webhook_receiver` does not actually _do_ anything with the information received: It merely prints the request headers and body for inspection. If you don't see any output, check that the `rqworker` process is running and that webhook events are being placed into the queue.
-Now, when the NetBox webhook is triggered and processed, you should see its headers and content appear in the terminal where the webhook receiver is listening. If you don't, check that the `rqworker` process is running and that webhook events are being placed into the queue (visible under the NetBox admin UI).
+Webhook results can be found in the NetBox admin UI under the Background Tasks section. You can see any finished or failed runs, as well as the error log for failed webhooks.
diff --git a/docs/media/development/transifex_download.png b/docs/media/development/transifex_download.png
new file mode 100644
index 000000000..99429ce11
Binary files /dev/null and b/docs/media/development/transifex_download.png differ
diff --git a/docs/media/misc/netbox_cloud.png b/docs/media/misc/netbox_cloud.png
new file mode 100644
index 000000000..f9deca674
Binary files /dev/null and b/docs/media/misc/netbox_cloud.png differ
diff --git a/docs/media/misc/reference_architecture.png b/docs/media/misc/reference_architecture.png
new file mode 100644
index 000000000..89ed4478d
Binary files /dev/null and b/docs/media/misc/reference_architecture.png differ
diff --git a/docs/media/admin_ui_run_permission.png b/docs/media/run_permission.png
similarity index 100%
rename from docs/media/admin_ui_run_permission.png
rename to docs/media/run_permission.png
diff --git a/docs/media/screenshots/cable-trace.png b/docs/media/screenshots/cable-trace.png
index b35272016..e228d1786 100644
Binary files a/docs/media/screenshots/cable-trace.png and b/docs/media/screenshots/cable-trace.png differ
diff --git a/docs/media/screenshots/home-dark.png b/docs/media/screenshots/home-dark.png
index 718413445..7b060785f 100644
Binary files a/docs/media/screenshots/home-dark.png and b/docs/media/screenshots/home-dark.png differ
diff --git a/docs/media/screenshots/home-light.png b/docs/media/screenshots/home-light.png
new file mode 100644
index 000000000..1eaca3ef0
Binary files /dev/null and b/docs/media/screenshots/home-light.png differ
diff --git a/docs/media/screenshots/netbox-ui.png b/docs/media/screenshots/netbox-ui.png
deleted file mode 100644
index 70cd77089..000000000
Binary files a/docs/media/screenshots/netbox-ui.png and /dev/null differ
diff --git a/docs/media/screenshots/prefixes-list.png b/docs/media/screenshots/prefixes-list.png
index 927a7a04e..7220a8817 100644
Binary files a/docs/media/screenshots/prefixes-list.png and b/docs/media/screenshots/prefixes-list.png differ
diff --git a/docs/media/screenshots/rack.png b/docs/media/screenshots/rack.png
index dbe9718f7..7179efda3 100644
Binary files a/docs/media/screenshots/rack.png and b/docs/media/screenshots/rack.png differ
diff --git a/docs/models/vpn/ikepolicy.md b/docs/models/vpn/ikepolicy.md
index 7b739072b..d2da28d16 100644
--- a/docs/models/vpn/ikepolicy.md
+++ b/docs/models/vpn/ikepolicy.md
@@ -14,7 +14,7 @@ The IKE version employed (v1 or v2).
### Mode
-The IKE mode employed (main or aggressive).
+The mode employed (main or aggressive) when IKEv1 is in use. This setting is not supported for IKEv2.
### Proposals
diff --git a/docs/plugins/development/dashboard-widgets.md b/docs/plugins/development/dashboard-widgets.md
index b1c9d0e45..74f9c9474 100644
--- a/docs/plugins/development/dashboard-widgets.md
+++ b/docs/plugins/development/dashboard-widgets.md
@@ -47,3 +47,14 @@ class ReminderWidget(DashboardWidget):
def render(self, request):
return self.config.get('content')
```
+
+## Initialization
+
+To register the widget, it becomes essential to import the widget module. The recommended approach is to accomplish this within the `ready` method situated in your `PluginConfig`:
+
+```python
+class FooBarConfig(PluginConfig):
+ def ready(self):
+ super().ready()
+ from . import widgets # point this to the above widget module you created
+```
diff --git a/docs/plugins/development/data-backends.md b/docs/plugins/development/data-backends.md
index feffa5bed..8b7226a41 100644
--- a/docs/plugins/development/data-backends.md
+++ b/docs/plugins/development/data-backends.md
@@ -20,4 +20,4 @@ backends = [MyDataBackend]
!!! tip
The path to the list of search indexes can be modified by setting `data_backends` in the PluginConfig instance.
-::: core.data_backends.DataBackend
+::: netbox.data_backends.DataBackend
diff --git a/docs/release-notes/version-3.7.md b/docs/release-notes/version-3.7.md
index d6818bdec..103b0664c 100644
--- a/docs/release-notes/version-3.7.md
+++ b/docs/release-notes/version-3.7.md
@@ -1,5 +1,60 @@
# NetBox v3.7
+## v3.7.3 (FUTURE)
+
+---
+
+## v3.7.2 (2024-02-05)
+
+### Enhancements
+
+* [#13729](https://github.com/netbox-community/netbox/issues/13729) - Omit sensitive data source parameters from change log data
+* [#14645](https://github.com/netbox-community/netbox/issues/14645) - Limit the number of assigned IP addresses displayed under interfaces list
+
+### Bug Fixes
+
+* [#14500](https://github.com/netbox-community/netbox/issues/14500) - Optimize calculation of available child prefixes & ranges when viewing a prefix
+* [#14511](https://github.com/netbox-community/netbox/issues/14511) - Fix GraphQL support for interfaces connected to provider networks
+* [#14572](https://github.com/netbox-community/netbox/issues/14572) - Correct the number of jobs listed for individual report & script modules
+* [#14703](https://github.com/netbox-community/netbox/issues/14703) - Revert to the default layout when encountering a misconfigured dashboard
+* [#14755](https://github.com/netbox-community/netbox/issues/14755) - Fix validation of choice values & labels when creating a custom field choice set via the REST API
+* [#14838](https://github.com/netbox-community/netbox/issues/14838) - Avoid corrupting JSON data when changing the action type while editing an event rule
+* [#14839](https://github.com/netbox-community/netbox/issues/14839) - Fix form validation error when attempting to terminate a tunnel to a virtual machine interface
+* [#14840](https://github.com/netbox-community/netbox/issues/14840) - Fix `NoReverseMatch` exception when rendering a custom field which references a user
+* [#14847](https://github.com/netbox-community/netbox/issues/14847) - IKE policy mode may be set inly when IKEv1 is selected
+* [#14851](https://github.com/netbox-community/netbox/issues/14851) - Automatically remove any associated bookmarks when deleting a user
+* [#14879](https://github.com/netbox-community/netbox/issues/14879) - Include custom fields in REST API representation of data sources
+* [#14885](https://github.com/netbox-community/netbox/issues/14885) - Add missing "group" field to VPN tunnel creation form
+* [#14892](https://github.com/netbox-community/netbox/issues/14892) - Fix exception when running report/script via command line due to missing username
+* [#14920](https://github.com/netbox-community/netbox/issues/14920) - Include button to display available status choices when bulk importing virtual device contexts
+* [#14945](https://github.com/netbox-community/netbox/issues/14945) - Fix "select all" button for device type components
+* [#14947](https://github.com/netbox-community/netbox/issues/14947) - Ensure that application & removal of tags is always recorded in an object's change log
+* [#14962](https://github.com/netbox-community/netbox/issues/14962) - Fix config context rendering for VMs assigned directly to a site (rather than via a cluster)
+* [#14999](https://github.com/netbox-community/netbox/issues/14999) - Fix "create & add another" link for interface FHRP group assignment
+* [#15015](https://github.com/netbox-community/netbox/issues/15015) - Pre-populate assigned tenant when allocating next available IP address under prefix view
+* [#15020](https://github.com/netbox-community/netbox/issues/15020) - Automatically update all VMs when changing a cluster's assigned site
+* [#15025](https://github.com/netbox-community/netbox/issues/15025) - The `can_add()` template filter should accept a model (not an instance)
+
+---
+
+## v3.7.1 (2024-01-17)
+
+### Bug Fixes
+
+* [#13844](https://github.com/netbox-community/netbox/issues/13844) - Use `available_at_site` filter when filtering VLANs under prefix form
+* [#14663](https://github.com/netbox-community/netbox/issues/14663) - Fix tunnel creation when setting initial termination to a VM interface
+* [#14706](https://github.com/netbox-community/netbox/issues/14706) - Relax one-to-one mapping of tunnel termination to IP address
+* [#14709](https://github.com/netbox-community/netbox/issues/14709) - Fix typo in tunnel termination type choice name
+* [#14749](https://github.com/netbox-community/netbox/issues/14749) - Remove errant translation wrapper from `installed_device` on DeviceBay
+* [#14778](https://github.com/netbox-community/netbox/issues/14778) - Custom field API serializer should accept null values for all optional fields
+* [#14791](https://github.com/netbox-community/netbox/issues/14791) - Hide available prefixes when searching within a parent prefix
+* [#14793](https://github.com/netbox-community/netbox/issues/14793) - Add missing Diffie-Hellman group 15
+* [#14816](https://github.com/netbox-community/netbox/issues/14816) - Ensure default contact assignment ordering is consistent
+* [#14817](https://github.com/netbox-community/netbox/issues/14817) - Relax required fields for IKE & IPSec models on bulk import
+* [#14827](https://github.com/netbox-community/netbox/issues/14827) - Ensure all matching event rules are processed in response to an event
+
+---
+
## v3.7.0 (2023-12-29)
### Breaking Changes
diff --git a/docs/release-notes/version-4.0.md b/docs/release-notes/version-4.0.md
index d2e592db0..507b8dcdd 100644
--- a/docs/release-notes/version-4.0.md
+++ b/docs/release-notes/version-4.0.md
@@ -2,14 +2,33 @@
## v4.0.0 (FUTURE)
+### Breaking Changes
+
+* The deprecated `device_role` & `device_role_id` filters for devices have been removed. (Use `role` and `role_id` instead.)
+
+### New Features
+
+#### Complete UI Refresh ([#12128](https://github.com/netbox-community/netbox/issues/12128))
+
+The NetBox user interface has been completely refreshed and updated.
+
### Enhancements
+* [#12851](https://github.com/netbox-community/netbox/issues/12851) - Replace bleach HTML sanitization library with nh3
* [#14637](https://github.com/netbox-community/netbox/issues/14637) - Upgrade to Django 5.0
* [#14672](https://github.com/netbox-community/netbox/issues/14672) - Add support for Python 3.12
+* [#14728](https://github.com/netbox-community/netbox/issues/14728) - The plugins list view has been moved from the legacy admin UI to the main NetBox UI
+* [#14729](https://github.com/netbox-community/netbox/issues/14729) - All background task views have been moved from the legacy admin UI to the main NetBox UI
### Other Changes
+* [#12325](https://github.com/netbox-community/netbox/issues/12325) - The Django admin UI is now disabled by default (set `DJANGO_ADMIN_ENABLED` to True to enable it)
+* [#12795](https://github.com/netbox-community/netbox/issues/12795) - NetBox now uses a custom User model rather than the stock model provided by Django
+* [#13647](https://github.com/netbox-community/netbox/issues/13647) - Squash all database migrations prior to v3.7
* [#14092](https://github.com/netbox-community/netbox/issues/14092) - Remove backward compatibility for importing plugin resources from `extras.plugins` (now `netbox.plugins`)
* [#14638](https://github.com/netbox-community/netbox/issues/14638) - Drop support for Python 3.8 and 3.9
* [#14657](https://github.com/netbox-community/netbox/issues/14657) - Remove backward compatibility for old permissions mapping under `ActionsMixin`
* [#14658](https://github.com/netbox-community/netbox/issues/14658) - Remove backward compatibility for importing `process_webhook()` (now `extras.webhooks.send_webhook()`)
+* [#14740](https://github.com/netbox-community/netbox/issues/14740) - Remove the obsolete `BootstrapMixin` form mixin class
+* [#15099](https://github.com/netbox-community/netbox/issues/15099) - Remove obsolete `device_role` and `device_role_id` filters for devices
+* [#15100](https://github.com/netbox-community/netbox/issues/15100) - Remove obsolete `NullableCharField` class
diff --git a/mkdocs.yml b/mkdocs.yml
index 5a7e00c2c..c5816f5f5 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -52,6 +52,7 @@ extra_css:
markdown_extensions:
- admonition
- attr_list
+ - footnotes
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
@@ -286,6 +287,7 @@ nav:
- User Preferences: 'development/user-preferences.md'
- Web UI: 'development/web-ui.md'
- Internationalization: 'development/internationalization.md'
+ - Translations: 'development/translations.md'
- Release Checklist: 'development/release-checklist.md'
- git Cheat Sheet: 'development/git-cheat-sheet.md'
- Release Notes:
diff --git a/netbox/core/api/serializers.py b/netbox/core/api/serializers.py
index 4ae426df5..a16a06d62 100644
--- a/netbox/core/api/serializers.py
+++ b/netbox/core/api/serializers.py
@@ -36,7 +36,7 @@ class DataSourceSerializer(NetBoxModelSerializer):
model = DataSource
fields = [
'id', 'url', 'display', 'name', 'type', 'source_url', 'enabled', 'status', 'description', 'comments',
- 'parameters', 'ignore_rules', 'created', 'last_updated', 'file_count',
+ 'parameters', 'ignore_rules', 'custom_fields', 'created', 'last_updated', 'file_count',
]
diff --git a/netbox/core/forms/bulk_edit.py b/netbox/core/forms/bulk_edit.py
index dcc92c6f0..bc2ef8fc9 100644
--- a/netbox/core/forms/bulk_edit.py
+++ b/netbox/core/forms/bulk_edit.py
@@ -21,7 +21,7 @@ class DataSourceBulkEditForm(NetBoxModelBulkEditForm):
enabled = forms.NullBooleanField(
required=False,
widget=BulkEditNullBooleanSelect(),
- label=_('Enforce unique space')
+ label=_('Enabled')
)
description = forms.CharField(
label=_('Description'),
diff --git a/netbox/core/forms/filtersets.py b/netbox/core/forms/filtersets.py
index f21bd3f87..0c164ac29 100644
--- a/netbox/core/forms/filtersets.py
+++ b/netbox/core/forms/filtersets.py
@@ -119,10 +119,7 @@ class JobFilterForm(SavedFiltersMixin, FilterForm):
user = DynamicModelMultipleChoiceField(
queryset=get_user_model().objects.all(),
required=False,
- label=_('User'),
- widget=APISelectMultiple(
- api_url='/api/users/users/',
- )
+ label=_('User')
)
diff --git a/netbox/core/management/commands/makemigrations.py b/netbox/core/management/commands/makemigrations.py
index ce40bd3cc..afab5077d 100644
--- a/netbox/core/management/commands/makemigrations.py
+++ b/netbox/core/management/commands/makemigrations.py
@@ -9,9 +9,9 @@ class Command(_Command):
"""
This built-in management command enables the creation of new database schema migration files, which should
never be required by and ordinary user. We prevent this command from executing unless the configuration
- indicates that the user is a developer (i.e. configuration.DEVELOPER == True).
+ indicates that the user is a developer (i.e. configuration.DEVELOPER == True), or it was run with --check.
"""
- if not settings.DEVELOPER:
+ if not kwargs['check_changes'] and not settings.DEVELOPER:
raise CommandError(
"This command is available for development purposes only. It will\n"
"NOT resolve any issues with missing or unapplied migrations. For assistance,\n"
diff --git a/netbox/core/models/data.py b/netbox/core/models/data.py
index efda879af..6597a4b4d 100644
--- a/netbox/core/models/data.py
+++ b/netbox/core/models/data.py
@@ -14,6 +14,7 @@ from django.utils import timezone
from django.utils.module_loading import import_string
from django.utils.translation import gettext as _
+from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
from netbox.models import PrimaryModel
from netbox.models.features import JobsMixin
from netbox.registry import registry
@@ -130,6 +131,28 @@ class DataSource(JobsMixin, PrimaryModel):
'source_url': f"URLs for local sources must start with file:// (or specify no scheme)"
})
+ def to_objectchange(self, action):
+ objectchange = super().to_objectchange(action)
+
+ # Censor any backend parameters marked as sensitive in the serialized data
+ pre_change_params = {}
+ post_change_params = {}
+ if objectchange.prechange_data:
+ pre_change_params = objectchange.prechange_data.get('parameters') or {} # parameters may be None
+ if objectchange.postchange_data:
+ post_change_params = objectchange.postchange_data.get('parameters') or {}
+ for param in self.backend_class.sensitive_parameters:
+ if post_change_params.get(param):
+ if post_change_params[param] != pre_change_params.get(param):
+ # Set the "changed" token if the parameter's value has been modified
+ post_change_params[param] = CENSOR_TOKEN_CHANGED
+ else:
+ post_change_params[param] = CENSOR_TOKEN
+ if pre_change_params.get(param):
+ pre_change_params[param] = CENSOR_TOKEN
+
+ return objectchange
+
def enqueue_sync_job(self, request):
"""
Enqueue a background job to synchronize the DataSource by calling sync().
diff --git a/netbox/core/tests/test_models.py b/netbox/core/tests/test_models.py
new file mode 100644
index 000000000..0eeb66984
--- /dev/null
+++ b/netbox/core/tests/test_models.py
@@ -0,0 +1,122 @@
+from django.test import TestCase
+
+from core.models import DataSource
+from extras.choices import ObjectChangeActionChoices
+from netbox.constants import CENSOR_TOKEN, CENSOR_TOKEN_CHANGED
+
+
+class DataSourceChangeLoggingTestCase(TestCase):
+
+ def test_password_added_on_create(self):
+ datasource = DataSource.objects.create(
+ name='Data Source 1',
+ type='git',
+ source_url='http://localhost/',
+ parameters={
+ 'username': 'jeff',
+ 'password': 'foobar123',
+ }
+ )
+
+ objectchange = datasource.to_objectchange(ObjectChangeActionChoices.ACTION_CREATE)
+ self.assertIsNone(objectchange.prechange_data)
+ self.assertEqual(objectchange.postchange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.postchange_data['parameters']['password'], CENSOR_TOKEN_CHANGED)
+
+ def test_password_added_on_update(self):
+ datasource = DataSource.objects.create(
+ name='Data Source 1',
+ type='git',
+ source_url='http://localhost/'
+ )
+ datasource.snapshot()
+
+ # Add a blank password
+ datasource.parameters = {
+ 'username': 'jeff',
+ 'password': '',
+ }
+
+ objectchange = datasource.to_objectchange(ObjectChangeActionChoices.ACTION_UPDATE)
+ self.assertIsNone(objectchange.prechange_data['parameters'])
+ self.assertEqual(objectchange.postchange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.postchange_data['parameters']['password'], '')
+
+ # Add a password
+ datasource.parameters = {
+ 'username': 'jeff',
+ 'password': 'foobar123',
+ }
+
+ objectchange = datasource.to_objectchange(ObjectChangeActionChoices.ACTION_UPDATE)
+ self.assertEqual(objectchange.postchange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.postchange_data['parameters']['password'], CENSOR_TOKEN_CHANGED)
+
+ def test_password_changed(self):
+ datasource = DataSource.objects.create(
+ name='Data Source 1',
+ type='git',
+ source_url='http://localhost/',
+ parameters={
+ 'username': 'jeff',
+ 'password': 'password1',
+ }
+ )
+ datasource.snapshot()
+
+ # Change the password
+ datasource.parameters['password'] = 'password2'
+
+ objectchange = datasource.to_objectchange(ObjectChangeActionChoices.ACTION_UPDATE)
+ self.assertEqual(objectchange.prechange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.prechange_data['parameters']['password'], CENSOR_TOKEN)
+ self.assertEqual(objectchange.postchange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.postchange_data['parameters']['password'], CENSOR_TOKEN_CHANGED)
+
+ def test_password_removed_on_update(self):
+ datasource = DataSource.objects.create(
+ name='Data Source 1',
+ type='git',
+ source_url='http://localhost/',
+ parameters={
+ 'username': 'jeff',
+ 'password': 'foobar123',
+ }
+ )
+ datasource.snapshot()
+
+ objectchange = datasource.to_objectchange(ObjectChangeActionChoices.ACTION_UPDATE)
+ self.assertEqual(objectchange.prechange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.prechange_data['parameters']['password'], CENSOR_TOKEN)
+ self.assertEqual(objectchange.postchange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.postchange_data['parameters']['password'], CENSOR_TOKEN)
+
+ # Remove the password
+ datasource.parameters['password'] = ''
+
+ objectchange = datasource.to_objectchange(ObjectChangeActionChoices.ACTION_UPDATE)
+ self.assertEqual(objectchange.prechange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.prechange_data['parameters']['password'], CENSOR_TOKEN)
+ self.assertEqual(objectchange.postchange_data['parameters']['username'], 'jeff')
+ self.assertEqual(objectchange.postchange_data['parameters']['password'], '')
+
+ def test_password_not_modified(self):
+ datasource = DataSource.objects.create(
+ name='Data Source 1',
+ type='git',
+ source_url='http://localhost/',
+ parameters={
+ 'username': 'username1',
+ 'password': 'foobar123',
+ }
+ )
+ datasource.snapshot()
+
+ # Remove the password
+ datasource.parameters['username'] = 'username2'
+
+ objectchange = datasource.to_objectchange(ObjectChangeActionChoices.ACTION_UPDATE)
+ self.assertEqual(objectchange.prechange_data['parameters']['username'], 'username1')
+ self.assertEqual(objectchange.prechange_data['parameters']['password'], CENSOR_TOKEN)
+ self.assertEqual(objectchange.postchange_data['parameters']['username'], 'username2')
+ self.assertEqual(objectchange.postchange_data['parameters']['password'], CENSOR_TOKEN)
diff --git a/netbox/dcim/filtersets.py b/netbox/dcim/filtersets.py
index 68edc93f6..4810e9cab 100644
--- a/netbox/dcim/filtersets.py
+++ b/netbox/dcim/filtersets.py
@@ -1288,18 +1288,6 @@ class DeviceComponentFilterSet(django_filters.FilterSet):
to_field_name='name',
label=_('Virtual Chassis'),
)
- # TODO: Remove in v4.0
- device_role_id = django_filters.ModelMultipleChoiceFilter(
- field_name='device__role',
- queryset=DeviceRole.objects.all(),
- label=_('Device role (ID)'),
- )
- device_role = django_filters.ModelMultipleChoiceFilter(
- field_name='device__role__slug',
- queryset=DeviceRole.objects.all(),
- to_field_name='slug',
- label=_('Device role (slug)'),
- )
def search(self, queryset, name, value):
if not value.strip():
diff --git a/netbox/dcim/forms/bulk_import.py b/netbox/dcim/forms/bulk_import.py
index d63873b59..f30ff91fa 100644
--- a/netbox/dcim/forms/bulk_import.py
+++ b/netbox/dcim/forms/bulk_import.py
@@ -727,7 +727,7 @@ class PowerOutletImportForm(NetBoxModelImportForm):
help_text=_('Local power port which feeds this outlet')
)
feed_leg = CSVChoiceField(
- label=_('Feed lag'),
+ label=_('Feed leg'),
choices=PowerOutletFeedLegChoices,
required=False,
help_text=_('Electrical phase (for three-phase circuits)')
@@ -1359,6 +1359,10 @@ class VirtualDeviceContextImportForm(NetBoxModelImportForm):
to_field_name='name',
help_text='Assigned tenant'
)
+ status = CSVChoiceField(
+ label=_('Status'),
+ choices=VirtualDeviceContextStatusChoices,
+ )
class Meta:
fields = [
diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py
index 95c441381..89793528d 100644
--- a/netbox/dcim/forms/filtersets.py
+++ b/netbox/dcim/forms/filtersets.py
@@ -393,10 +393,7 @@ class RackReservationFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
user_id = DynamicModelMultipleChoiceField(
queryset=get_user_model().objects.all(),
required=False,
- label=_('User'),
- widget=APISelectMultiple(
- api_url='/api/users/users/',
- )
+ label=_('User')
)
tag = TagFilterField(model)
@@ -551,8 +548,7 @@ class ModuleTypeFilterForm(NetBoxModelFilterSetForm):
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
- label=_('Manufacturer'),
- fetch_trigger='open'
+ label=_('Manufacturer')
)
part_number = forms.CharField(
label=_('Part number'),
@@ -828,8 +824,7 @@ class VirtualDeviceContextFilterForm(
device = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
- label=_('Device'),
- fetch_trigger='open'
+ label=_('Device')
)
status = forms.MultipleChoiceField(
label=_('Status'),
@@ -855,8 +850,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
required=False,
- label=_('Manufacturer'),
- fetch_trigger='open'
+ label=_('Manufacturer')
)
module_type_id = DynamicModelMultipleChoiceField(
queryset=ModuleType.objects.all(),
@@ -864,8 +858,7 @@ class ModuleFilterForm(LocalConfigContextFilterForm, TenancyFilterForm, NetBoxMo
query_params={
'manufacturer_id': '$manufacturer_id'
},
- label=_('Type'),
- fetch_trigger='open'
+ label=_('Type')
)
status = forms.MultipleChoiceField(
label=_('Status'),
@@ -1414,8 +1407,7 @@ class InventoryItemFilterForm(DeviceComponentFilterForm):
role_id = DynamicModelMultipleChoiceField(
queryset=InventoryItemRole.objects.all(),
required=False,
- label=_('Role'),
- fetch_trigger='open'
+ label=_('Role')
)
manufacturer_id = DynamicModelMultipleChoiceField(
queryset=Manufacturer.objects.all(),
diff --git a/netbox/dcim/graphql/gfk_mixins.py b/netbox/dcim/graphql/gfk_mixins.py
index c97aa4c2b..2f669fb87 100644
--- a/netbox/dcim/graphql/gfk_mixins.py
+++ b/netbox/dcim/graphql/gfk_mixins.py
@@ -1,6 +1,6 @@
import graphene
-from circuits.graphql.types import CircuitTerminationType
-from circuits.models import CircuitTermination
+from circuits.graphql.types import CircuitTerminationType, ProviderNetworkType
+from circuits.models import CircuitTermination, ProviderNetwork
from dcim.graphql.types import (
ConsolePortTemplateType,
ConsolePortType,
@@ -167,3 +167,42 @@ class InventoryItemComponentType(graphene.Union):
return PowerPortType
if type(instance) is RearPort:
return RearPortType
+
+
+class ConnectedEndpointType(graphene.Union):
+ class Meta:
+ types = (
+ CircuitTerminationType,
+ ConsolePortType,
+ ConsoleServerPortType,
+ FrontPortType,
+ InterfaceType,
+ PowerFeedType,
+ PowerOutletType,
+ PowerPortType,
+ ProviderNetworkType,
+ RearPortType,
+ )
+
+ @classmethod
+ def resolve_type(cls, instance, info):
+ if type(instance) is CircuitTermination:
+ return CircuitTerminationType
+ if type(instance) is ConsolePortType:
+ return ConsolePortType
+ if type(instance) is ConsoleServerPort:
+ return ConsoleServerPortType
+ if type(instance) is FrontPort:
+ return FrontPortType
+ if type(instance) is Interface:
+ return InterfaceType
+ if type(instance) is PowerFeed:
+ return PowerFeedType
+ if type(instance) is PowerOutlet:
+ return PowerOutletType
+ if type(instance) is PowerPort:
+ return PowerPortType
+ if type(instance) is ProviderNetwork:
+ return ProviderNetworkType
+ if type(instance) is RearPort:
+ return RearPortType
diff --git a/netbox/dcim/graphql/mixins.py b/netbox/dcim/graphql/mixins.py
index f8e626fe8..8241b7de5 100644
--- a/netbox/dcim/graphql/mixins.py
+++ b/netbox/dcim/graphql/mixins.py
@@ -13,7 +13,7 @@ class CabledObjectMixin:
class PathEndpointMixin:
- connected_endpoints = graphene.List('dcim.graphql.gfk_mixins.LinkPeerType')
+ connected_endpoints = graphene.List('dcim.graphql.gfk_mixins.ConnectedEndpointType')
def resolve_connected_endpoints(self, info):
# Handle empty values
diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py
index ef235078f..88dddb312 100644
--- a/netbox/dcim/models/device_components.py
+++ b/netbox/dcim/models/device_components.py
@@ -1115,7 +1115,7 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
installed_device = models.OneToOneField(
to='dcim.Device',
on_delete=models.SET_NULL,
- related_name=_('parent_bay'),
+ related_name='parent_bay',
blank=True,
null=True
)
diff --git a/netbox/dcim/tables/template_code.py b/netbox/dcim/tables/template_code.py
index 8e382dd66..835a72bc6 100644
--- a/netbox/dcim/tables/template_code.py
+++ b/netbox/dcim/tables/template_code.py
@@ -35,13 +35,17 @@ DEVICEBAY_STATUS = """
"""
INTERFACE_IPADDRESSES = """
- {% for ip in value.all %}
- {% if ip.status != 'active' %}
- {{ ip }}
- {% else %}
- {{ ip }}
- {% endif %}
- {% endfor %}
+ {% if value.count > 3 %}
+ {{ value.count }}
+ {% else %}
+ {% for ip in value.all %}
+ {% if ip.status != 'active' %}
+ {{ ip }}
+ {% else %}
+ {{ ip }}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
"""
INTERFACE_FHRPGROUPS = """
diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py
index 0129c218c..b5a90ced3 100644
--- a/netbox/dcim/views.py
+++ b/netbox/dcim/views.py
@@ -58,7 +58,11 @@ class DeviceComponentsView(generic.ObjectChildrenView):
return self.child_model.objects.restrict(request.user, 'view').filter(device=parent)
-class DeviceTypeComponentsView(DeviceComponentsView):
+class DeviceTypeComponentsView(generic.ObjectChildrenView):
+ actions = {
+ **DEFAULT_ACTION_PERMISSIONS,
+ 'bulk_rename': {'change'},
+ }
queryset = DeviceType.objects.all()
template_name = 'dcim/devicetype/component_templates.html'
viewname = None # Used for return_url resolution
diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py
index 60a30aed2..e2f30e8b0 100644
--- a/netbox/extras/api/serializers.py
+++ b/netbox/extras/api/serializers.py
@@ -3,6 +3,7 @@ from django.core.exceptions import ObjectDoesNotExist
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
+from rest_framework.fields import ListField
from core.api.nested_serializers import NestedDataSourceSerializer, NestedDataFileSerializer, NestedJobSerializer
from core.api.serializers import JobSerializer
@@ -49,8 +50,6 @@ __all__ = (
'SavedFilterSerializer',
'ScriptDetailSerializer',
'ScriptInputSerializer',
- 'ScriptLogMessageSerializer',
- 'ScriptOutputSerializer',
'ScriptSerializer',
'TagSerializer',
'WebhookSerializer',
@@ -126,11 +125,15 @@ class CustomFieldSerializer(ValidatedModelSerializer):
type = ChoiceField(choices=CustomFieldTypeChoices)
object_type = ContentTypeField(
queryset=ContentType.objects.all(),
- required=False
+ required=False,
+ allow_null=True
)
filter_logic = ChoiceField(choices=CustomFieldFilterLogicChoices, required=False)
data_type = serializers.SerializerMethodField()
- choice_set = NestedCustomFieldChoiceSetSerializer(required=False)
+ choice_set = NestedCustomFieldChoiceSetSerializer(
+ required=False,
+ allow_null=True
+ )
ui_visible = ChoiceField(choices=CustomFieldUIVisibleChoices, required=False)
ui_editable = ChoiceField(choices=CustomFieldUIEditableChoices, required=False)
@@ -171,6 +174,12 @@ class CustomFieldChoiceSetSerializer(ValidatedModelSerializer):
choices=CustomFieldChoiceSetBaseChoices,
required=False
)
+ extra_choices = serializers.ListField(
+ child=serializers.ListField(
+ min_length=2,
+ max_length=2
+ )
+ )
class Meta:
model = CustomFieldChoiceSet
@@ -593,22 +602,6 @@ class ScriptInputSerializer(serializers.Serializer):
return value
-class ScriptLogMessageSerializer(serializers.Serializer):
- status = serializers.SerializerMethodField(read_only=True)
- message = serializers.SerializerMethodField(read_only=True)
-
- def get_status(self, instance):
- return instance[0]
-
- def get_message(self, instance):
- return instance[1]
-
-
-class ScriptOutputSerializer(serializers.Serializer):
- log = ScriptLogMessageSerializer(many=True, read_only=True)
- output = serializers.CharField(read_only=True)
-
-
#
# Change logging
#
diff --git a/netbox/extras/api/urls.py b/netbox/extras/api/urls.py
index 1616b8554..68b4488bc 100644
--- a/netbox/extras/api/urls.py
+++ b/netbox/extras/api/urls.py
@@ -20,7 +20,6 @@ router.register('image-attachments', views.ImageAttachmentViewSet)
router.register('journal-entries', views.JournalEntryViewSet)
router.register('config-contexts', views.ConfigContextViewSet)
router.register('config-templates', views.ConfigTemplateViewSet)
-router.register('reports', views.ReportViewSet, basename='report')
router.register('scripts', views.ScriptViewSet, basename='script')
router.register('object-changes', views.ObjectChangeViewSet)
router.register('content-types', views.ContentTypeViewSet)
diff --git a/netbox/extras/api/views.py b/netbox/extras/api/views.py
index e0fca8617..8f5841da1 100644
--- a/netbox/extras/api/views.py
+++ b/netbox/extras/api/views.py
@@ -16,7 +16,6 @@ from core.choices import JobStatusChoices
from core.models import Job
from extras import filtersets
from extras.models import *
-from extras.reports import get_module_and_report, run_report
from extras.scripts import get_module_and_script, run_script
from netbox.api.authentication import IsAuthenticatedOrLoginNotRequired
from netbox.api.features import SyncedDataMixin
@@ -211,111 +210,6 @@ class ConfigTemplateViewSet(SyncedDataMixin, ConfigTemplateRenderMixin, NetBoxMo
return self.render_configtemplate(request, configtemplate, context)
-#
-# Reports
-#
-
-class ReportViewSet(ViewSet):
- permission_classes = [IsAuthenticatedOrLoginNotRequired]
- _ignore_model_permissions = True
- schema = None
- lookup_value_regex = '[^/]+' # Allow dots
-
- def _get_report(self, pk):
- try:
- module_name, report_name = pk.split('.', maxsplit=1)
- except ValueError:
- raise Http404
-
- module, report = get_module_and_report(module_name, report_name)
- if report is None:
- raise Http404
-
- return module, report
-
- def list(self, request):
- """
- Compile all reports and their related results (if any). Result data is deferred in the list view.
- """
- results = {
- job.name: job
- for job in Job.objects.filter(
- object_type=ContentType.objects.get(app_label='extras', model='reportmodule'),
- status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
- ).order_by('name', '-created').distinct('name').defer('data')
- }
-
- report_list = []
- for report_module in ReportModule.objects.restrict(request.user):
- report_list.extend([report() for report in report_module.reports.values()])
-
- # Attach Job objects to each report (if any)
- for report in report_list:
- report.result = results.get(report.name, None)
-
- serializer = serializers.ReportSerializer(report_list, many=True, context={
- 'request': request,
- })
-
- return Response({'count': len(report_list), 'results': serializer.data})
-
- def retrieve(self, request, pk):
- """
- Retrieve a single Report identified as ".".
- """
- module, report = self._get_report(pk)
-
- # Retrieve the Report and Job, if any.
- object_type = ContentType.objects.get(app_label='extras', model='reportmodule')
- report.result = Job.objects.filter(
- object_type=object_type,
- name=report.name,
- status__in=JobStatusChoices.TERMINAL_STATE_CHOICES
- ).first()
-
- serializer = serializers.ReportDetailSerializer(report, context={
- 'request': request
- })
-
- return Response(serializer.data)
-
- @action(detail=True, methods=['post'])
- def run(self, request, pk):
- """
- Run a Report identified as ".