mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 04:02:52 -06:00
Merge feature -> develop
This commit is contained in:
commit
5fc373f5cc
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -17,7 +17,7 @@ body:
|
|||||||
What version of NetBox are you currently running? (If you don't have access to the most
|
What version of NetBox are you currently running? (If you don't have access to the most
|
||||||
recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
|
recent NetBox release, consider testing on our [demo instance](https://demo.netbox.dev/)
|
||||||
before opening a bug report to see if your issue has already been addressed.)
|
before opening a bug report to see if your issue has already been addressed.)
|
||||||
placeholder: v3.0.8
|
placeholder: v3.0.9
|
||||||
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.0.8
|
placeholder: v3.0.9
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
@ -5,4 +5,4 @@
|
|||||||
|
|
||||||
# Example Power Topology
|
# Example Power Topology
|
||||||
|
|
||||||

|

|
||||||
|
@ -240,7 +240,7 @@ An IPv4 or IPv6 network with a mask. Returns a `netaddr.IPNetwork` object. Two a
|
|||||||
!!! note
|
!!! note
|
||||||
To run a custom script, a user must be assigned the `extras.run_script` permission. This is achieved by assigning the user (or group) a permission on the Script object and specifying the `run` action in the admin UI as shown below.
|
To run a custom script, a user must be assigned the `extras.run_script` permission. This is achieved by assigning the user (or group) a permission on the Script object and specifying the `run` action in the admin UI as shown below.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### Via the Web UI
|
### Via the Web UI
|
||||||
|
|
||||||
@ -259,6 +259,22 @@ http://netbox/api/extras/scripts/example.MyReport/ \
|
|||||||
--data '{"data": {"foo": "somevalue", "bar": 123}, "commit": true}'
|
--data '{"data": {"foo": "somevalue", "bar": 123}, "commit": true}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Via the CLI
|
||||||
|
|
||||||
|
Scripts can be run on the CLI by invoking the management command:
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 manage.py runscript [--commit] [--loglevel {debug,info,warning,error,critical}] [--data "<data>"] <module>.<script>
|
||||||
|
```
|
||||||
|
|
||||||
|
The required ``<module>.<script>`` argument is the script to run where ``<module>`` is the name of the python file in the ``scripts`` directory without the ``.py`` extension and ``<script>`` is the name of the script class in the ``<module>`` to run.
|
||||||
|
|
||||||
|
The optional ``--data "<data>"`` argument is the data to send to the script
|
||||||
|
|
||||||
|
The optional ``--loglevel`` argument is the desired logging level to output to the console.
|
||||||
|
|
||||||
|
The optional ``--commit`` argument will commit any changes in the script to the database.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Below is an example script that creates new objects for a planned site. The user is prompted for three variables:
|
Below is an example script that creates new objects for a planned site. The user is prompted for three variables:
|
||||||
|
@ -25,7 +25,7 @@ A cable may be traced from either of its endpoints by clicking the "trace" butto
|
|||||||
|
|
||||||
In the example below, three individual cables comprise a path between devices A and D:
|
In the example below, three individual cables comprise a path between devices A and D:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Traced from Interface 1 on Device A, NetBox will show the following path:
|
Traced from Interface 1 on Device A, NetBox will show the following path:
|
||||||
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
version-3.0.md
|
|
113
docs/release-notes/index.md
Normal file
113
docs/release-notes/index.md
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# Release Notes
|
||||||
|
|
||||||
|
Listed below are the major features introduced in each NetBox release. For more detail on a specific release train, see its individual release notes page.
|
||||||
|
|
||||||
|
#### [Version 3.1](./version-3.1.md) (December 2021)
|
||||||
|
|
||||||
|
* Contact Objects ([#1344](https://github.com/netbox-community/netbox/issues/1344))
|
||||||
|
* Wireless Networks ([#3979](https://github.com/netbox-community/netbox/issues/3979))
|
||||||
|
* Dynamic Configuration Updates ([#5883](https://github.com/netbox-community/netbox/issues/5883))
|
||||||
|
* First Hop Redundancy Protocol (FHRP) Groups ([#6235](https://github.com/netbox-community/netbox/issues/6235))
|
||||||
|
* Conditional Webhooks ([#6238](https://github.com/netbox-community/netbox/issues/6238))
|
||||||
|
* Interface Bridging ([#6346](https://github.com/netbox-community/netbox/issues/6346))
|
||||||
|
* Multiple ASNs per Site ([#6732](https://github.com/netbox-community/netbox/issues/6732))
|
||||||
|
* Single Sign-On (SSO) Authentication ([#7649](https://github.com/netbox-community/netbox/issues/7649))
|
||||||
|
|
||||||
|
#### [Version 3.0](./version-3.0.md) (August 2021)
|
||||||
|
|
||||||
|
* Updated User Interface ([#5893](https://github.com/netbox-community/netbox/issues/5893))
|
||||||
|
* GraphQL API ([#2007](https://github.com/netbox-community/netbox/issues/2007))
|
||||||
|
* IP Ranges ([#834](https://github.com/netbox-community/netbox/issues/834))
|
||||||
|
* Custom Model Validation ([#5963](https://github.com/netbox-community/netbox/issues/5963))
|
||||||
|
* SVG Cable Traces ([#6000](https://github.com/netbox-community/netbox/issues/6000))
|
||||||
|
* New Views for Models Previously Under the Admin UI ([#6466](https://github.com/netbox-community/netbox/issues/6466))
|
||||||
|
* REST API Token Provisioning ([#5264](https://github.com/netbox-community/netbox/issues/5264))
|
||||||
|
* New Housekeeping Command ([#6590](https://github.com/netbox-community/netbox/issues/6590))
|
||||||
|
* Custom Queue Support for Plugins ([#6651](https://github.com/netbox-community/netbox/issues/6651))
|
||||||
|
|
||||||
|
#### [Version 2.11](./version-2.11.md) (April 2021)
|
||||||
|
|
||||||
|
* Journaling Support ([#151](https://github.com/netbox-community/netbox/issues/151))
|
||||||
|
* Parent Interface Assignments ([#1519](https://github.com/netbox-community/netbox/issues/1519))
|
||||||
|
* Pre- and Post-Change Snapshots in Webhooks ([#3451](https://github.com/netbox-community/netbox/issues/3451))
|
||||||
|
* Mark as Connected Without a Cable ([#3648](https://github.com/netbox-community/netbox/issues/3648))
|
||||||
|
* Allow Assigning Devices to Locations ([#4971](https://github.com/netbox-community/netbox/issues/4971))
|
||||||
|
* Dynamic Object Exports ([#4999](https://github.com/netbox-community/netbox/issues/4999))
|
||||||
|
* Variable Scope Support for VLAN Groups ([#5284](https://github.com/netbox-community/netbox/issues/5284))
|
||||||
|
* New Site Group Model ([#5892](https://github.com/netbox-community/netbox/issues/5892))
|
||||||
|
* Improved Change Logging ([#5913](https://github.com/netbox-community/netbox/issues/5913))
|
||||||
|
* Provider Network Modeling ([#5986](https://github.com/netbox-community/netbox/issues/5986))
|
||||||
|
|
||||||
|
#### [Version 2.10](./version-2.10.md) (December 2020)
|
||||||
|
|
||||||
|
* Route Targets ([#259](https://github.com/netbox-community/netbox/issues/259))
|
||||||
|
* REST API Bulk Deletion ([#3436](https://github.com/netbox-community/netbox/issues/3436))
|
||||||
|
* REST API Bulk Update ([#4882](https://github.com/netbox-community/netbox/issues/4882))
|
||||||
|
* Reimplementation of Custom Fields ([#4878](https://github.com/netbox-community/netbox/issues/4878))
|
||||||
|
* Improved Cable Trace Performance ([#4900](https://github.com/netbox-community/netbox/issues/4900))
|
||||||
|
|
||||||
|
#### [Version 2.9](./version-2.9.md) (August 2020)
|
||||||
|
|
||||||
|
* Object-Based Permissions ([#554](https://github.com/netbox-community/netbox/issues/554))
|
||||||
|
* Background Execution of Scripts & Reports ([#2006](https://github.com/netbox-community/netbox/issues/2006))
|
||||||
|
* Named Virtual Chassis ([#2018](https://github.com/netbox-community/netbox/issues/2018))
|
||||||
|
* Changes to Tag Creation ([#3703](https://github.com/netbox-community/netbox/issues/3703))
|
||||||
|
* Dedicated Model for VM Interfaces ([#4721](https://github.com/netbox-community/netbox/issues/4721))
|
||||||
|
* REST API Endpoints for Users and Groups ([#4877](https://github.com/netbox-community/netbox/issues/4877))
|
||||||
|
|
||||||
|
#### [Version 2.8](./version-2.8.md) (April 2020)
|
||||||
|
|
||||||
|
* Remote Authentication Support ([#2328](https://github.com/netbox-community/netbox/issues/2328))
|
||||||
|
* Plugins ([#3351](https://github.com/netbox-community/netbox/issues/3351))
|
||||||
|
|
||||||
|
#### [Version 2.7](./version-2.7.md) (January 2020)
|
||||||
|
|
||||||
|
* Enhanced Device Type Import ([#451](https://github.com/netbox-community/netbox/issues/451))
|
||||||
|
* Bulk Import of Device Components ([#822](https://github.com/netbox-community/netbox/issues/822))
|
||||||
|
* External File Storage ([#1814](https://github.com/netbox-community/netbox/issues/1814))
|
||||||
|
* Rack Elevations Rendered via SVG ([#2248](https://github.com/netbox-community/netbox/issues/2248))
|
||||||
|
|
||||||
|
#### [Version 2.6](./version-2.6.md) (June 2019)
|
||||||
|
|
||||||
|
* Power Panels and Feeds ([#54](https://github.com/netbox-community/netbox/issues/54))
|
||||||
|
* Caching ([#2647](https://github.com/netbox-community/netbox/issues/2647))
|
||||||
|
* View Permissions ([#323](https://github.com/netbox-community/netbox/issues/323))
|
||||||
|
* Custom Links ([#969](https://github.com/netbox-community/netbox/issues/969))
|
||||||
|
* Prometheus Metrics ([#3104](https://github.com/netbox-community/netbox/issues/3104))
|
||||||
|
|
||||||
|
#### [Version 2.5](./version-2.5.md) (December 2018)
|
||||||
|
|
||||||
|
* Patch Panels and Cables ([#20](https://github.com/netbox-community/netbox/issues/20))
|
||||||
|
|
||||||
|
#### [Version 2.4](./version-2.4.md) (August 2018)
|
||||||
|
|
||||||
|
* Webhooks ([#81](https://github.com/netbox-community/netbox/issues/81))
|
||||||
|
* Tagging ([#132](https://github.com/netbox-community/netbox/issues/132))
|
||||||
|
* Contextual Configuration Data ([#1349](https://github.com/netbox-community/netbox/issues/1349))
|
||||||
|
* Change Logging ([#1898](https://github.com/netbox-community/netbox/issues/1898))
|
||||||
|
|
||||||
|
#### [Version 2.3](./version-2.3.md) (February 2018)
|
||||||
|
|
||||||
|
* Virtual Chassis ([#99](https://github.com/netbox-community/netbox/issues/99))
|
||||||
|
* Interface VLAN Assignments ([#150](https://github.com/netbox-community/netbox/issues/150))
|
||||||
|
* Bulk Object Creation via the API ([#1553](https://github.com/netbox-community/netbox/issues/1553))
|
||||||
|
* Automatic Provisioning of Next Available Prefixes ([#1694](https://github.com/netbox-community/netbox/issues/1694))
|
||||||
|
* Bulk Renaming of Device/VM Components ([#1781](https://github.com/netbox-community/netbox/issues/1781))
|
||||||
|
|
||||||
|
#### [Version 2.2](./version-2.2.md) (October 2017)
|
||||||
|
|
||||||
|
* Virtual Machines and Clusters ([#142](https://github.com/netbox-community/netbox/issues/142))
|
||||||
|
* Custom Validation Reports ([#1511](https://github.com/netbox-community/netbox/issues/1511))
|
||||||
|
|
||||||
|
#### [Version 2.1](./version-2.1.md) (July 2017)
|
||||||
|
|
||||||
|
* IP Address Roles ([#819](https://github.com/netbox-community/netbox/issues/819))
|
||||||
|
* Automatic Provisioning of Next Available IP ([#1246](https://github.com/netbox-community/netbox/issues/1246))
|
||||||
|
* NAPALM Integration ([#1348](https://github.com/netbox-community/netbox/issues/1348))
|
||||||
|
|
||||||
|
#### [Version 2.0](./version-2.0.md) (May 2017)
|
||||||
|
|
||||||
|
* API 2.0 ([#113](https://github.com/netbox-community/netbox/issues/113))
|
||||||
|
* Image Attachments ([#152](https://github.com/netbox-community/netbox/issues/152))
|
||||||
|
* Global Search ([#159](https://github.com/netbox-community/netbox/issues/159))
|
||||||
|
* Rack Elevations View ([#951](https://github.com/netbox-community/netbox/issues/951))
|
@ -1,6 +1,29 @@
|
|||||||
# NetBox v3.0
|
# NetBox v3.0
|
||||||
|
|
||||||
## v3.0.9 (FUTURE)
|
## v3.0.10 (FUTURE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v3.0.9 (2021-11-03)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#6529](https://github.com/netbox-community/netbox/issues/6529) - Introduce the `runscript` management command
|
||||||
|
* [#6930](https://github.com/netbox-community/netbox/issues/6930) - Add an optional "ID" column to all tables
|
||||||
|
* [#7668](https://github.com/netbox-community/netbox/issues/7668) - Add "view elevations" button to location view
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#7599](https://github.com/netbox-community/netbox/issues/7599) - Improve color mode preference handling
|
||||||
|
* [#7601](https://github.com/netbox-community/netbox/issues/7601) - Correct devices count for locations within global search results
|
||||||
|
* [#7612](https://github.com/netbox-community/netbox/issues/7612) - Strip HTML from custom field descriptions
|
||||||
|
* [#7628](https://github.com/netbox-community/netbox/issues/7628) - Fix `load_yaml` method for custom scripts
|
||||||
|
* [#7643](https://github.com/netbox-community/netbox/issues/7643) - Fix circuit assignment when creating multiple terminations simultaneously
|
||||||
|
* [#7644](https://github.com/netbox-community/netbox/issues/7644) - Prevent inadvertent deletion of prior change records when deleting objects (#7333 revisited)
|
||||||
|
* [#7647](https://github.com/netbox-community/netbox/issues/7647) - Require interface assignment when designating IP address as primary for device/VM during CSV import
|
||||||
|
* [#7664](https://github.com/netbox-community/netbox/issues/7664) - Preserve initial form data when bulk edit validation fails
|
||||||
|
* [#7717](https://github.com/netbox-community/netbox/issues/7717) - Restore missing tags column on IP range table
|
||||||
|
* [#7721](https://github.com/netbox-community/netbox/issues/7721) - Retain pagination preference when `MAX_PAGE_SIZE` is zero
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|
||||||
#### Contacts ([#1344](https://github.com/netbox-community/netbox/issues/1344))
|
#### Contact Objects ([#1344](https://github.com/netbox-community/netbox/issues/1344))
|
||||||
|
|
||||||
A set of new models for tracking contact information has been introduced within the tenancy app. Users may now create individual contact objects to be associated with various models within NetBox. Each contact has a name, title, email address, etc. Contacts can be arranged in hierarchical groups for ease of management.
|
A set of new models for tracking contact information has been introduced within the tenancy app. Users may now create individual contact objects to be associated with various models within NetBox. Each contact has a name, title, email address, etc. Contacts can be arranged in hierarchical groups for ease of management.
|
||||||
|
|
||||||
|
@ -110,6 +110,8 @@ nav:
|
|||||||
- Web UI: 'development/web-ui.md'
|
- Web UI: 'development/web-ui.md'
|
||||||
- Release Checklist: 'development/release-checklist.md'
|
- Release Checklist: 'development/release-checklist.md'
|
||||||
- Release Notes:
|
- Release Notes:
|
||||||
|
- Summary: 'release-notes/index.md'
|
||||||
|
- Version 3.1: 'release-notes/version-3.1.md'
|
||||||
- Version 3.0: 'release-notes/version-3.0.md'
|
- Version 3.0: 'release-notes/version-3.0.md'
|
||||||
- Version 2.11: 'release-notes/version-2.11.md'
|
- Version 2.11: 'release-notes/version-2.11.md'
|
||||||
- Version 2.10: 'release-notes/version-2.10.md'
|
- Version 2.10: 'release-notes/version-2.10.md'
|
||||||
|
@ -11,6 +11,7 @@ def update_circuit(instance, **kwargs):
|
|||||||
When a CircuitTermination has been modified, update its parent Circuit.
|
When a CircuitTermination has been modified, update its parent Circuit.
|
||||||
"""
|
"""
|
||||||
termination_name = f'termination_{instance.term_side.lower()}'
|
termination_name = f'termination_{instance.term_side.lower()}'
|
||||||
|
instance.circuit.refresh_from_db()
|
||||||
setattr(instance.circuit, termination_name, instance)
|
setattr(instance.circuit, termination_name, instance)
|
||||||
instance.circuit.save()
|
instance.circuit.save()
|
||||||
|
|
||||||
|
@ -44,8 +44,8 @@ class ProviderTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Provider
|
model = Provider
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count', 'comments',
|
'pk', 'id', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count',
|
||||||
'tags',
|
'comments', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
|
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ class ProviderNetworkTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ProviderNetwork
|
model = ProviderNetwork
|
||||||
fields = ('pk', 'name', 'provider', 'description', 'comments', 'tags')
|
fields = ('pk', 'id', 'name', 'provider', 'description', 'comments', 'tags')
|
||||||
default_columns = ('pk', 'name', 'provider', 'description')
|
default_columns = ('pk', 'name', 'provider', 'description')
|
||||||
|
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ class CircuitTypeTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fields = ('pk', 'name', 'circuit_count', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions')
|
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ class CircuitTable(BaseTable):
|
|||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
cid = tables.Column(
|
cid = tables.Column(
|
||||||
linkify=True,
|
linkify=True,
|
||||||
verbose_name='ID'
|
verbose_name='Circuit ID'
|
||||||
)
|
)
|
||||||
provider = tables.Column(
|
provider = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
@ -127,7 +127,7 @@ class CircuitTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Circuit
|
model = Circuit
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date',
|
'pk', 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date',
|
||||||
'commit_rate', 'description', 'comments', 'tags',
|
'commit_rate', 'description', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
|
@ -43,6 +43,7 @@ class ConsoleConnectionTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = ('device', 'name', 'console_server', 'console_server_port', 'reachable')
|
fields = ('device', 'name', 'console_server', 'console_server_port', 'reachable')
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionTable(BaseTable):
|
class PowerConnectionTable(BaseTable):
|
||||||
@ -73,6 +74,7 @@ class PowerConnectionTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = ('device', 'name', 'pdu', 'outlet', 'reachable')
|
fields = ('device', 'name', 'pdu', 'outlet', 'reachable')
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionTable(BaseTable):
|
class InterfaceConnectionTable(BaseTable):
|
||||||
@ -106,3 +108,4 @@ class InterfaceConnectionTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = ('device_a', 'interface_a', 'device_b', 'interface_b', 'reachable')
|
fields = ('device_a', 'interface_a', 'device_b', 'interface_b', 'reachable')
|
||||||
|
exclude = ('id', )
|
||||||
|
@ -17,10 +17,6 @@ __all__ = (
|
|||||||
|
|
||||||
class CableTable(BaseTable):
|
class CableTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
id = tables.Column(
|
|
||||||
linkify=True,
|
|
||||||
verbose_name='ID'
|
|
||||||
)
|
|
||||||
termination_a_parent = tables.TemplateColumn(
|
termination_a_parent = tables.TemplateColumn(
|
||||||
template_code=CABLE_TERMINATION_PARENT,
|
template_code=CABLE_TERMINATION_PARENT,
|
||||||
accessor=Accessor('termination_a'),
|
accessor=Accessor('termination_a'),
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django_tables2.utils import Accessor
|
from django_tables2.utils import Accessor
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from dcim.models import (
|
from dcim.models import (
|
||||||
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, FrontPort, Interface, InventoryItem, Platform,
|
ConsolePort, ConsoleServerPort, Device, DeviceBay, DeviceRole, FrontPort, Interface, InventoryItem, Platform,
|
||||||
@ -15,6 +14,7 @@ from .template_code import *
|
|||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BaseInterfaceTable',
|
'BaseInterfaceTable',
|
||||||
|
'CableTerminationTable',
|
||||||
'ConsolePortTable',
|
'ConsolePortTable',
|
||||||
'ConsoleServerPortTable',
|
'ConsoleServerPortTable',
|
||||||
'DeviceBayTable',
|
'DeviceBayTable',
|
||||||
@ -88,7 +88,8 @@ class DeviceRoleTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'tags', 'actions',
|
'pk', 'id', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'tags',
|
||||||
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'actions')
|
default_columns = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'actions')
|
||||||
|
|
||||||
@ -120,7 +121,7 @@ class PlatformTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Platform
|
model = Platform
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args',
|
'pk', 'id', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args',
|
||||||
'description', 'tags', 'actions',
|
'description', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -193,8 +194,8 @@ class DeviceTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Device
|
model = Device
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'status', 'tenant', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial',
|
'pk', 'id', 'name', 'status', 'tenant', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial',
|
||||||
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'airflow', 'primary_ip', 'primary_ip4',
|
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'primary_ip', 'airflow', 'primary_ip4',
|
||||||
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags',
|
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -224,7 +225,7 @@ class DeviceImportTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Device
|
model = Device
|
||||||
fields = ('name', 'status', 'tenant', 'site', 'rack', 'position', 'device_role', 'device_type')
|
fields = ('id', 'name', 'status', 'tenant', 'site', 'rack', 'position', 'device_role', 'device_type')
|
||||||
empty_text = False
|
empty_text = False
|
||||||
|
|
||||||
|
|
||||||
@ -287,7 +288,7 @@ class ConsolePortTable(DeviceComponentTable, PathEndpointTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
'pk', 'id', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||||
'link_peer', 'connection', 'tags',
|
'link_peer', 'connection', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
||||||
@ -308,7 +309,7 @@ class DeviceConsolePortTable(ConsolePortTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
'pk', 'id', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||||
'link_peer', 'connection', 'tags', 'actions'
|
'link_peer', 'connection', 'tags', 'actions'
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
||||||
@ -331,8 +332,8 @@ class ConsoleServerPortTable(DeviceComponentTable, PathEndpointTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
'pk', 'id', 'name', 'device', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable',
|
||||||
'link_peer', 'connection', 'tags',
|
'cable_color', 'link_peer', 'connection', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
|
||||||
|
|
||||||
@ -353,7 +354,7 @@ class DeviceConsoleServerPortTable(ConsoleServerPortTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
'pk', 'id', 'name', 'label', 'type', 'speed', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||||
'link_peer', 'connection', 'tags', 'actions',
|
'link_peer', 'connection', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
default_columns = ('pk', 'name', 'label', 'type', 'speed', 'description', 'cable', 'connection', 'actions')
|
||||||
@ -376,8 +377,8 @@ class PowerPortTable(DeviceComponentTable, PathEndpointTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'type', 'description', 'mark_connected', 'maximum_draw', 'allocated_draw',
|
'pk', 'id', 'name', 'device', 'label', 'type', 'description', 'mark_connected', 'maximum_draw',
|
||||||
'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
default_columns = ('pk', 'name', 'device', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
|
||||||
|
|
||||||
@ -398,8 +399,8 @@ class DevicePowerPortTable(PowerPortTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected', 'cable',
|
'pk', 'id', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'mark_connected',
|
||||||
'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection',
|
'pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'cable', 'connection',
|
||||||
@ -427,8 +428,8 @@ class PowerOutletTable(DeviceComponentTable, PathEndpointTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected', 'cable',
|
'pk', 'id', 'name', 'device', 'label', 'type', 'description', 'power_port', 'feed_leg', 'mark_connected',
|
||||||
'cable_color', 'link_peer', 'connection', 'tags',
|
'cable', 'cable_color', 'link_peer', 'connection', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description')
|
default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description')
|
||||||
|
|
||||||
@ -448,7 +449,7 @@ class DevicePowerOutletTable(PowerOutletTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable',
|
'pk', 'id', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'mark_connected', 'cable',
|
||||||
'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
'cable_color', 'link_peer', 'connection', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -497,7 +498,7 @@ class InterfaceTable(DeviceComponentTable, BaseInterfaceTable, PathEndpointTable
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn',
|
'pk', 'id', 'name', 'device', 'label', 'enabled', 'type', 'mgmt_only', 'mtu', 'mode', 'mac_address', 'wwn',
|
||||||
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description',
|
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'description',
|
||||||
'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection',
|
'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection',
|
||||||
'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
|
'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans',
|
||||||
@ -532,7 +533,7 @@ class DeviceInterfaceTable(InterfaceTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag', 'mgmt_only', 'mtu', 'mode',
|
'pk', 'id', 'name', 'label', 'enabled', 'type', 'parent', 'bridge', 'lag', 'mgmt_only', 'mtu', 'mode',
|
||||||
'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', 'description',
|
'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_width', 'tx_power', 'description',
|
||||||
'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection',
|
'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans', 'link_peer', 'connection',
|
||||||
'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
|
'tags', 'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
|
||||||
@ -570,7 +571,7 @@ class FrontPortTable(DeviceComponentTable, CableTerminationTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
|
'pk', 'id', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
|
||||||
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
|
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -594,7 +595,7 @@ class DeviceFrontPortTable(FrontPortTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', 'cable',
|
'pk', 'id', 'name', 'label', 'type', 'rear_port', 'rear_port_position', 'description', 'mark_connected', 'cable',
|
||||||
'cable_color', 'link_peer', 'tags', 'actions',
|
'cable_color', 'link_peer', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -621,7 +622,7 @@ class RearPortTable(DeviceComponentTable, CableTerminationTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable',
|
'pk', 'id', 'name', 'device', 'label', 'type', 'color', 'positions', 'description', 'mark_connected', 'cable',
|
||||||
'cable_color', 'link_peer', 'tags',
|
'cable_color', 'link_peer', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description')
|
default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description')
|
||||||
@ -643,7 +644,7 @@ class DeviceRearPortTable(RearPortTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', 'cable_color',
|
'pk', 'id', 'name', 'label', 'type', 'positions', 'description', 'mark_connected', 'cable', 'cable_color',
|
||||||
'link_peer', 'tags', 'actions',
|
'link_peer', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -673,7 +674,7 @@ class DeviceBayTable(DeviceComponentTable):
|
|||||||
|
|
||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description', 'tags')
|
fields = ('pk', 'id', 'name', 'device', 'label', 'status', 'installed_device', 'description', 'tags')
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description')
|
default_columns = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description')
|
||||||
|
|
||||||
|
|
||||||
@ -693,7 +694,7 @@ class DeviceDeviceBayTable(DeviceBayTable):
|
|||||||
class Meta(DeviceComponentTable.Meta):
|
class Meta(DeviceComponentTable.Meta):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
|
'pk', 'id', 'name', 'label', 'status', 'installed_device', 'description', 'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'status', 'installed_device', 'description', 'actions',
|
'pk', 'name', 'label', 'status', 'installed_device', 'description', 'actions',
|
||||||
@ -719,7 +720,7 @@ class InventoryItemTable(DeviceComponentTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
|
'pk', 'id', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description',
|
||||||
'discovered', 'tags',
|
'discovered', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag')
|
default_columns = ('pk', 'name', 'device', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag')
|
||||||
@ -740,7 +741,7 @@ class DeviceInventoryItemTable(InventoryItemTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered',
|
'pk', 'id', 'name', 'label', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'description', 'discovered',
|
||||||
'tags', 'actions',
|
'tags', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -772,5 +773,5 @@ class VirtualChassisTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VirtualChassis
|
model = VirtualChassis
|
||||||
fields = ('pk', 'name', 'domain', 'master', 'member_count', 'tags')
|
fields = ('pk', 'id', 'name', 'domain', 'master', 'member_count', 'tags')
|
||||||
default_columns = ('pk', 'name', 'domain', 'master', 'member_count')
|
default_columns = ('pk', 'name', 'domain', 'master', 'member_count')
|
||||||
|
@ -49,9 +49,12 @@ class ManufacturerTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', 'tags',
|
'pk', 'id', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug',
|
||||||
'actions',
|
'actions',
|
||||||
)
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug', 'actions',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -80,7 +83,7 @@ class DeviceTypeTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
|
||||||
'airflow', 'comments', 'instance_count', 'tags',
|
'airflow', 'comments', 'instance_count', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -94,10 +97,16 @@ class DeviceTypeTable(BaseTable):
|
|||||||
|
|
||||||
class ComponentTemplateTable(BaseTable):
|
class ComponentTemplateTable(BaseTable):
|
||||||
pk = ToggleColumn()
|
pk = ToggleColumn()
|
||||||
|
id = tables.Column(
|
||||||
|
verbose_name='ID'
|
||||||
|
)
|
||||||
name = tables.Column(
|
name = tables.Column(
|
||||||
order_by=('_name',)
|
order_by=('_name',)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta(BaseTable.Meta):
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateTable(ComponentTemplateTable):
|
class ConsolePortTemplateTable(ComponentTemplateTable):
|
||||||
actions = ButtonsColumn(
|
actions = ButtonsColumn(
|
||||||
@ -106,7 +115,7 @@ class ConsolePortTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_consoleports'
|
return_url_extra='%23tab_consoleports'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
@ -119,7 +128,7 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_consoleserverports'
|
return_url_extra='%23tab_consoleserverports'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'type', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
@ -132,7 +141,7 @@ class PowerPortTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_powerports'
|
return_url_extra='%23tab_powerports'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = ('pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
@ -145,7 +154,7 @@ class PowerOutletTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_poweroutlets'
|
return_url_extra='%23tab_poweroutlets'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = ('pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
@ -161,7 +170,7 @@ class InterfaceTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_interfaces'
|
return_url_extra='%23tab_interfaces'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = ('pk', 'name', 'label', 'mgmt_only', 'type', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'mgmt_only', 'type', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
@ -178,7 +187,7 @@ class FrontPortTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_frontports'
|
return_url_extra='%23tab_frontports'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
@ -192,7 +201,7 @@ class RearPortTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_rearports'
|
return_url_extra='%23tab_rearports'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'type', 'color', 'positions', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
@ -205,7 +214,7 @@ class DeviceBayTemplateTable(ComponentTemplateTable):
|
|||||||
return_url_extra='%23tab_devicebays'
|
return_url_extra='%23tab_devicebays'
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(ComponentTemplateTable.Meta):
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
fields = ('pk', 'name', 'label', 'description', 'actions')
|
fields = ('pk', 'name', 'label', 'description', 'actions')
|
||||||
empty_text = "None"
|
empty_text = "None"
|
||||||
|
@ -33,7 +33,7 @@ class PowerPanelTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = PowerPanel
|
model = PowerPanel
|
||||||
fields = ('pk', 'name', 'site', 'location', 'powerfeed_count', 'tags')
|
fields = ('pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'tags')
|
||||||
default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count')
|
default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count')
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ class PowerFeedTable(CableTerminationTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = PowerFeed
|
model = PowerFeed
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
|
'pk', 'id', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
|
||||||
'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'available_power',
|
'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'available_power',
|
||||||
'comments', 'tags',
|
'comments', 'tags',
|
||||||
)
|
)
|
||||||
|
@ -31,7 +31,7 @@ class RackRoleTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RackRole
|
model = RackRole
|
||||||
fields = ('pk', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'rack_count', 'color', 'description', 'actions')
|
default_columns = ('pk', 'name', 'rack_count', 'color', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ class RackTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
|
'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
|
||||||
'width', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags',
|
'width', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -118,7 +118,7 @@ class RackReservationTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RackReservation
|
model = RackReservation
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags',
|
'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags',
|
||||||
'actions',
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
|
@ -36,7 +36,7 @@ class RegionTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Region
|
model = Region
|
||||||
fields = ('pk', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ class SiteGroupTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = SiteGroup
|
model = SiteGroup
|
||||||
fields = ('pk', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'site_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ class SiteTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Site
|
model = Site
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn_count', 'time_zone',
|
'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn_count', 'time_zone',
|
||||||
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
|
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'contact_name',
|
||||||
'contact_phone', 'contact_email', 'comments', 'tags',
|
'contact_phone', 'contact_email', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
@ -136,6 +136,7 @@ class LocationTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Location
|
model = Location
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'slug', 'tags', 'actions',
|
'pk', 'id', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'slug', 'tags',
|
||||||
|
'actions',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'actions')
|
||||||
|
158
netbox/extras/management/commands/runscript.py
Normal file
158
netbox/extras/management/commands/runscript.py
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
from extras.api.serializers import ScriptOutputSerializer
|
||||||
|
from extras.choices import JobResultStatusChoices
|
||||||
|
from extras.context_managers import change_logging
|
||||||
|
from extras.models import JobResult
|
||||||
|
from extras.scripts import get_script
|
||||||
|
from utilities.exceptions import AbortTransaction
|
||||||
|
from utilities.utils import NetBoxFakeRequest
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Run a script in Netbox"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--loglevel',
|
||||||
|
help="Logging Level (default: info)",
|
||||||
|
dest='loglevel',
|
||||||
|
default='info',
|
||||||
|
choices=['debug', 'info', 'warning', 'error', 'critical'])
|
||||||
|
parser.add_argument('--commit', help="Commit this script to database", action='store_true')
|
||||||
|
parser.add_argument('--user', help="User script is running as")
|
||||||
|
parser.add_argument('--data', help="Data as a string encapsulated JSON blob")
|
||||||
|
parser.add_argument('script', help="Script to run")
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
def _run_script():
|
||||||
|
"""
|
||||||
|
Core script execution task. We capture this within a subfunction to allow for conditionally wrapping it with
|
||||||
|
the change_logging context manager (which is bypassed if commit == False).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
script.output = script.run(data=data, commit=commit)
|
||||||
|
job_result.set_status(JobResultStatusChoices.STATUS_COMPLETED)
|
||||||
|
|
||||||
|
if not commit:
|
||||||
|
raise AbortTransaction()
|
||||||
|
|
||||||
|
except AbortTransaction:
|
||||||
|
script.log_info("Database changes have been reverted automatically.")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
stacktrace = traceback.format_exc()
|
||||||
|
script.log_failure(
|
||||||
|
f"An exception occurred: `{type(e).__name__}: {e}`\n```\n{stacktrace}\n```"
|
||||||
|
)
|
||||||
|
script.log_info("Database changes have been reverted due to error.")
|
||||||
|
logger.error(f"Exception raised during script execution: {e}")
|
||||||
|
job_result.set_status(JobResultStatusChoices.STATUS_ERRORED)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
job_result.data = ScriptOutputSerializer(script).data
|
||||||
|
job_result.save()
|
||||||
|
|
||||||
|
logger.info(f"Script completed in {job_result.duration}")
|
||||||
|
|
||||||
|
# Params
|
||||||
|
script = options['script']
|
||||||
|
loglevel = options['loglevel']
|
||||||
|
commit = options['commit']
|
||||||
|
try:
|
||||||
|
data = json.loads(options['data'])
|
||||||
|
except TypeError:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
module, name = script.split('.', 1)
|
||||||
|
|
||||||
|
# Take user from command line if provided and exists, other
|
||||||
|
if options['user']:
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=options['user'])
|
||||||
|
except User.DoesNotExist:
|
||||||
|
user = User.objects.filter(is_superuser=True).order_by('pk')[0]
|
||||||
|
else:
|
||||||
|
user = User.objects.filter(is_superuser=True).order_by('pk')[0]
|
||||||
|
|
||||||
|
# Setup logging to Stdout
|
||||||
|
formatter = logging.Formatter(f'[%(asctime)s][%(levelname)s] - %(message)s')
|
||||||
|
stdouthandler = logging.StreamHandler(sys.stdout)
|
||||||
|
stdouthandler.setLevel(logging.DEBUG)
|
||||||
|
stdouthandler.setFormatter(formatter)
|
||||||
|
|
||||||
|
logger = logging.getLogger(f"netbox.scripts.{module}.{name}")
|
||||||
|
logger.addHandler(stdouthandler)
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.setLevel({
|
||||||
|
'critical': logging.CRITICAL,
|
||||||
|
'debug': logging.DEBUG,
|
||||||
|
'error': logging.ERROR,
|
||||||
|
'fatal': logging.FATAL,
|
||||||
|
'info': logging.INFO,
|
||||||
|
'warning': logging.WARNING,
|
||||||
|
}[loglevel])
|
||||||
|
except KeyError:
|
||||||
|
raise CommandError(f"Invalid log level: {loglevel}")
|
||||||
|
|
||||||
|
# Get the script
|
||||||
|
script = get_script(module, name)()
|
||||||
|
# Parse the parameters
|
||||||
|
form = script.as_form(data, None)
|
||||||
|
|
||||||
|
script_content_type = ContentType.objects.get(app_label='extras', model='script')
|
||||||
|
|
||||||
|
# Delete any previous terminal state results
|
||||||
|
JobResult.objects.filter(
|
||||||
|
obj_type=script_content_type,
|
||||||
|
name=script.full_name,
|
||||||
|
status__in=JobResultStatusChoices.TERMINAL_STATE_CHOICES
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
# Create the job result
|
||||||
|
job_result = JobResult.objects.create(
|
||||||
|
name=script.full_name,
|
||||||
|
obj_type=script_content_type,
|
||||||
|
user=User.objects.filter(is_superuser=True).order_by('pk')[0],
|
||||||
|
job_id=uuid.uuid4()
|
||||||
|
)
|
||||||
|
|
||||||
|
request = NetBoxFakeRequest({
|
||||||
|
'META': {},
|
||||||
|
'POST': data,
|
||||||
|
'GET': {},
|
||||||
|
'FILES': {},
|
||||||
|
'user': user,
|
||||||
|
'path': '',
|
||||||
|
'id': job_result.job_id
|
||||||
|
})
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
job_result.status = JobResultStatusChoices.STATUS_RUNNING
|
||||||
|
job_result.save()
|
||||||
|
|
||||||
|
logger.info(f"Running script (commit={commit})")
|
||||||
|
script.request = request
|
||||||
|
|
||||||
|
# Execute the script. If commit is True, wrap it with the change_logging context manager to ensure we process
|
||||||
|
# change logging, webhooks, etc.
|
||||||
|
with change_logging(request):
|
||||||
|
_run_script()
|
||||||
|
else:
|
||||||
|
logger.error('Data is not valid:')
|
||||||
|
for field, errors in form.errors.get_json_data().items():
|
||||||
|
for error in errors:
|
||||||
|
logger.error(f'\t{field}: {error.get("message")}')
|
||||||
|
job_result.status = JobResultStatusChoices.STATUS_ERRORED
|
||||||
|
job_result.save()
|
@ -8,6 +8,7 @@ from django.contrib.postgres.fields import ArrayField
|
|||||||
from django.core.validators import RegexValidator, ValidationError
|
from django.core.validators import RegexValidator, ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
@ -306,7 +307,7 @@ class CustomField(ChangeLoggedModel):
|
|||||||
field.model = self
|
field.model = self
|
||||||
field.label = str(self)
|
field.label = str(self)
|
||||||
if self.description:
|
if self.description:
|
||||||
field.help_text = self.description
|
field.help_text = escape(self.description)
|
||||||
|
|
||||||
return field
|
return field
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
@ -345,9 +344,14 @@ class BaseScript:
|
|||||||
"""
|
"""
|
||||||
Return data from a YAML file
|
Return data from a YAML file
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
|
from yaml import CLoader as Loader
|
||||||
|
except ImportError:
|
||||||
|
from yaml import Loader
|
||||||
|
|
||||||
file_path = os.path.join(settings.SCRIPTS_ROOT, filename)
|
file_path = os.path.join(settings.SCRIPTS_ROOT, filename)
|
||||||
with open(file_path, 'r') as datafile:
|
with open(file_path, 'r') as datafile:
|
||||||
data = yaml.load(datafile)
|
data = yaml.load(datafile, Loader=Loader)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -57,8 +57,8 @@ class CustomFieldTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = CustomField
|
model = CustomField
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'content_types', 'label', 'type', 'required', 'weight', 'default', 'description',
|
'pk', 'id', 'name', 'content_types', 'label', 'type', 'required', 'weight', 'default',
|
||||||
'filter_logic', 'choices',
|
'description', 'filter_logic', 'choices',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'content_types', 'label', 'type', 'required', 'description')
|
default_columns = ('pk', 'name', 'content_types', 'label', 'type', 'required', 'description')
|
||||||
|
|
||||||
@ -78,7 +78,8 @@ class CustomLinkTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = CustomLink
|
model = CustomLink
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'content_type', 'link_text', 'link_url', 'weight', 'group_name', 'button_class', 'new_window',
|
'pk', 'id', 'name', 'content_type', 'link_text', 'link_url', 'weight', 'group_name',
|
||||||
|
'button_class', 'new_window',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'content_type', 'group_name', 'button_class', 'new_window')
|
default_columns = ('pk', 'name', 'content_type', 'group_name', 'button_class', 'new_window')
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ class ExportTemplateTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ExportTemplate
|
model = ExportTemplate
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
'pk', 'id', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
|
||||||
@ -132,7 +133,7 @@ class WebhookTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Webhook
|
model = Webhook
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
|
'pk', 'id', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
|
||||||
'payload_url', 'secret', 'ssl_validation', 'ca_file_path',
|
'payload_url', 'secret', 'ssl_validation', 'ca_file_path',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -155,10 +156,16 @@ class TagTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Tag
|
model = Tag
|
||||||
fields = ('pk', 'name', 'items', 'slug', 'color', 'description', 'actions')
|
fields = ('pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'actions')
|
||||||
|
default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
class TaggedItemTable(BaseTable):
|
class TaggedItemTable(BaseTable):
|
||||||
|
id = tables.Column(
|
||||||
|
verbose_name='ID',
|
||||||
|
linkify=lambda record: record.content_object.get_absolute_url(),
|
||||||
|
accessor='content_object__id'
|
||||||
|
)
|
||||||
content_type = ContentTypeColumn(
|
content_type = ContentTypeColumn(
|
||||||
verbose_name='Type'
|
verbose_name='Type'
|
||||||
)
|
)
|
||||||
@ -170,7 +177,7 @@ class TaggedItemTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = TaggedItem
|
model = TaggedItem
|
||||||
fields = ('content_type', 'content_object')
|
fields = ('id', 'content_type', 'content_object')
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextTable(BaseTable):
|
class ConfigContextTable(BaseTable):
|
||||||
@ -185,8 +192,8 @@ class ConfigContextTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ConfigContext
|
model = ConfigContext
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles', 'platforms',
|
'pk', 'id', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles',
|
||||||
'cluster_groups', 'clusters', 'tenant_groups', 'tenants',
|
'platforms', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'weight', 'is_active', 'description')
|
default_columns = ('pk', 'name', 'weight', 'is_active', 'description')
|
||||||
|
|
||||||
@ -211,7 +218,7 @@ class ObjectChangeTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ObjectChange
|
model = ObjectChange
|
||||||
fields = ('time', 'user_name', 'action', 'changed_object_type', 'object_repr', 'request_id')
|
fields = ('id', 'time', 'user_name', 'action', 'changed_object_type', 'object_repr', 'request_id')
|
||||||
|
|
||||||
|
|
||||||
class ObjectJournalTable(BaseTable):
|
class ObjectJournalTable(BaseTable):
|
||||||
@ -232,7 +239,7 @@ class ObjectJournalTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = JournalEntry
|
model = JournalEntry
|
||||||
fields = ('created', 'created_by', 'kind', 'comments', 'actions')
|
fields = ('id', 'created', 'created_by', 'kind', 'comments', 'actions')
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryTable(ObjectJournalTable):
|
class JournalEntryTable(ObjectJournalTable):
|
||||||
@ -250,5 +257,10 @@ class JournalEntryTable(ObjectJournalTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = JournalEntry
|
model = JournalEntry
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind', 'comments', 'actions'
|
'pk', 'id', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind',
|
||||||
|
'comments', 'actions'
|
||||||
|
)
|
||||||
|
default_columns = (
|
||||||
|
'pk', 'created', 'created_by', 'assigned_object_type', 'assigned_object', 'kind',
|
||||||
|
'comments', 'actions'
|
||||||
)
|
)
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import tempfile
|
||||||
|
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from netaddr import IPAddress, IPNetwork
|
from netaddr import IPAddress, IPNetwork
|
||||||
@ -11,6 +13,50 @@ CHOICES = (
|
|||||||
('0000ff', 'Blue')
|
('0000ff', 'Blue')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
YAML_DATA = """
|
||||||
|
Foo: 123
|
||||||
|
Bar: 456
|
||||||
|
Baz:
|
||||||
|
- A
|
||||||
|
- B
|
||||||
|
- C
|
||||||
|
"""
|
||||||
|
|
||||||
|
JSON_DATA = """
|
||||||
|
{
|
||||||
|
"Foo": 123,
|
||||||
|
"Bar": 456,
|
||||||
|
"Baz": ["A", "B", "C"]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ScriptTest(TestCase):
|
||||||
|
|
||||||
|
def test_load_yaml(self):
|
||||||
|
datafile = tempfile.NamedTemporaryFile()
|
||||||
|
datafile.write(bytes(YAML_DATA, 'UTF-8'))
|
||||||
|
datafile.seek(0)
|
||||||
|
|
||||||
|
data = Script().load_yaml(datafile.name)
|
||||||
|
self.assertEqual(data, {
|
||||||
|
'Foo': 123,
|
||||||
|
'Bar': 456,
|
||||||
|
'Baz': ['A', 'B', 'C'],
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_load_json(self):
|
||||||
|
datafile = tempfile.NamedTemporaryFile()
|
||||||
|
datafile.write(bytes(JSON_DATA, 'UTF-8'))
|
||||||
|
datafile.seek(0)
|
||||||
|
|
||||||
|
data = Script().load_json(datafile.name)
|
||||||
|
self.assertEqual(data, {
|
||||||
|
'Foo': 123,
|
||||||
|
'Bar': 456,
|
||||||
|
'Baz': ['A', 'B', 'C'],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class ScriptVariablesTest(TestCase):
|
class ScriptVariablesTest(TestCase):
|
||||||
|
|
||||||
|
@ -278,11 +278,18 @@ class IPAddressCSVForm(CustomFieldModelCSVForm):
|
|||||||
|
|
||||||
device = self.cleaned_data.get('device')
|
device = self.cleaned_data.get('device')
|
||||||
virtual_machine = self.cleaned_data.get('virtual_machine')
|
virtual_machine = self.cleaned_data.get('virtual_machine')
|
||||||
|
interface = self.cleaned_data.get('interface')
|
||||||
is_primary = self.cleaned_data.get('is_primary')
|
is_primary = self.cleaned_data.get('is_primary')
|
||||||
|
|
||||||
# Validate is_primary
|
# Validate is_primary
|
||||||
if is_primary and not device and not virtual_machine:
|
if is_primary and not device and not virtual_machine:
|
||||||
raise forms.ValidationError("No device or virtual machine specified; cannot set as primary IP")
|
raise forms.ValidationError({
|
||||||
|
"is_primary": "No device or virtual machine specified; cannot set as primary IP"
|
||||||
|
})
|
||||||
|
if is_primary and not interface:
|
||||||
|
raise forms.ValidationError({
|
||||||
|
"is_primary": "No interface specified; cannot set as primary IP"
|
||||||
|
})
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ class RIRTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ('pk', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ class AggregateTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Aggregate
|
model = Aggregate
|
||||||
fields = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags')
|
fields = ('pk', 'id', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags')
|
||||||
default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
|
default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
|
||||||
|
|
||||||
|
|
||||||
@ -177,7 +177,7 @@ class RoleTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Role
|
model = Role
|
||||||
fields = ('pk', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -259,7 +259,7 @@ class PrefixTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Prefix
|
model = Prefix
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role',
|
'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role',
|
||||||
'is_pool', 'mark_utilized', 'description', 'tags',
|
'is_pool', 'mark_utilized', 'description', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -293,12 +293,15 @@ class IPRangeTable(BaseTable):
|
|||||||
accessor='utilization',
|
accessor='utilization',
|
||||||
orderable=False
|
orderable=False
|
||||||
)
|
)
|
||||||
|
tags = TagColumn(
|
||||||
|
url_name='ipam:iprange_list'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPRange
|
model = IPRange
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
|
'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
|
||||||
'utilization',
|
'utilization', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
|
'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
|
||||||
@ -355,7 +358,7 @@ class IPAddressTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name', 'description',
|
'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name', 'description',
|
||||||
'tags',
|
'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -379,6 +382,7 @@ class IPAddressAssignTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'description')
|
fields = ('address', 'dns_name', 'vrf', 'status', 'role', 'tenant', 'assigned_object', 'description')
|
||||||
|
exclude = ('id', )
|
||||||
orderable = False
|
orderable = False
|
||||||
|
|
||||||
|
|
||||||
@ -403,3 +407,4 @@ class AssignedIPAddressesTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = IPAddress
|
model = IPAddress
|
||||||
fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
|
fields = ('address', 'vrf', 'status', 'role', 'tenant', 'description')
|
||||||
|
exclude = ('id', )
|
||||||
|
@ -31,5 +31,5 @@ class ServiceTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Service
|
model = Service
|
||||||
fields = ('pk', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags')
|
fields = ('pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags')
|
||||||
default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')
|
default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')
|
||||||
|
@ -84,7 +84,7 @@ class VLANGroupTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VLANGroup
|
model = VLANGroup
|
||||||
fields = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'scope_type', 'scope', 'vlan_count', 'slug', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ class VLANTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
|
fields = ('pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
|
||||||
default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description')
|
default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description')
|
||||||
row_attrs = {
|
row_attrs = {
|
||||||
'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
|
'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
|
||||||
@ -152,6 +152,7 @@ class VLANDevicesTable(VLANMembersTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Interface
|
model = Interface
|
||||||
fields = ('device', 'name', 'tagged', 'actions')
|
fields = ('device', 'name', 'tagged', 'actions')
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
|
||||||
class VLANVirtualMachinesTable(VLANMembersTable):
|
class VLANVirtualMachinesTable(VLANMembersTable):
|
||||||
@ -163,6 +164,7 @@ class VLANVirtualMachinesTable(VLANMembersTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
fields = ('virtual_machine', 'name', 'tagged', 'actions')
|
fields = ('virtual_machine', 'name', 'tagged', 'actions')
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
|
|
||||||
class InterfaceVLANTable(BaseTable):
|
class InterfaceVLANTable(BaseTable):
|
||||||
@ -190,6 +192,7 @@ class InterfaceVLANTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VLAN
|
model = VLAN
|
||||||
fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
|
fields = ('vid', 'tagged', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
|
||||||
|
exclude = ('id', )
|
||||||
|
|
||||||
def __init__(self, interface, *args, **kwargs):
|
def __init__(self, interface, *args, **kwargs):
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
|
@ -47,7 +47,7 @@ class VRFTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VRF
|
model = VRF
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags',
|
'pk', 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
|
default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
|
||||||
|
|
||||||
@ -68,5 +68,5 @@ class RouteTargetTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = RouteTarget
|
model = RouteTarget
|
||||||
fields = ('pk', 'name', 'tenant', 'description', 'tags')
|
fields = ('pk', 'id', 'name', 'tenant', 'description', 'tags')
|
||||||
default_columns = ('pk', 'name', 'tenant', 'description')
|
default_columns = ('pk', 'name', 'tenant', 'description')
|
||||||
|
@ -69,7 +69,13 @@ SEARCH_TYPES = OrderedDict((
|
|||||||
}),
|
}),
|
||||||
('location', {
|
('location', {
|
||||||
'queryset': Location.objects.add_related_count(
|
'queryset': Location.objects.add_related_count(
|
||||||
Location.objects.all(),
|
Location.objects.add_related_count(
|
||||||
|
Location.objects.all(),
|
||||||
|
Device,
|
||||||
|
'location',
|
||||||
|
'device_count',
|
||||||
|
cumulative=True
|
||||||
|
),
|
||||||
Rack,
|
Rack,
|
||||||
'location',
|
'location',
|
||||||
'rack_count',
|
'rack_count',
|
||||||
|
@ -40,11 +40,6 @@ class ChangeLoggingMixin(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
object_changes = GenericRelation(
|
|
||||||
to='extras.ObjectChange',
|
|
||||||
content_type_field='changed_object_type',
|
|
||||||
object_id_field='changed_object_id'
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -777,8 +777,21 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|||||||
else:
|
else:
|
||||||
pk_list = request.POST.getlist('pk')
|
pk_list = request.POST.getlist('pk')
|
||||||
|
|
||||||
|
# Include the PK list as initial data for the form
|
||||||
|
initial_data = {'pk': pk_list}
|
||||||
|
|
||||||
|
# Check for other contextual data needed for the form. We avoid passing all of request.GET because the
|
||||||
|
# filter values will conflict with the bulk edit form fields.
|
||||||
|
# TODO: Find a better way to accomplish this
|
||||||
|
if 'device' in request.GET:
|
||||||
|
initial_data['device'] = request.GET.get('device')
|
||||||
|
elif 'device_type' in request.GET:
|
||||||
|
initial_data['device_type'] = request.GET.get('device_type')
|
||||||
|
elif 'virtual_machine' in request.GET:
|
||||||
|
initial_data['virtual_machine'] = request.GET.get('virtual_machine')
|
||||||
|
|
||||||
if '_apply' in request.POST:
|
if '_apply' in request.POST:
|
||||||
form = self.form(model, request.POST)
|
form = self.form(model, request.POST, initial=initial_data)
|
||||||
restrict_form_fields(form, request.user)
|
restrict_form_fields(form, request.user)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@ -867,18 +880,6 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
|
|||||||
logger.debug("Form validation failed")
|
logger.debug("Form validation failed")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Include the PK list as initial data for the form
|
|
||||||
initial_data = {'pk': pk_list}
|
|
||||||
|
|
||||||
# Check for other contextual data needed for the form. We avoid passing all of request.GET because the
|
|
||||||
# filter values will conflict with the bulk edit form fields.
|
|
||||||
# TODO: Find a better way to accomplish this
|
|
||||||
if 'device' in request.GET:
|
|
||||||
initial_data['device'] = request.GET.get('device')
|
|
||||||
elif 'device_type' in request.GET:
|
|
||||||
initial_data['device_type'] = request.GET.get('device_type')
|
|
||||||
elif 'virtual_machine' in request.GET:
|
|
||||||
initial_data['virtual_machine'] = request.GET.get('virtual_machine')
|
|
||||||
|
|
||||||
form = self.form(model, initial=initial_data)
|
form = self.form(model, initial=initial_data)
|
||||||
restrict_form_fields(form, request.user)
|
restrict_form_fields(form, request.user)
|
||||||
|
@ -27,55 +27,78 @@
|
|||||||
<title>{% block title %}Home{% endblock %} | NetBox</title>
|
<title>{% block title %}Home{% endblock %} | NetBox</title>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
/**
|
/**
|
||||||
* Set the color mode on the `<html/>` element and in local storage.
|
* Set the color mode on the `<html/>` element and in local storage.
|
||||||
*/
|
*
|
||||||
function setMode(mode) {
|
* @param mode {"dark" | "light"} NetBox Color Mode.
|
||||||
document.documentElement.setAttribute("data-netbox-color-mode", mode);
|
* @param inferred {boolean} Value is inferred from browser/system preference.
|
||||||
localStorage.setItem("netbox-color-mode", mode);
|
*/
|
||||||
}
|
function setMode(mode, inferred) {
|
||||||
/**
|
document.documentElement.setAttribute("data-netbox-color-mode", mode);
|
||||||
* Determine the best initial color mode to use prior to rendering.
|
localStorage.setItem("netbox-color-mode", mode);
|
||||||
*/
|
localStorage.setItem("netbox-color-mode-inferred", inferred);
|
||||||
(function () {
|
}
|
||||||
try {
|
/**
|
||||||
// Browser prefers dark color scheme.
|
* Determine the best initial color mode to use prior to rendering.
|
||||||
var preferDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
*/
|
||||||
// Browser prefers light color scheme.
|
(function () {
|
||||||
var preferLight = window.matchMedia("(prefers-color-scheme: light)").matches;
|
try {
|
||||||
// Client NetBox color-mode override.
|
// Browser prefers dark color scheme.
|
||||||
var clientMode = localStorage.getItem("netbox-color-mode");
|
var preferDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
// NetBox server-rendered value.
|
// Browser prefers light color scheme.
|
||||||
var serverMode = document.documentElement.getAttribute("data-netbox-color-mode");
|
var preferLight = window.matchMedia("(prefers-color-scheme: light)").matches;
|
||||||
|
// Client NetBox color-mode override.
|
||||||
if (clientMode === null && (serverMode === "light" || serverMode === "dark")) {
|
var clientMode = localStorage.getItem("netbox-color-mode");
|
||||||
// If the client mode is not set but the server mode is, use the server mode.
|
// NetBox server-rendered value.
|
||||||
return setMode(serverMode);
|
var serverMode = document.documentElement.getAttribute("data-netbox-color-mode");
|
||||||
}
|
// Color mode is inferred from browser/system preference and not deterministically set by
|
||||||
if (clientMode !== null && clientMode !== serverMode) {
|
// the client or server.
|
||||||
// If the client mode is set and is different than the server mode, use the client mode
|
var inferred = JSON.parse(localStorage.getItem("netbox-color-mode-inferred"));
|
||||||
// over the server mode, as it should be more recent.
|
|
||||||
return setMode(clientMode);
|
if (inferred === true && (serverMode === "light" || serverMode === "dark")) {
|
||||||
}
|
// The color mode was previously inferred from browser/system preference, but
|
||||||
if (clientMode === serverMode) {
|
// the server now has a value, so we should use the server's value.
|
||||||
// If the client and server modes match, use that value.
|
return setMode(serverMode, false);
|
||||||
return setMode(clientMode);
|
}
|
||||||
}
|
if (clientMode === null && (serverMode === "light" || serverMode === "dark")) {
|
||||||
if (preferDark && serverMode === "unset") {
|
// If the client mode is not set but the server mode is, use the server mode.
|
||||||
// If the server mode is not set but the browser prefers dark mode, use dark mode.
|
return setMode(serverMode, false);
|
||||||
return setMode("dark");
|
}
|
||||||
}
|
if (clientMode !== null && serverMode === "unset") {
|
||||||
if (preferLight && serverMode === "unset") {
|
// The color mode has been set, deterministically or otherwise, and the server
|
||||||
// If the server mode is not set but the browser prefers light mode, use light mode.
|
// has no preference or has not been set. Use the client mode, but allow it to
|
||||||
return setMode("light");
|
/// be overridden by the server if/when a server value exists.
|
||||||
}
|
return setMode(clientMode, true);
|
||||||
} catch (error) {
|
}
|
||||||
// In the event of an error, log it to the console and set the mode to light mode.
|
if (
|
||||||
console.error(error);
|
clientMode !== null &&
|
||||||
}
|
(serverMode === "light" || serverMode === "dark") &&
|
||||||
return setMode("light");
|
clientMode !== serverMode
|
||||||
})();
|
) {
|
||||||
|
// If the client mode is set and is different than the server mode (which is also set),
|
||||||
|
// use the client mode over the server mode, as it should be more recent.
|
||||||
|
return setMode(clientMode, false);
|
||||||
|
}
|
||||||
|
if (clientMode === serverMode) {
|
||||||
|
// If the client and server modes match, use that value.
|
||||||
|
return setMode(clientMode, false);
|
||||||
|
}
|
||||||
|
if (preferDark && serverMode === "unset") {
|
||||||
|
// If the server mode is not set but the browser prefers dark mode, use dark mode, but
|
||||||
|
// allow it to be overridden by an explicit preference.
|
||||||
|
return setMode("dark", true);
|
||||||
|
}
|
||||||
|
if (preferLight && serverMode === "unset") {
|
||||||
|
// If the server mode is not set but the browser prefers light mode, use light mode,
|
||||||
|
// but allow it to be overridden by an explicit preference.
|
||||||
|
return setMode("light", true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// In the event of an error, log it to the console and set the mode to light mode.
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
return setMode("light", true);
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{# Static resources #}
|
{# Static resources #}
|
||||||
|
@ -56,6 +56,13 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Racks</th>
|
<th scope="row">Racks</th>
|
||||||
<td>
|
<td>
|
||||||
|
{% if rack_count %}
|
||||||
|
<div class="float-end noprint">
|
||||||
|
<a href="{% url 'dcim:rack_elevation_list' %}?location_id={{ object.pk }}" class="btn btn-sm btn-primary" title="View elevations">
|
||||||
|
<i class="mdi mdi-server"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<a href="{% url 'dcim:rack_list' %}?location_id={{ object.pk }}">{{ rack_count }}</a>
|
<a href="{% url 'dcim:rack_list' %}?location_id={{ object.pk }}">{{ rack_count }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
{% for field, value in custom_fields.items %}
|
{% for field, value in custom_fields.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span title="{{ field.description }}">{{ field }}</span></td>
|
<td><span title="{{ field.description|escape }}">{{ field }}</span></td>
|
||||||
<td>
|
<td>
|
||||||
{% if field.type == 'longtext' and value %}
|
{% if field.type == 'longtext' and value %}
|
||||||
{{ value|render_markdown }}
|
{{ value|render_markdown }}
|
||||||
|
@ -62,7 +62,7 @@ class TenantGroupTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = TenantGroup
|
model = TenantGroup
|
||||||
fields = ('pk', 'name', 'tenant_count', 'description', 'slug', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'tenant_count', 'description', 'slug', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'tenant_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'tenant_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ class TenantTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Tenant
|
model = Tenant
|
||||||
fields = ('pk', 'name', 'slug', 'group', 'description', 'comments', 'tags')
|
fields = ('pk', 'id', 'name', 'slug', 'group', 'description', 'comments', 'tags')
|
||||||
default_columns = ('pk', 'name', 'group', 'description')
|
default_columns = ('pk', 'name', 'group', 'description')
|
||||||
|
|
||||||
|
|
||||||
|
@ -68,17 +68,22 @@ def get_paginate_count(request):
|
|||||||
"""
|
"""
|
||||||
config = get_config()
|
config = get_config()
|
||||||
|
|
||||||
|
def _max_allowed(page_size):
|
||||||
|
if config.MAX_PAGE_SIZE:
|
||||||
|
return min(page_size, config.MAX_PAGE_SIZE)
|
||||||
|
return page_size
|
||||||
|
|
||||||
if 'per_page' in request.GET:
|
if 'per_page' in request.GET:
|
||||||
try:
|
try:
|
||||||
per_page = int(request.GET.get('per_page'))
|
per_page = int(request.GET.get('per_page'))
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
request.user.config.set('pagination.per_page', per_page, commit=True)
|
request.user.config.set('pagination.per_page', per_page, commit=True)
|
||||||
return min(per_page, config.MAX_PAGE_SIZE)
|
return _max_allowed(per_page)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
per_page = request.user.config.get('pagination.per_page', config.PAGINATE_COUNT)
|
per_page = request.user.config.get('pagination.per_page', config.PAGINATE_COUNT)
|
||||||
return min(per_page, config.MAX_PAGE_SIZE)
|
return _max_allowed(per_page)
|
||||||
|
|
||||||
return min(config.PAGINATE_COUNT, config.MAX_PAGE_SIZE)
|
return _max_allowed(config.PAGINATE_COUNT)
|
||||||
|
@ -23,6 +23,10 @@ class BaseTable(tables.Table):
|
|||||||
|
|
||||||
:param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
|
:param user: Personalize table display for the given user (optional). Has no effect if AnonymousUser is passed.
|
||||||
"""
|
"""
|
||||||
|
id = tables.Column(
|
||||||
|
linkify=True,
|
||||||
|
verbose_name='ID'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
attrs = {
|
attrs = {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from django.conf import settings
|
|
||||||
from dcim.tables.devices import BaseInterfaceTable
|
from dcim.tables.devices import BaseInterfaceTable
|
||||||
from tenancy.tables import TenantColumn
|
from tenancy.tables import TenantColumn
|
||||||
from utilities.tables import (
|
from utilities.tables import (
|
||||||
@ -45,7 +44,7 @@ class ClusterTypeTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ClusterType
|
model = ClusterType
|
||||||
fields = ('pk', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -68,7 +67,7 @@ class ClusterGroupTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = ClusterGroup
|
model = ClusterGroup
|
||||||
fields = ('pk', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
|
||||||
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
|
default_columns = ('pk', 'name', 'cluster_count', 'description', 'actions')
|
||||||
|
|
||||||
|
|
||||||
@ -104,7 +103,7 @@ class ClusterTable(BaseTable):
|
|||||||
|
|
||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = Cluster
|
model = Cluster
|
||||||
fields = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'device_count', 'vm_count', 'tags')
|
fields = ('pk', 'id', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'device_count', 'vm_count', 'tags')
|
||||||
default_columns = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count')
|
default_columns = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count')
|
||||||
|
|
||||||
|
|
||||||
@ -144,7 +143,7 @@ class VirtualMachineTable(BaseTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VirtualMachine
|
model = VirtualMachine
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4',
|
'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4',
|
||||||
'primary_ip6', 'primary_ip', 'comments', 'tags',
|
'primary_ip6', 'primary_ip', 'comments', 'tags',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
@ -171,7 +170,7 @@ class VMInterfaceTable(BaseInterfaceTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
'pk', 'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
||||||
'ip_addresses', 'untagged_vlan', 'tagged_vlans',
|
'ip_addresses', 'untagged_vlan', 'tagged_vlans',
|
||||||
)
|
)
|
||||||
default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')
|
default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')
|
||||||
@ -193,7 +192,7 @@ class VirtualMachineVMInterfaceTable(VMInterfaceTable):
|
|||||||
class Meta(BaseTable.Meta):
|
class Meta(BaseTable.Meta):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'name', 'enabled', 'parent', 'bridge', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
'pk', 'id', 'name', 'enabled', 'parent', 'bridge', 'mac_address', 'mtu', 'mode', 'description', 'tags',
|
||||||
'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
|
'ip_addresses', 'untagged_vlan', 'tagged_vlans', 'actions',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Django==3.2.8
|
Django==3.2.9
|
||||||
django-cors-headers==3.10.0
|
django-cors-headers==3.10.0
|
||||||
django-debug-toolbar==3.2.2
|
django-debug-toolbar==3.2.2
|
||||||
django-filter==21.1
|
django-filter==21.1
|
||||||
@ -18,7 +18,7 @@ gunicorn==20.1.0
|
|||||||
Jinja2==3.0.2
|
Jinja2==3.0.2
|
||||||
Markdown==3.3.4
|
Markdown==3.3.4
|
||||||
markdown-include==0.6.0
|
markdown-include==0.6.0
|
||||||
mkdocs-material==7.3.4
|
mkdocs-material==7.3.6
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==8.4.0
|
Pillow==8.4.0
|
||||||
psycopg2-binary==2.9.1
|
psycopg2-binary==2.9.1
|
||||||
@ -26,7 +26,7 @@ PyYAML==6.0
|
|||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
social-auth-core==4.1.0
|
social-auth-core==4.1.0
|
||||||
svgwrite==1.4.1
|
svgwrite==1.4.1
|
||||||
tablib==3.0.0
|
tablib==3.1.0
|
||||||
|
|
||||||
# Workaround for #7401
|
# Workaround for #7401
|
||||||
jsonschema==3.2.0
|
jsonschema==3.2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user