mirror of
https://github.com/netbox-community/netbox.git
synced 2025-08-26 09:16:10 -06:00
Merge branch 'develop' into feature
This commit is contained in:
commit
a461123336
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.4.3
|
placeholder: v3.4.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.4.3
|
placeholder: v3.4.4
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
119
README.md
119
README.md
@ -1,71 +1,59 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo.svg" width="400" alt="NetBox logo" />
|
<img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo.svg" width="400" alt="NetBox logo" />
|
||||||
|
|
||||||
|
The premiere source of truth powering network automation
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
NetBox is the leading solution for modeling and documenting modern networks. By
|
NetBox is the leading solution for modeling and documenting modern networks. By
|
||||||
combining the traditional disciplines of IP address management (IPAM) and
|
combining the traditional disciplines of IP address management (IPAM) and
|
||||||
datacenter infrastructure management (DCIM) with powerful APIs and extensions,
|
datacenter infrastructure management (DCIM) with powerful APIs and extensions,
|
||||||
NetBox provides the ideal "source of truth" to power network automation.
|
NetBox provides the ideal "source of truth" to power network automation.
|
||||||
Available as open source software under the Apache 2.0 license, NetBox is
|
Available as open source software under the Apache 2.0 license, NetBox serves
|
||||||
employed by thousands of organizations around the world.
|
as the cornerstone for network automation in thousands of organizations.
|
||||||
|
|
||||||

|
* **Physical infrasucture:** 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.
|
||||||
[](https://github.com/netbox-community/netbox/commits)
|
* **Data circuits:** Confidently manage the delivery of crtical circuits from various service providers, modeled seamlessly alongside your own infrastructure.
|
||||||
[](https://github.com/netbox-community/netbox/issues)
|
* **Power tracking:** Map the distribution of power from upstream sources to individual feeds and outlets.
|
||||||
[](https://github.com/netbox-community/netbox/pulls)
|
* **Organization:** Manage tenant and contact assignments natively.
|
||||||
[](https://github.com/netbox-community/netbox/graphs/contributors)
|
* **Powerful search:** Easily find anything you need using a single global search function.
|
||||||
<br />Stats via [Repography](https://repography.com)
|
* **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!
|
||||||
## About NetBox
|
* **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!
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Myriad infrastructure components can be modeled in NetBox, including:
|
## Getting Started
|
||||||
|
|
||||||
* Hierarchical regions, site groups, sites, and locations
|
* Just want to explore? Check out [our public demo](https://demo.netbox.dev/) right now!
|
||||||
* Racks, devices, and device components
|
* The [official documentation](https://docs.netbox.dev) offers a comprehensive introduction.
|
||||||
* Cables and wireless connections
|
* Choose your deployment: [self-hosted](https://github.com/netbox-community/netbox), [Docker](https://github.com/netbox-community/netbox-docker), or [NetBox Cloud](https://netboxlabs.com/netbox-cloud/).
|
||||||
* Power distribution
|
* Check out [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions) for even more projects to get the most out of NetBox!
|
||||||
* Data circuits and providers
|
|
||||||
* Virtual machines and clusters
|
|
||||||
* IP prefixes, ranges, and addresses
|
|
||||||
* VRFs and route targets
|
|
||||||
* L2VPN and overlays
|
|
||||||
* FHRP groups (VRRP, HSRP, etc.)
|
|
||||||
* AS numbers
|
|
||||||
* VLANs and scoped VLAN groups
|
|
||||||
* Organizational tenants and contacts
|
|
||||||
|
|
||||||
In addition to its extensive built-in models and functionality, NetBox can be
|
## Get Involved
|
||||||
customized and extended through the use of:
|
|
||||||
|
|
||||||
* Custom fields
|
* Follow [@NetBoxOfficial](https://twitter.com/NetBoxOfficial) on Twitter!
|
||||||
* Custom links
|
* Join the conversation on [the discussion forum](https://github.com/netbox-community/netbox/discussions) and [Slack](https://netdev.chat/)!
|
||||||
* Configuration contexts
|
* 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.
|
||||||
* Custom model validation rules
|
* Contributions from the community are encouraged and appreciated! Check out our [contributing guide](CONTRIBUTING.md) to get started.
|
||||||
* Reports
|
|
||||||
* Custom scripts
|
|
||||||
* Export templates
|
|
||||||
* Conditional webhooks
|
|
||||||
* Plugins
|
|
||||||
* Single sign-on (SSO) authentication
|
|
||||||
* NAPALM integration
|
|
||||||
* Detailed change logging
|
|
||||||
|
|
||||||
NetBox also features a complete REST API as well as a GraphQL API for easily
|
## Project Stats
|
||||||
integrating with other tools and systems.
|
|
||||||
|
<div align="center">
|
||||||
The complete documentation for NetBox can be found at [docs.netbox.dev](https://docs.netbox.dev/).
|
<a href="https://github.com/netbox-community/netbox/commits"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_timeline.svg" alt="Timeline graph"></a>
|
||||||
A public demo instance is available at [demo.netbox.dev](https://demo.netbox.dev).
|
<a href="https://github.com/netbox-community/netbox/issues"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_issues.svg" alt="Issues graph"></a>
|
||||||
|
<a href="https://github.com/netbox-community/netbox/pulls"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_prs.svg" alt="Pull requests graph"></a>
|
||||||
NetBox runs as a web application atop the [Django](https://www.djangoproject.com/)
|
<a href="https://github.com/netbox-community/netbox/graphs/contributors"><img src="https://images.repography.com/29023055/netbox-community/netbox/recent-activity/31db894eee74b8a5475e3af307a81b6c_users.svg" alt="Top contributors"></a>
|
||||||
Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a
|
<br />Stats via <a href="https://repography.com">Repography</a>
|
||||||
complete list of requirements, see `requirements.txt`. The code is available
|
</div>
|
||||||
[on GitHub](https://github.com/netbox-community/netbox).
|
|
||||||
|
## Sponsors
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h3>Thank you to our sponsors!</h3>
|
|
||||||
|
|
||||||
[](https://netboxlabs.com)
|
[](https://netboxlabs.com)
|
||||||
|
|
||||||
@ -76,34 +64,10 @@ complete list of requirements, see `requirements.txt`. The code is available
|
|||||||
[](https://sentry.io)
|
[](https://sentry.io)
|
||||||
|
|
||||||
[](https://metal.equinix.com)
|
[](https://metal.equinix.com)
|
||||||
|
|
||||||
[](https://stellar.tech)
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
### Discussion
|
## Screenshots
|
||||||
|
|
||||||
* [GitHub Discussions](https://github.com/netbox-community/netbox/discussions) - Discussion forum hosted by GitHub; ideal for Q&A and other structured discussions
|
|
||||||
* [Slack](https://netdev.chat/) - Real-time chat hosted by the NetDev Community; best for unstructured discussion or just hanging out
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Please see [the documentation](https://docs.netbox.dev/) for
|
|
||||||
instructions on installing NetBox. To upgrade NetBox, please download the
|
|
||||||
[latest release](https://github.com/netbox-community/netbox/releases) and
|
|
||||||
run `upgrade.sh`.
|
|
||||||
|
|
||||||
### Providing Feedback
|
|
||||||
|
|
||||||
The best platform for general feedback, assistance, and other discussion is our
|
|
||||||
[GitHub discussions](https://github.com/netbox-community/netbox/discussions).
|
|
||||||
To report a bug or request a specific feature, please open a GitHub issue using
|
|
||||||
the [appropriate template](https://github.com/netbox-community/netbox/issues/new/choose).
|
|
||||||
|
|
||||||
If you are interested in contributing to the development of NetBox, please read
|
|
||||||
our [contributing guide](CONTRIBUTING.md) prior to beginning any work.
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
|
||||||
")
|
")
|
||||||
|
|
||||||
@ -112,8 +76,3 @@ our [contributing guide](CONTRIBUTING.md) prior to beginning any work.
|
|||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Related projects
|
|
||||||
|
|
||||||
Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions)
|
|
||||||
for a list of relevant community projects.
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# HTML sanitizer
|
# HTML sanitizer
|
||||||
# https://github.com/mozilla/bleach
|
# https://github.com/mozilla/bleach
|
||||||
bleach
|
bleach<6.0
|
||||||
|
|
||||||
# The Python web framework on which NetBox is built
|
# The Python web framework on which NetBox is built
|
||||||
# https://github.com/django/django
|
# https://github.com/django/django
|
||||||
|
@ -140,6 +140,19 @@ obj.full_clean()
|
|||||||
obj.save()
|
obj.save()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Error handling
|
||||||
|
|
||||||
|
Sometimes things go wrong and a script will run into an `Exception`. If that happens and an uncaught exception is raised by the custom script, the execution is aborted and a full stack trace is reported.
|
||||||
|
|
||||||
|
Although this is helpful for debugging, in some situations it might be required to cleanly abort the execution of a custom script (e.g. because of invalid input data) and thereby make sure no changes are performed on the database. In this case the script can throw an `AbortScript` exception, which will prevent the stack trace from being reported, but still terminating the script's execution and reporting a given error message.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from utilities.exceptions import AbortScript
|
||||||
|
|
||||||
|
if some_error:
|
||||||
|
raise AbortScript("Some meaningful error message")
|
||||||
|
```
|
||||||
|
|
||||||
## Variable Reference
|
## Variable Reference
|
||||||
|
|
||||||
### Default Options
|
### Default Options
|
||||||
|
@ -52,4 +52,4 @@ NetBox is built on the enormously popular [Django](http://www.djangoproject.com/
|
|||||||
* Try out our [public demo](https://demo.netbox.dev/) if you want to jump right in
|
* Try out our [public demo](https://demo.netbox.dev/) if you want to jump right in
|
||||||
* The [installation guide](./installation/index.md) will help you get your own deployment up and running
|
* The [installation guide](./installation/index.md) will help you get your own deployment up and running
|
||||||
* Or try the community [Docker image](https://github.com/netbox-community/netbox-docker) for a low-touch approach
|
* Or try the community [Docker image](https://github.com/netbox-community/netbox-docker) for a low-touch approach
|
||||||
* [NetBox Cloud](https://www.getnetbox.io/) is a hosted solution offered by NS1
|
* [NetBox Cloud](https://netboxlabs.com/netbox-cloud) is a managed solution offered by [NetBox Labs](https://netboxlabs.com/)
|
||||||
|
@ -48,7 +48,7 @@ menu_items = (item1, item2, item3)
|
|||||||
|
|
||||||
Each menu item represents a link and (optionally) a set of buttons comprising one entry in NetBox's navigation menu. Menu items are defined as PluginMenuItem instances. An example is shown below.
|
Each menu item represents a link and (optionally) a set of buttons comprising one entry in NetBox's navigation menu. Menu items are defined as PluginMenuItem instances. An example is shown below.
|
||||||
|
|
||||||
```python filename="navigation.py"
|
```python title="navigation.py"
|
||||||
from extras.plugins import PluginMenuButton, PluginMenuItem
|
from extras.plugins import PluginMenuButton, PluginMenuItem
|
||||||
from utilities.choices import ButtonColorChoices
|
from utilities.choices import ButtonColorChoices
|
||||||
|
|
||||||
|
@ -1,19 +1,31 @@
|
|||||||
# NetBox v3.4
|
# NetBox v3.4
|
||||||
|
|
||||||
## v3.4.4 (FUTURE)
|
## v3.4.5 (FUTURE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3.4.4 (2023-02-02)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
|
||||||
* [#10762](https://github.com/netbox-community/netbox/issues/10762) - Permit selection custom fields to have only one choice
|
* [#10762](https://github.com/netbox-community/netbox/issues/10762) - Permit selection custom fields to have only one choice
|
||||||
|
* [#11152](https://github.com/netbox-community/netbox/issues/11152) - Introduce AbortScript exception to elegantly abort scripts
|
||||||
|
* [#11554](https://github.com/netbox-community/netbox/issues/11554) - Add module types count to manufacturers list
|
||||||
* [#11585](https://github.com/netbox-community/netbox/issues/11585) - Add IP address filters for services
|
* [#11585](https://github.com/netbox-community/netbox/issues/11585) - Add IP address filters for services
|
||||||
|
* [#11598](https://github.com/netbox-community/netbox/issues/11598) - Add buttons to easily switch between rack list and elevations views
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#11267](https://github.com/netbox-community/netbox/issues/11267) - Avoid catching ImportErrors when loading plugin resources
|
||||||
* [#11487](https://github.com/netbox-community/netbox/issues/11487) - Remove "set null" option from non-writable custom fields during bulk edit
|
* [#11487](https://github.com/netbox-community/netbox/issues/11487) - Remove "set null" option from non-writable custom fields during bulk edit
|
||||||
* [#11491](https://github.com/netbox-community/netbox/issues/11491) - Show edit/delete buttons in user tokens table
|
* [#11491](https://github.com/netbox-community/netbox/issues/11491) - Show edit/delete buttons in user tokens table
|
||||||
* [#11528](https://github.com/netbox-community/netbox/issues/11528) - Permit import of devices using uploaded file
|
* [#11528](https://github.com/netbox-community/netbox/issues/11528) - Permit import of devices using uploaded file
|
||||||
* [#11555](https://github.com/netbox-community/netbox/issues/11555) - Avoid inadvertent interpretation of search query as regular expression under global search (previously [#11516](https://github.com/netbox-community/netbox/issues/11516))
|
* [#11555](https://github.com/netbox-community/netbox/issues/11555) - Avoid inadvertent interpretation of search query as regular expression under global search (previously [#11516](https://github.com/netbox-community/netbox/issues/11516))
|
||||||
* [#11562](https://github.com/netbox-community/netbox/issues/11562) - Correct ordering of virtual chassis interfaces with duplicate names
|
* [#11562](https://github.com/netbox-community/netbox/issues/11562) - Correct ordering of virtual chassis interfaces with duplicate names
|
||||||
|
* [#11574](https://github.com/netbox-community/netbox/issues/11574) - Fix exception when attempting to schedule reports/scripts
|
||||||
|
* [#11620](https://github.com/netbox-community/netbox/issues/11620) - Correct available filter choices for interface PoE type
|
||||||
|
* [#11635](https://github.com/netbox-community/netbox/issues/11635) - Pre-populate assigned VRF when following "first available IP" link from prefix view
|
||||||
|
* [#11650](https://github.com/netbox-community/netbox/issues/11650) - Display error message when attempting to create device component with duplicate name
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1170,7 +1170,7 @@ class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
|||||||
label='PoE mode'
|
label='PoE mode'
|
||||||
)
|
)
|
||||||
poe_type = MultipleChoiceField(
|
poe_type = MultipleChoiceField(
|
||||||
choices=InterfacePoEModeChoices,
|
choices=InterfacePoETypeChoices,
|
||||||
required=False,
|
required=False,
|
||||||
label='PoE type'
|
label='PoE type'
|
||||||
)
|
)
|
||||||
|
@ -595,6 +595,7 @@ class RackListView(generic.ObjectListView):
|
|||||||
filterset = filtersets.RackFilterSet
|
filterset = filtersets.RackFilterSet
|
||||||
filterset_form = forms.RackFilterForm
|
filterset_form = forms.RackFilterForm
|
||||||
table = tables.RackTable
|
table = tables.RackTable
|
||||||
|
template_name = 'dcim/rack_list.html'
|
||||||
|
|
||||||
|
|
||||||
class RackElevationListView(generic.ObjectListView):
|
class RackElevationListView(generic.ObjectListView):
|
||||||
|
@ -45,12 +45,16 @@ class ScriptForm(BootstrapMixin, forms.Form):
|
|||||||
self.fields['_interval'] = interval
|
self.fields['_interval'] = interval
|
||||||
self.fields['_commit'] = commit
|
self.fields['_commit'] = commit
|
||||||
|
|
||||||
def clean__schedule_at(self):
|
def clean(self):
|
||||||
scheduled_time = self.cleaned_data['_schedule_at']
|
scheduled_time = self.cleaned_data['_schedule_at']
|
||||||
if scheduled_time and scheduled_time < timezone.now():
|
if scheduled_time and scheduled_time < local_now():
|
||||||
raise forms.ValidationError(_('Scheduled time must be in the future.'))
|
raise forms.ValidationError(_('Scheduled time must be in the future.'))
|
||||||
|
|
||||||
return scheduled_time
|
# When interval is used without schedule at, raise an exception
|
||||||
|
if self.cleaned_data['_interval'] and not scheduled_time:
|
||||||
|
raise forms.ValidationError(_('Scheduled time must be set when recurs is used.'))
|
||||||
|
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def requires_input(self):
|
def requires_input(self):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import collections
|
import collections
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -21,6 +22,15 @@ registry['plugins'] = {
|
|||||||
'template_extensions': collections.defaultdict(list),
|
'template_extensions': collections.defaultdict(list),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DEFAULT_RESOURCE_PATHS = {
|
||||||
|
'search_indexes': 'search.indexes',
|
||||||
|
'graphql_schema': 'graphql.schema',
|
||||||
|
'menu': 'navigation.menu',
|
||||||
|
'menu_items': 'navigation.menu_items',
|
||||||
|
'template_extensions': 'template_content.template_extensions',
|
||||||
|
'user_preferences': 'preferences.preferences',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Plugin AppConfig class
|
# Plugin AppConfig class
|
||||||
@ -58,58 +68,53 @@ class PluginConfig(AppConfig):
|
|||||||
# Django apps to append to INSTALLED_APPS when plugin requires them.
|
# Django apps to append to INSTALLED_APPS when plugin requires them.
|
||||||
django_apps = []
|
django_apps = []
|
||||||
|
|
||||||
# Default integration paths. Plugin authors can override these to customize the paths to
|
# Optional plugin resources
|
||||||
# integrated components.
|
search_indexes = None
|
||||||
search_indexes = 'search.indexes'
|
graphql_schema = None
|
||||||
graphql_schema = 'graphql.schema'
|
menu = None
|
||||||
menu = 'navigation.menu'
|
menu_items = None
|
||||||
menu_items = 'navigation.menu_items'
|
template_extensions = None
|
||||||
template_extensions = 'template_content.template_extensions'
|
user_preferences = None
|
||||||
user_preferences = 'preferences.preferences'
|
|
||||||
|
def _load_resource(self, name):
|
||||||
|
# Import from the configured path, if defined.
|
||||||
|
if getattr(self, name):
|
||||||
|
return import_string(f"{self.__module__}.{self.name}")
|
||||||
|
|
||||||
|
# Fall back to the resource's default path. Return None if the module has not been provided.
|
||||||
|
default_path = f'{self.__module__}.{DEFAULT_RESOURCE_PATHS[name]}'
|
||||||
|
default_module, resource_name = default_path.rsplit('.', 1)
|
||||||
|
try:
|
||||||
|
module = import_module(default_module)
|
||||||
|
return getattr(module, resource_name, None)
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
plugin_name = self.name.rsplit('.', 1)[-1]
|
plugin_name = self.name.rsplit('.', 1)[-1]
|
||||||
|
|
||||||
# Register search extensions (if defined)
|
# Register search extensions (if defined)
|
||||||
try:
|
search_indexes = self._load_resource('search_indexes') or []
|
||||||
search_indexes = import_string(f"{self.__module__}.{self.search_indexes}")
|
|
||||||
for idx in search_indexes:
|
for idx in search_indexes:
|
||||||
register_search(idx)
|
register_search(idx)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Register template content (if defined)
|
# Register template content (if defined)
|
||||||
try:
|
if template_extensions := self._load_resource('template_extensions'):
|
||||||
template_extensions = import_string(f"{self.__module__}.{self.template_extensions}")
|
|
||||||
register_template_extensions(template_extensions)
|
register_template_extensions(template_extensions)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Register navigation menu and/or menu items (if defined)
|
# Register navigation menu and/or menu items (if defined)
|
||||||
try:
|
if menu := self._load_resource('menu'):
|
||||||
menu = import_string(f"{self.__module__}.{self.menu}")
|
|
||||||
register_menu(menu)
|
register_menu(menu)
|
||||||
except ImportError:
|
if menu_items := self._load_resource('menu_items'):
|
||||||
pass
|
|
||||||
try:
|
|
||||||
menu_items = import_string(f"{self.__module__}.{self.menu_items}")
|
|
||||||
register_menu_items(self.verbose_name, menu_items)
|
register_menu_items(self.verbose_name, menu_items)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Register GraphQL schema (if defined)
|
# Register GraphQL schema (if defined)
|
||||||
try:
|
if graphql_schema := self._load_resource('graphql_schema'):
|
||||||
graphql_schema = import_string(f"{self.__module__}.{self.graphql_schema}")
|
|
||||||
register_graphql_schema(graphql_schema)
|
register_graphql_schema(graphql_schema)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Register user preferences (if defined)
|
# Register user preferences (if defined)
|
||||||
try:
|
if user_preferences := self._load_resource('user_preferences'):
|
||||||
user_preferences = import_string(f"{self.__module__}.{self.user_preferences}")
|
|
||||||
register_user_preferences(plugin_name, user_preferences)
|
register_user_preferences(plugin_name, user_preferences)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate(cls, user_config, netbox_version):
|
def validate(cls, user_config, netbox_version):
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
from importlib import import_module
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include
|
from django.conf.urls import include
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string, module_has_submodule
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
@ -19,24 +21,21 @@ plugin_admin_patterns = [
|
|||||||
|
|
||||||
# Register base/API URL patterns for each plugin
|
# Register base/API URL patterns for each plugin
|
||||||
for plugin_path in settings.PLUGINS:
|
for plugin_path in settings.PLUGINS:
|
||||||
|
plugin = import_module(plugin_path)
|
||||||
plugin_name = plugin_path.split('.')[-1]
|
plugin_name = plugin_path.split('.')[-1]
|
||||||
app = apps.get_app_config(plugin_name)
|
app = apps.get_app_config(plugin_name)
|
||||||
base_url = getattr(app, 'base_url') or app.label
|
base_url = getattr(app, 'base_url') or app.label
|
||||||
|
|
||||||
# Check if the plugin specifies any base URLs
|
# Check if the plugin specifies any base URLs
|
||||||
try:
|
if module_has_submodule(plugin, 'urls'):
|
||||||
urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns")
|
urlpatterns = import_string(f"{plugin_path}.urls.urlpatterns")
|
||||||
plugin_patterns.append(
|
plugin_patterns.append(
|
||||||
path(f"{base_url}/", include((urlpatterns, app.label)))
|
path(f"{base_url}/", include((urlpatterns, app.label)))
|
||||||
)
|
)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Check if the plugin specifies any API URLs
|
# Check if the plugin specifies any API URLs
|
||||||
try:
|
if module_has_submodule(plugin, 'api.urls'):
|
||||||
urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns")
|
urlpatterns = import_string(f"{plugin_path}.api.urls.urlpatterns")
|
||||||
plugin_api_patterns.append(
|
plugin_api_patterns.append(
|
||||||
path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
|
path(f"{base_url}/", include((urlpatterns, f"{app.label}-api")))
|
||||||
)
|
)
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
@ -21,7 +21,7 @@ from extras.models import JobResult
|
|||||||
from extras.signals import clear_webhooks
|
from extras.signals import clear_webhooks
|
||||||
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
from ipam.formfields import IPAddressFormField, IPNetworkFormField
|
||||||
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
from ipam.validators import MaxPrefixLengthValidator, MinPrefixLengthValidator, prefix_validator
|
||||||
from utilities.exceptions import AbortTransaction
|
from utilities.exceptions import AbortScript, AbortTransaction
|
||||||
from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
from utilities.forms import add_blank_choice, DynamicModelChoiceField, DynamicModelMultipleChoiceField
|
||||||
from .context_managers import change_logging
|
from .context_managers import change_logging
|
||||||
from .forms import ScriptForm
|
from .forms import ScriptForm
|
||||||
@ -470,6 +470,14 @@ def run_script(data, request, commit=True, *args, **kwargs):
|
|||||||
except AbortTransaction:
|
except AbortTransaction:
|
||||||
script.log_info("Database changes have been reverted automatically.")
|
script.log_info("Database changes have been reverted automatically.")
|
||||||
clear_webhooks.send(request)
|
clear_webhooks.send(request)
|
||||||
|
except AbortScript as e:
|
||||||
|
script.log_failure(
|
||||||
|
f"Script aborted with error: {e}"
|
||||||
|
)
|
||||||
|
script.log_info("Database changes have been reverted due to error.")
|
||||||
|
logger.error(f"Script aborted with error: {e}")
|
||||||
|
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
|
||||||
|
clear_webhooks.send(request)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
stacktrace = traceback.format_exc()
|
stacktrace = traceback.format_exc()
|
||||||
script.log_failure(
|
script.log_failure(
|
||||||
|
@ -459,6 +459,9 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
|
|
||||||
if component_form.is_valid():
|
if component_form.is_valid():
|
||||||
new_components.append(component_form)
|
new_components.append(component_form)
|
||||||
|
else:
|
||||||
|
form.errors.update(component_form.errors)
|
||||||
|
break
|
||||||
|
|
||||||
if not form.errors and not component_form.errors:
|
if not form.errors and not component_form.errors:
|
||||||
try:
|
try:
|
||||||
|
@ -7,6 +7,9 @@
|
|||||||
{% block controls %}
|
{% block controls %}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
|
<a href="{% url 'dcim:rack_list' %}{% querystring request %}" class="btn btn-sm btn-primary">
|
||||||
|
<i class="mdi mdi-format-list-checkbox"></i> View List
|
||||||
|
</a>
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<select class="btn btn-sm btn-outline-secondary rack-view">
|
<select class="btn btn-sm btn-outline-secondary rack-view">
|
||||||
<option value="images-and-labels" selected="selected">Images and Labels</option>
|
<option value="images-and-labels" selected="selected">Images and Labels</option>
|
||||||
|
9
netbox/templates/dcim/rack_list.html
Normal file
9
netbox/templates/dcim/rack_list.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends 'generic/object_list.html' %}
|
||||||
|
{% load helpers %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_controls %}
|
||||||
|
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request %}" class="btn btn-sm btn-primary">
|
||||||
|
<i class="mdi mdi-view-day-outline"></i> View Elevations
|
||||||
|
</a>
|
||||||
|
{% endblock %}
|
@ -26,7 +26,6 @@ Context:
|
|||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
{% plugin_list_buttons model %}
|
{% plugin_list_buttons model %}
|
||||||
|
|
||||||
{% block extra_controls %}{% endblock %}
|
{% block extra_controls %}{% endblock %}
|
||||||
{% if 'add' in actions %}
|
{% if 'add' in actions %}
|
||||||
{% add_button model %}
|
{% add_button model %}
|
||||||
|
@ -133,7 +133,7 @@
|
|||||||
{% with first_available_ip=object.get_first_available_ip %}
|
{% with first_available_ip=object.get_first_available_ip %}
|
||||||
{% if first_available_ip %}
|
{% if first_available_ip %}
|
||||||
{% if perms.ipam.add_ipaddress %}
|
{% if perms.ipam.add_ipaddress %}
|
||||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}">{{ first_available_ip }}</a>
|
<a href="{% url 'ipam:ipaddress_add' %}?address={{ first_available_ip }}{% if object.vrf %}&vrf={{ object.vrf_id }}{% endif %}">{{ first_available_ip }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ first_available_ip }}
|
{{ first_available_ip }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -24,6 +24,13 @@ class AbortRequest(Exception):
|
|||||||
self.message = message
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
class AbortScript(Exception):
|
||||||
|
"""
|
||||||
|
Raised to cleanly abort a script.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PermissionsViolation(Exception):
|
class PermissionsViolation(Exception):
|
||||||
"""
|
"""
|
||||||
Raised when an operation was prevented because it would violate the
|
Raised when an operation was prevented because it would violate the
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
bleach==5.0.1
|
bleach==5.0.1
|
||||||
Django==4.1.5
|
Django==4.1.6
|
||||||
django-cors-headers==3.13.0
|
django-cors-headers==3.13.0
|
||||||
django-debug-toolbar==3.8.1
|
django-debug-toolbar==3.8.1
|
||||||
django-filter==22.1
|
django-filter==22.1
|
||||||
@ -19,13 +19,13 @@ graphene-django==3.0.0
|
|||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
Markdown==3.3.7
|
Markdown==3.3.7
|
||||||
mkdocs-material==9.0.6
|
mkdocs-material==9.0.10
|
||||||
mkdocstrings[python-legacy]==0.20.0
|
mkdocstrings[python-legacy]==0.20.0
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==9.4.0
|
Pillow==9.4.0
|
||||||
psycopg2-binary==2.9.5
|
psycopg2-binary==2.9.5
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
sentry-sdk==1.13.0
|
sentry-sdk==1.14.0
|
||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
social-auth-core[openidconnect]==4.3.0
|
social-auth-core[openidconnect]==4.3.0
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
|
Loading…
Reference in New Issue
Block a user