mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 12:12:53 -06:00
Merge branch 'feature' into feature-2434
This commit is contained in:
commit
515aed7022
39
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
39
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -5,21 +5,25 @@ labels: ["type: bug"]
|
|||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: "**NOTE:** This form is only for reporting _reproducible bugs_ in a
|
value: >
|
||||||
current NetBox installation. If you're having trouble with installation or just
|
**NOTE:** This form is only for reporting _reproducible bugs_ in a current NetBox
|
||||||
looking for assistance with using NetBox, please visit our
|
installation. If you're having trouble with installation or just looking for
|
||||||
[discussion forum](https://github.com/netbox-community/netbox/discussions) instead."
|
assistance with using NetBox, please visit our
|
||||||
|
[discussion forum](https://github.com/netbox-community/netbox/discussions) instead.
|
||||||
- type: input
|
- type: input
|
||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: "What version of NetBox are you currently running?"
|
description: >
|
||||||
placeholder: v2.10.4
|
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/)
|
||||||
|
before opening a bug report to see if your issue has already been addressed.)
|
||||||
|
placeholder: v2.11.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: Python version
|
label: Python version
|
||||||
description: "What version of Python are you currently running?"
|
description: What version of Python are you currently running?
|
||||||
options:
|
options:
|
||||||
- 3.6
|
- 3.6
|
||||||
- 3.7
|
- 3.7
|
||||||
@ -30,12 +34,13 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Steps to Reproduce
|
label: Steps to Reproduce
|
||||||
description: "Describe in detail the exact steps that someone else can take to
|
description: >
|
||||||
reproduce this bug using the current stable release of NetBox. Begin with the
|
Describe in detail the exact steps that someone else can take to
|
||||||
creation of any necessary database objects and call out every operation being
|
reproduce this bug using the current stable release of NetBox. Begin with the
|
||||||
performed explicitly. If reporting a bug in the REST API, be sure to reconstruct
|
creation of any necessary database objects and call out every operation being
|
||||||
the raw HTTP request(s) being made: Don't rely on a client library such as
|
performed explicitly. If reporting a bug in the REST API, be sure to reconstruct
|
||||||
pynetbox."
|
the raw HTTP request(s) being made: Don't rely on a client library such as
|
||||||
|
pynetbox."
|
||||||
placeholder: |
|
placeholder: |
|
||||||
1. Click on "create widget"
|
1. Click on "create widget"
|
||||||
2. Set foo to 12 and bar to G
|
2. Set foo to 12 and bar to G
|
||||||
@ -45,14 +50,14 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Expected Behavior
|
label: Expected Behavior
|
||||||
description: "What did you expect to happen?"
|
description: What did you expect to happen?
|
||||||
placeholder: "A new widget should have been created with the specified attributes"
|
placeholder: A new widget should have been created with the specified attributes
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Observed Behavior
|
label: Observed Behavior
|
||||||
description: "What happened instead?"
|
description: What happened instead?
|
||||||
placeholder: "A TypeError exception was raised"
|
placeholder: A TypeError exception was raised
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
@ -30,6 +30,6 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Proposed Changes
|
label: Proposed Changes
|
||||||
description: "Describe the proposed changes and why they are necessary"
|
description: Describe the proposed changes and why they are necessary.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
36
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -5,15 +5,16 @@ labels: ["type: feature"]
|
|||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: "**NOTE:** This form is only for submitting well-formed proposals to extend or
|
value: >
|
||||||
modify NetBox in some way. If you're trying to solve a problem but can't figure out how,
|
**NOTE:** This form is only for submitting well-formed proposals to extend or modify
|
||||||
or if you still need time to work on the details of a proposed new feature, please start
|
NetBox in some way. If you're trying to solve a problem but can't figure out how, or if
|
||||||
a [discussion](https://github.com/netbox-community/netbox/discussions) instead."
|
you still need time to work on the details of a proposed new feature, please start a
|
||||||
|
[discussion](https://github.com/netbox-community/netbox/discussions) instead.
|
||||||
- type: input
|
- type: input
|
||||||
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: v2.10.4
|
placeholder: v2.11.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
@ -28,26 +29,29 @@ body:
|
|||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Proposed functionality
|
label: Proposed functionality
|
||||||
description: "Describe in detail the new feature or behavior you'd like to propose.
|
description: >
|
||||||
Include any specific changes to work flows, data models, or the user interface."
|
Describe in detail the new feature or behavior you'd like to propose. Include any specific
|
||||||
|
changes to work flows, data models, or the user interface.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Use case
|
label: Use case
|
||||||
description: "Explain how adding this functionality would benefit NetBox users. What
|
description: >
|
||||||
need does it address?"
|
Explain how adding this functionality would benefit NetBox users. What need does it address?
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Database changes
|
label: Database changes
|
||||||
description: "Note any changes to the database schema necessary to support the new
|
description: >
|
||||||
feature. For example, does the proposal require adding a new model or field? (Not
|
Note any changes to the database schema necessary to support the new feature. For example,
|
||||||
all new features require database changes.)"
|
does the proposal require adding a new model or field? (Not all new features require database
|
||||||
|
changes.)
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: External dependencies
|
label: External dependencies
|
||||||
description: "List any new dependencies on external libraries or services that this
|
description: >
|
||||||
new feature would introduce. For example, does the proposal require the installation
|
List any new dependencies on external libraries or services that this new feature would
|
||||||
of a new Python package? (Not all new features introduce new dependencies.)"
|
introduce. For example, does the proposal require the installation of a new Python package?
|
||||||
|
(Not all new features introduce new dependencies.)
|
||||||
|
12
.github/ISSUE_TEMPLATE/housekeeping.yaml
vendored
12
.github/ISSUE_TEMPLATE/housekeeping.yaml
vendored
@ -5,18 +5,20 @@ labels: ["type: housekeeping"]
|
|||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: "**NOTE:** This template is for use by maintainers only. Please do not submit
|
value: >
|
||||||
an issue using this template unless you have been specifically asked to do so."
|
**NOTE:** This template is for use by maintainers only. Please do not submit
|
||||||
|
an issue using this template unless you have been specifically asked to do so.
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Proposed Changes
|
label: Proposed Changes
|
||||||
description: "Describe in detail the new feature or behavior you'd like to propose.
|
description: >
|
||||||
Include any specific changes to work flows, data models, or the user interface."
|
Describe in detail the new feature or behavior you'd like to propose.
|
||||||
|
Include any specific changes to work flows, data models, or the user interface.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Justification
|
label: Justification
|
||||||
description: "Please provide justification for the proposed change(s)."
|
description: Please provide justification for the proposed change(s).
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
5
.github/workflows/stale.yml
vendored
5
.github/workflows/stale.yml
vendored
@ -17,9 +17,10 @@ jobs:
|
|||||||
necessary.
|
necessary.
|
||||||
close-pr-message: >
|
close-pr-message: >
|
||||||
This PR has been automatically closed due to lack of activity.
|
This PR has been automatically closed due to lack of activity.
|
||||||
days-before-stale: 45
|
days-before-stale: 60
|
||||||
days-before-close: 15
|
days-before-close: 30
|
||||||
exempt-issue-labels: 'status: accepted,status: blocked,status: needs milestone'
|
exempt-issue-labels: 'status: accepted,status: blocked,status: needs milestone'
|
||||||
|
operations-per-run: 100
|
||||||
remove-stale-when-updated: false
|
remove-stale-when-updated: false
|
||||||
stale-issue-label: 'pending closure'
|
stale-issue-label: 'pending closure'
|
||||||
stale-issue-message: >
|
stale-issue-message: >
|
||||||
|
59
README.md
59
README.md
@ -1,4 +1,6 @@
|
|||||||

|
<div align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo.svg" width="400" alt="NetBox logo" />
|
||||||
|
</div>
|
||||||
|
|
||||||
NetBox is an IP address management (IPAM) and data center infrastructure
|
NetBox is an IP address management (IPAM) and data center infrastructure
|
||||||
management (DCIM) tool. Initially conceived by the network engineering team at
|
management (DCIM) tool. Initially conceived by the network engineering team at
|
||||||
@ -10,7 +12,21 @@ NetBox runs as a web application atop the [Django](https://www.djangoproject.com
|
|||||||
Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a
|
Python framework with a [PostgreSQL](https://www.postgresql.org/) database. For a
|
||||||
complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/netbox-community/netbox).
|
complete list of requirements, see `requirements.txt`. The code is available [on GitHub](https://github.com/netbox-community/netbox).
|
||||||
|
|
||||||
The complete documentation for NetBox can be found at [Read the Docs](https://netbox.readthedocs.io/en/stable/).
|
The complete documentation for NetBox can be found at [Read the Docs](https://netbox.readthedocs.io/en/stable/). A public demo instance is available at https://demo.netbox.dev.
|
||||||
|
|
||||||
|
| | status |
|
||||||
|
|-------------|------------|
|
||||||
|
| **master** |  |
|
||||||
|
| **develop** |  |
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<h4>Thank you to our sponsors!</h4>
|
||||||
|
|
||||||
|
[](https://ns1.com/)
|
||||||
|
|
||||||
|
[](https://stellar.tech/)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
### Discussion
|
### Discussion
|
||||||
|
|
||||||
@ -18,37 +34,14 @@ The complete documentation for NetBox can be found at [Read the Docs](https://ne
|
|||||||
* [Slack](https://slack.netbox.dev/) - Real-time chat hosted by the NetDev Community; best for unstructured discussion or just hanging out
|
* [Slack](https://slack.netbox.dev/) - Real-time chat hosted by the NetDev Community; best for unstructured discussion or just hanging out
|
||||||
* [Google Group](https://groups.google.com/g/netbox-discuss) - Legacy mailing list; slowly being replaced by GitHub discussions
|
* [Google Group](https://groups.google.com/g/netbox-discuss) - Legacy mailing list; slowly being replaced by GitHub discussions
|
||||||
|
|
||||||
### Build Status
|
### Installation
|
||||||
|
|
||||||
| | status |
|
|
||||||
| ----------- | ------------------------------------------------------------------------------------------------- |
|
|
||||||
| **master** |  |
|
|
||||||
| **develop** |  |
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Please see [the documentation](https://netbox.readthedocs.io/en/stable/) for
|
Please see [the documentation](https://netbox.readthedocs.io/en/stable/) for
|
||||||
instructions on installing NetBox. To upgrade NetBox, please download the
|
instructions on installing NetBox. To upgrade NetBox, please download the
|
||||||
[latest release](https://github.com/netbox-community/netbox/releases) and
|
[latest release](https://github.com/netbox-community/netbox/releases) and
|
||||||
run `upgrade.sh`.
|
run `upgrade.sh`.
|
||||||
|
|
||||||
## Providing Feedback
|
### Providing Feedback
|
||||||
|
|
||||||
The best platform for general feedback, assistance, and other discussion is our
|
The best platform for general feedback, assistance, and other discussion is our
|
||||||
[GitHub discussions](https://github.com/netbox-community/netbox/discussions).
|
[GitHub discussions](https://github.com/netbox-community/netbox/discussions).
|
||||||
@ -58,7 +51,17 @@ the [appropriate template](https://github.com/netbox-community/netbox/issues/new
|
|||||||
If you are interested in contributing to the development of NetBox, please read
|
If you are interested in contributing to the development of NetBox, please read
|
||||||
our [contributing guide](CONTRIBUTING.md) prior to beginning any work.
|
our [contributing guide](CONTRIBUTING.md) prior to beginning any work.
|
||||||
|
|
||||||
## Related projects
|
### Screenshots
|
||||||
|
|
||||||
|

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

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

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

|
||||||
|
|
||||||
|
### Related projects
|
||||||
|
|
||||||
Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions)
|
Please see [our wiki](https://github.com/netbox-community/netbox/wiki/Community-Contributions)
|
||||||
for a list of relevant community projects.
|
for a list of relevant community projects.
|
||||||
|
@ -515,6 +515,14 @@ The file path to the location where custom scripts will be kept. By default, thi
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## SESSION_COOKIE_NAME
|
||||||
|
|
||||||
|
Default: `sessionid`
|
||||||
|
|
||||||
|
The name used for the session cookie. See the [Django documentation](https://docs.djangoproject.com/en/stable/ref/settings/#session-cookie-name) for more detail.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## SESSION_FILE_PATH
|
## SESSION_FILE_PATH
|
||||||
|
|
||||||
Default: None
|
Default: None
|
||||||
|
@ -70,7 +70,11 @@ Ensure that continuous integration testing on the `develop` branch is completing
|
|||||||
|
|
||||||
### Update Version and Changelog
|
### Update Version and Changelog
|
||||||
|
|
||||||
Update the `VERSION` constant in `settings.py` to the new release version and annotate the current data in the release notes for the new version. Commit these changes to the `develop` branch.
|
* Update the `VERSION` constant in `settings.py` to the new release version.
|
||||||
|
* Update the example version numbers in the feature request and bug report templates under `.github/ISSUE_TEMPLATES/`.
|
||||||
|
* Replace the "FUTURE" placeholder in the release notes with the current date.
|
||||||
|
|
||||||
|
Commit these changes to the `develop` branch.
|
||||||
|
|
||||||
### Submit a Pull Request
|
### Submit a Pull Request
|
||||||
|
|
||||||
|
@ -1,5 +1,32 @@
|
|||||||
# NetBox v2.11
|
# NetBox v2.11
|
||||||
|
|
||||||
|
## v2.11.3 (2021-05-07)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#6197](https://github.com/netbox-community/netbox/issues/6197) - Introduced `SESSION_COOKIE_NAME` config parameter
|
||||||
|
* [#6318](https://github.com/netbox-community/netbox/issues/6318) - Add OM5 MMF cable type
|
||||||
|
* [#6351](https://github.com/netbox-community/netbox/issues/6351) - Add aggregates count to tenant view
|
||||||
|
* [#6359](https://github.com/netbox-community/netbox/issues/6359) - Enable custom links for organizational and nested group models
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#6240](https://github.com/netbox-community/netbox/issues/6240) - Fix display of available VLAN ranges under VLAN group view
|
||||||
|
* [#6308](https://github.com/netbox-community/netbox/issues/6308) - Fix linking of available VLANs in VLAN group view
|
||||||
|
* [#6309](https://github.com/netbox-community/netbox/issues/6309) - Restrict parent VM interface assignment to the parent VM
|
||||||
|
* [#6312](https://github.com/netbox-community/netbox/issues/6312) - Interface device filter should return all virtual chassis interfaces only if device is master
|
||||||
|
* [#6313](https://github.com/netbox-community/netbox/issues/6313) - Fix device type instance count under manufacturer view
|
||||||
|
* [#6321](https://github.com/netbox-community/netbox/issues/6321) - Restore "add an IP" button under prefix IPs view
|
||||||
|
* [#6333](https://github.com/netbox-community/netbox/issues/6333) - Fix filtering of circuit terminations by primary key
|
||||||
|
* [#6339](https://github.com/netbox-community/netbox/issues/6339) - Improve ordering of interfaces when viewing virtual chassis master
|
||||||
|
* [#6350](https://github.com/netbox-community/netbox/issues/6350) - Include first & last IP addresses when allocating available IPv6 addresses via the REST API
|
||||||
|
* [#6355](https://github.com/netbox-community/netbox/issues/6355) - Fix caching error when swapping A/Z circuit terminations
|
||||||
|
* [#6357](https://github.com/netbox-community/netbox/issues/6357) - Fix ProviderNetwork nested API serializer
|
||||||
|
* [#6363](https://github.com/netbox-community/netbox/issues/6363) - Correct pre-population of cluster group when creating a cluster
|
||||||
|
* [#6369](https://github.com/netbox-community/netbox/issues/6369) - Fix interface assignment for VLANs in non-scoped groups
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v2.11.2 (2021-04-27)
|
## v2.11.2 (2021-04-27)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
@ -6,3 +6,18 @@
|
|||||||
|
|
||||||
* [#5532](https://github.com/netbox-community/netbox/issues/5532) - Drop support for Python 3.6
|
* [#5532](https://github.com/netbox-community/netbox/issues/5532) - Drop support for Python 3.6
|
||||||
* [#5994](https://github.com/netbox-community/netbox/issues/5994) - Drop support for `display_field` argument on ObjectVar
|
* [#5994](https://github.com/netbox-community/netbox/issues/5994) - Drop support for `display_field` argument on ObjectVar
|
||||||
|
|
||||||
|
### REST API Changes
|
||||||
|
|
||||||
|
* dcim.Device
|
||||||
|
* Removed the `display_name` attribute (use `display` instead)
|
||||||
|
* dcim.DeviceType
|
||||||
|
* Removed the `display_name` attribute (use `display` instead)
|
||||||
|
* dcim.Rack
|
||||||
|
* Removed the `display_name` attribute (use `display` instead)
|
||||||
|
* extras.ContentType
|
||||||
|
* Removed the `display_name` attribute (use `display` instead)
|
||||||
|
* ipam.VLAN
|
||||||
|
* Removed the `display_name` attribute (use `display` instead)
|
||||||
|
* ipam.VRF
|
||||||
|
* Removed the `display_name` attribute (use `display` instead)
|
||||||
|
@ -20,7 +20,7 @@ class NestedProviderNetworkSerializer(WritableNestedSerializer):
|
|||||||
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:providernetwork-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:providernetwork-detail')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Provider
|
model = ProviderNetwork
|
||||||
fields = ['id', 'url', 'display', 'name']
|
fields = ['id', 'url', 'display', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from circuits import filters
|
from circuits import filtersets
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.api.views import PassThroughPortMixin
|
from dcim.api.views import PassThroughPortMixin
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
@ -26,7 +26,7 @@ class ProviderViewSet(CustomFieldModelViewSet):
|
|||||||
circuit_count=count_related(Circuit, 'provider')
|
circuit_count=count_related(Circuit, 'provider')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.ProviderSerializer
|
serializer_class = serializers.ProviderSerializer
|
||||||
filterset_class = filters.ProviderFilterSet
|
filterset_class = filtersets.ProviderFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -38,7 +38,7 @@ class CircuitTypeViewSet(CustomFieldModelViewSet):
|
|||||||
circuit_count=count_related(Circuit, 'type')
|
circuit_count=count_related(Circuit, 'type')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.CircuitTypeSerializer
|
serializer_class = serializers.CircuitTypeSerializer
|
||||||
filterset_class = filters.CircuitTypeFilterSet
|
filterset_class = filtersets.CircuitTypeFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -50,7 +50,7 @@ class CircuitViewSet(CustomFieldModelViewSet):
|
|||||||
'type', 'tenant', 'provider', 'termination_a', 'termination_z'
|
'type', 'tenant', 'provider', 'termination_a', 'termination_z'
|
||||||
).prefetch_related('tags')
|
).prefetch_related('tags')
|
||||||
serializer_class = serializers.CircuitSerializer
|
serializer_class = serializers.CircuitSerializer
|
||||||
filterset_class = filters.CircuitFilterSet
|
filterset_class = filtersets.CircuitFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -62,7 +62,7 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet):
|
|||||||
'circuit', 'site', 'provider_network', 'cable'
|
'circuit', 'site', 'provider_network', 'cable'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.CircuitTerminationSerializer
|
serializer_class = serializers.CircuitTerminationSerializer
|
||||||
filterset_class = filters.CircuitTerminationFilterSet
|
filterset_class = filtersets.CircuitTerminationFilterSet
|
||||||
brief_prefetch_fields = ['circuit']
|
brief_prefetch_fields = ['circuit']
|
||||||
|
|
||||||
|
|
||||||
@ -73,4 +73,4 @@ class CircuitTerminationViewSet(PassThroughPortMixin, ModelViewSet):
|
|||||||
class ProviderNetworkViewSet(CustomFieldModelViewSet):
|
class ProviderNetworkViewSet(CustomFieldModelViewSet):
|
||||||
queryset = ProviderNetwork.objects.prefetch_related('tags')
|
queryset = ProviderNetwork.objects.prefetch_related('tags')
|
||||||
serializer_class = serializers.ProviderNetworkSerializer
|
serializer_class = serializers.ProviderNetworkSerializer
|
||||||
filterset_class = filters.ProviderNetworkFilterSet
|
filterset_class = filtersets.ProviderNetworkFilterSet
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dcim.filters import CableTerminationFilterSet
|
from dcim.filtersets import CableTerminationFilterSet
|
||||||
from dcim.models import Region, Site, SiteGroup
|
from dcim.models import Region, Site, SiteGroup
|
||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import TagFilter
|
||||||
from tenancy.filters import TenancyFilterSet
|
from netbox.filtersets import ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet
|
||||||
from utilities.filters import (
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
BaseFilterSet, NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter
|
from utilities.filters import TreeNodeMultipleChoiceFilter
|
||||||
)
|
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -20,7 +19,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class ProviderFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -80,7 +79,7 @@ class ProviderFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdated
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class ProviderNetworkFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -110,14 +109,14 @@ class ProviderNetworkFilterSet(BaseFilterSet, CustomFieldModelFilterSet, Created
|
|||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
class CircuitTypeFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class CircuitTypeFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitType
|
model = CircuitType
|
||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSet, CreatedUpdatedFilterSet):
|
class CircuitFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -207,7 +206,7 @@ class CircuitFilterSet(BaseFilterSet, CustomFieldModelFilterSet, TenancyFilterSe
|
|||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
|
||||||
class CircuitTerminationFilterSet(BaseFilterSet, CreatedUpdatedFilterSet, CableTerminationFilterSet):
|
class CircuitTerminationFilterSet(ChangeLoggedModelFilterSet, CableTerminationFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -233,7 +232,7 @@ class CircuitTerminationFilterSet(BaseFilterSet, CreatedUpdatedFilterSet, CableT
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = ['term_side', 'port_speed', 'upstream_speed', 'xconnect_id']
|
fields = ['id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id']
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
def search(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
@ -149,7 +149,7 @@ class ProviderNetwork(PrimaryModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class CircuitType(OrganizationalModel):
|
class CircuitType(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
Circuits can be organized by their functional role. For example, a user might wish to define CircuitTypes named
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
from django.db.models.signals import post_delete, post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from dcim.signals import rebuild_paths
|
from dcim.signals import rebuild_paths
|
||||||
from .models import Circuit, CircuitTermination
|
from .models import CircuitTermination
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=CircuitTermination)
|
@receiver(post_save, sender=CircuitTermination)
|
||||||
@ -11,11 +10,9 @@ 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.
|
||||||
"""
|
"""
|
||||||
fields = {
|
termination_name = f'termination_{instance.term_side.lower()}'
|
||||||
'last_updated': timezone.now(),
|
setattr(instance.circuit, termination_name, instance)
|
||||||
f'termination_{instance.term_side.lower()}': instance.pk,
|
instance.circuit.save()
|
||||||
}
|
|
||||||
Circuit.objects.filter(pk=instance.circuit_id).update(**fields)
|
|
||||||
|
|
||||||
|
|
||||||
@receiver((post_save, post_delete), sender=CircuitTermination)
|
@receiver((post_save, post_delete), sender=CircuitTermination)
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from circuits.choices import *
|
from circuits.choices import *
|
||||||
from circuits.filters import *
|
from circuits.filtersets import *
|
||||||
from circuits.models import *
|
from circuits.models import *
|
||||||
from dcim.models import Cable, Region, Site, SiteGroup
|
from dcim.models import Cable, Region, Site, SiteGroup
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
from utilities.testing import ChangeLoggedFilterSetTests
|
||||||
|
|
||||||
|
|
||||||
class ProviderTestCase(TestCase):
|
class ProviderTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Provider.objects.all()
|
queryset = Provider.objects.all()
|
||||||
filterset = ProviderFilterSet
|
filterset = ProviderFilterSet
|
||||||
|
|
||||||
@ -61,10 +62,6 @@ class ProviderTestCase(TestCase):
|
|||||||
CircuitTermination(circuit=circuits[1], site=sites[0], term_side='A'),
|
CircuitTermination(circuit=circuits[1], site=sites[0], term_side='A'),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Provider 1', 'Provider 2']}
|
params = {'name': ['Provider 1', 'Provider 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -103,7 +100,7 @@ class ProviderTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class CircuitTypeTestCase(TestCase):
|
class CircuitTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = CircuitType.objects.all()
|
queryset = CircuitType.objects.all()
|
||||||
filterset = CircuitTypeFilterSet
|
filterset = CircuitTypeFilterSet
|
||||||
|
|
||||||
@ -116,10 +113,6 @@ class CircuitTypeTestCase(TestCase):
|
|||||||
CircuitType(name='Circuit Type 3', slug='circuit-type-3'),
|
CircuitType(name='Circuit Type 3', slug='circuit-type-3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': [self.queryset.first().pk]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Circuit Type 1']}
|
params = {'name': ['Circuit Type 1']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
@ -129,7 +122,7 @@ class CircuitTypeTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class CircuitTestCase(TestCase):
|
class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Circuit.objects.all()
|
queryset = Circuit.objects.all()
|
||||||
filterset = CircuitFilterSet
|
filterset = CircuitFilterSet
|
||||||
|
|
||||||
@ -213,10 +206,6 @@ class CircuitTestCase(TestCase):
|
|||||||
))
|
))
|
||||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_cid(self):
|
def test_cid(self):
|
||||||
params = {'cid': ['Test Circuit 1', 'Test Circuit 2']}
|
params = {'cid': ['Test Circuit 1', 'Test Circuit 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -288,7 +277,7 @@ class CircuitTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class CircuitTerminationTestCase(TestCase):
|
class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = CircuitTermination.objects.all()
|
queryset = CircuitTermination.objects.all()
|
||||||
filterset = CircuitTerminationFilterSet
|
filterset = CircuitTerminationFilterSet
|
||||||
|
|
||||||
@ -382,7 +371,7 @@ class CircuitTerminationTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetworkTestCase(TestCase):
|
class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
filterset = ProviderNetworkFilterSet
|
filterset = ProviderNetworkFilterSet
|
||||||
|
|
||||||
@ -403,10 +392,6 @@ class ProviderNetworkTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
ProviderNetwork.objects.bulk_create(provider_networks)
|
ProviderNetwork.objects.bulk_create(provider_networks)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Provider Network 1', 'Provider Network 2']}
|
params = {'name': ['Provider Network 1', 'Provider Network 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
@ -7,7 +7,7 @@ from netbox.views import generic
|
|||||||
from utilities.forms import ConfirmationForm
|
from utilities.forms import ConfirmationForm
|
||||||
from utilities.tables import paginate_table
|
from utilities.tables import paginate_table
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from . import filters, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import CircuitTerminationSideChoices
|
from .choices import CircuitTerminationSideChoices
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ class ProviderListView(generic.ObjectListView):
|
|||||||
queryset = Provider.objects.annotate(
|
queryset = Provider.objects.annotate(
|
||||||
count_circuits=count_related(Circuit, 'provider')
|
count_circuits=count_related(Circuit, 'provider')
|
||||||
)
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filtersets.ProviderFilterSet
|
||||||
filterset_form = forms.ProviderFilterForm
|
filterset_form = forms.ProviderFilterForm
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ class ProviderBulkEditView(generic.BulkEditView):
|
|||||||
queryset = Provider.objects.annotate(
|
queryset = Provider.objects.annotate(
|
||||||
count_circuits=count_related(Circuit, 'provider')
|
count_circuits=count_related(Circuit, 'provider')
|
||||||
)
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filtersets.ProviderFilterSet
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
form = forms.ProviderBulkEditForm
|
form = forms.ProviderBulkEditForm
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = Provider.objects.annotate(
|
queryset = Provider.objects.annotate(
|
||||||
count_circuits=count_related(Circuit, 'provider')
|
count_circuits=count_related(Circuit, 'provider')
|
||||||
)
|
)
|
||||||
filterset = filters.ProviderFilterSet
|
filterset = filtersets.ProviderFilterSet
|
||||||
table = tables.ProviderTable
|
table = tables.ProviderTable
|
||||||
|
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class ProviderBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ProviderNetworkListView(generic.ObjectListView):
|
class ProviderNetworkListView(generic.ObjectListView):
|
||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
filterset = filters.ProviderNetworkFilterSet
|
filterset = filtersets.ProviderNetworkFilterSet
|
||||||
filterset_form = forms.ProviderNetworkFilterForm
|
filterset_form = forms.ProviderNetworkFilterForm
|
||||||
table = tables.ProviderNetworkTable
|
table = tables.ProviderNetworkTable
|
||||||
|
|
||||||
@ -125,14 +125,14 @@ class ProviderNetworkBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class ProviderNetworkBulkEditView(generic.BulkEditView):
|
class ProviderNetworkBulkEditView(generic.BulkEditView):
|
||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
filterset = filters.ProviderNetworkFilterSet
|
filterset = filtersets.ProviderNetworkFilterSet
|
||||||
table = tables.ProviderNetworkTable
|
table = tables.ProviderNetworkTable
|
||||||
form = forms.ProviderNetworkBulkEditForm
|
form = forms.ProviderNetworkBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
|
class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ProviderNetwork.objects.all()
|
queryset = ProviderNetwork.objects.all()
|
||||||
filterset = filters.ProviderNetworkFilterSet
|
filterset = filtersets.ProviderNetworkFilterSet
|
||||||
table = tables.ProviderNetworkTable
|
table = tables.ProviderNetworkTable
|
||||||
|
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ class CircuitTypeBulkEditView(generic.BulkEditView):
|
|||||||
queryset = CircuitType.objects.annotate(
|
queryset = CircuitType.objects.annotate(
|
||||||
circuit_count=count_related(Circuit, 'type')
|
circuit_count=count_related(Circuit, 'type')
|
||||||
)
|
)
|
||||||
filterset = filters.CircuitTypeFilterSet
|
filterset = filtersets.CircuitTypeFilterSet
|
||||||
table = tables.CircuitTypeTable
|
table = tables.CircuitTypeTable
|
||||||
form = forms.CircuitTypeBulkEditForm
|
form = forms.CircuitTypeBulkEditForm
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ class CircuitListView(generic.ObjectListView):
|
|||||||
queryset = Circuit.objects.prefetch_related(
|
queryset = Circuit.objects.prefetch_related(
|
||||||
'provider', 'type', 'tenant', 'termination_a', 'termination_z'
|
'provider', 'type', 'tenant', 'termination_a', 'termination_z'
|
||||||
)
|
)
|
||||||
filterset = filters.CircuitFilterSet
|
filterset = filtersets.CircuitFilterSet
|
||||||
filterset_form = forms.CircuitFilterForm
|
filterset_form = forms.CircuitFilterForm
|
||||||
table = tables.CircuitTable
|
table = tables.CircuitTable
|
||||||
|
|
||||||
@ -211,27 +211,6 @@ class CircuitListView(generic.ObjectListView):
|
|||||||
class CircuitView(generic.ObjectView):
|
class CircuitView(generic.ObjectView):
|
||||||
queryset = Circuit.objects.all()
|
queryset = Circuit.objects.all()
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
|
||||||
|
|
||||||
# A-side termination
|
|
||||||
termination_a = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
|
||||||
'site__region'
|
|
||||||
).filter(
|
|
||||||
circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_A
|
|
||||||
).first()
|
|
||||||
|
|
||||||
# Z-side termination
|
|
||||||
termination_z = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
|
|
||||||
'site__region'
|
|
||||||
).filter(
|
|
||||||
circuit=instance, term_side=CircuitTerminationSideChoices.SIDE_Z
|
|
||||||
).first()
|
|
||||||
|
|
||||||
return {
|
|
||||||
'termination_a': termination_a,
|
|
||||||
'termination_z': termination_z,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CircuitEditView(generic.ObjectEditView):
|
class CircuitEditView(generic.ObjectEditView):
|
||||||
queryset = Circuit.objects.all()
|
queryset = Circuit.objects.all()
|
||||||
@ -252,7 +231,7 @@ class CircuitBulkEditView(generic.BulkEditView):
|
|||||||
queryset = Circuit.objects.prefetch_related(
|
queryset = Circuit.objects.prefetch_related(
|
||||||
'provider', 'type', 'tenant', 'terminations'
|
'provider', 'type', 'tenant', 'terminations'
|
||||||
)
|
)
|
||||||
filterset = filters.CircuitFilterSet
|
filterset = filtersets.CircuitFilterSet
|
||||||
table = tables.CircuitTable
|
table = tables.CircuitTable
|
||||||
form = forms.CircuitBulkEditForm
|
form = forms.CircuitBulkEditForm
|
||||||
|
|
||||||
@ -261,7 +240,7 @@ class CircuitBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = Circuit.objects.prefetch_related(
|
queryset = Circuit.objects.prefetch_related(
|
||||||
'provider', 'type', 'tenant', 'terminations'
|
'provider', 'type', 'tenant', 'terminations'
|
||||||
)
|
)
|
||||||
filterset = filters.CircuitFilterSet
|
filterset = filtersets.CircuitFilterSet
|
||||||
table = tables.CircuitTable
|
table = tables.CircuitTable
|
||||||
|
|
||||||
|
|
||||||
@ -296,16 +275,11 @@ class CircuitSwapTerminations(generic.ObjectEditView):
|
|||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|
||||||
termination_a = CircuitTermination.objects.filter(
|
termination_a = CircuitTermination.objects.filter(pk=circuit.termination_a_id).first()
|
||||||
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
|
termination_z = CircuitTermination.objects.filter(pk=circuit.termination_z_id).first()
|
||||||
).first()
|
|
||||||
termination_z = CircuitTermination.objects.filter(
|
|
||||||
circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if termination_a and termination_z:
|
if termination_a and termination_z:
|
||||||
# Use a placeholder to avoid an IntegrityError on the (circuit, term_side) unique constraint
|
# Use a placeholder to avoid an IntegrityError on the (circuit, term_side) unique constraint
|
||||||
print('swapping')
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
termination_a.term_side = '_'
|
termination_a.term_side = '_'
|
||||||
termination_a.save()
|
termination_a.save()
|
||||||
@ -316,11 +290,20 @@ class CircuitSwapTerminations(generic.ObjectEditView):
|
|||||||
elif termination_a:
|
elif termination_a:
|
||||||
termination_a.term_side = 'Z'
|
termination_a.term_side = 'Z'
|
||||||
termination_a.save()
|
termination_a.save()
|
||||||
|
circuit.refresh_from_db()
|
||||||
|
circuit.termination_a = None
|
||||||
|
circuit.save()
|
||||||
else:
|
else:
|
||||||
termination_z.term_side = 'A'
|
termination_z.term_side = 'A'
|
||||||
termination_z.save()
|
termination_z.save()
|
||||||
|
circuit.refresh_from_db()
|
||||||
|
circuit.termination_z = None
|
||||||
|
circuit.save()
|
||||||
|
|
||||||
messages.success(request, "Swapped terminations for circuit {}.".format(circuit))
|
print(f'term A: {circuit.termination_a}')
|
||||||
|
print(f'term Z: {circuit.termination_z}')
|
||||||
|
|
||||||
|
messages.success(request, f"Swapped terminations for circuit {circuit}.")
|
||||||
return redirect('circuits:circuit', pk=circuit.pk)
|
return redirect('circuits:circuit', pk=circuit.pk)
|
||||||
|
|
||||||
return render(request, 'circuits/circuit_terminations_swap.html', {
|
return render(request, 'circuits/circuit_terminations_swap.html', {
|
||||||
|
@ -101,7 +101,7 @@ class NestedRackSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Rack
|
model = models.Rack
|
||||||
fields = ['id', 'url', 'display', 'name', 'display_name', 'device_count']
|
fields = ['id', 'url', 'display', 'name', 'device_count']
|
||||||
|
|
||||||
|
|
||||||
class NestedRackReservationSerializer(WritableNestedSerializer):
|
class NestedRackReservationSerializer(WritableNestedSerializer):
|
||||||
@ -136,7 +136,7 @@ class NestedDeviceTypeSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.DeviceType
|
model = models.DeviceType
|
||||||
fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'device_count']
|
fields = ['id', 'url', 'display', 'manufacturer', 'model', 'slug', 'device_count']
|
||||||
|
|
||||||
|
|
||||||
class NestedConsolePortTemplateSerializer(WritableNestedSerializer):
|
class NestedConsolePortTemplateSerializer(WritableNestedSerializer):
|
||||||
@ -232,7 +232,7 @@ class NestedDeviceSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Device
|
model = models.Device
|
||||||
fields = ['id', 'url', 'display', 'name', 'display_name']
|
fields = ['id', 'url', 'display', 'name']
|
||||||
|
|
||||||
|
|
||||||
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
|
class NestedConsoleServerPortSerializer(WritableNestedSerializer):
|
||||||
|
@ -172,10 +172,9 @@ class RackSerializer(PrimaryModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Rack
|
model = Rack
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'facility_id', 'display_name', 'site', 'location', 'tenant', 'status',
|
'id', 'url', 'display', 'name', 'facility_id', 'site', 'location', 'tenant', 'status', 'role', 'serial',
|
||||||
'role', 'serial', 'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth',
|
'asset_tag', 'type', 'width', 'u_height', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
|
||||||
'outer_unit', 'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count',
|
'comments', 'tags', 'custom_fields', 'created', 'last_updated', 'device_count', 'powerfeed_count',
|
||||||
'powerfeed_count',
|
|
||||||
]
|
]
|
||||||
# Omit the UniqueTogetherValidator that would be automatically added to validate (location, facility_id). This
|
# Omit the UniqueTogetherValidator that would be automatically added to validate (location, facility_id). This
|
||||||
# prevents facility_id from being interpreted as a required field.
|
# prevents facility_id from being interpreted as a required field.
|
||||||
@ -284,9 +283,9 @@ class DeviceTypeSerializer(PrimaryModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'display_name', 'part_number', 'u_height',
|
'id', 'url', 'display', 'manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth',
|
||||||
'is_full_depth', 'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields',
|
'subdevice_role', 'front_image', 'rear_image', 'comments', 'tags', 'custom_fields', 'created',
|
||||||
'created', 'last_updated', 'device_count',
|
'last_updated', 'device_count',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -465,10 +464,10 @@ class DeviceSerializer(PrimaryModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Device
|
model = Device
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform',
|
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status',
|
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4',
|
||||||
'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
|
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data',
|
||||||
'comments', 'local_context_data', 'tags', 'custom_fields', 'created', 'last_updated',
|
'tags', 'custom_fields', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
validators = []
|
validators = []
|
||||||
|
|
||||||
@ -501,10 +500,10 @@ class DeviceWithConfigContextSerializer(DeviceSerializer):
|
|||||||
|
|
||||||
class Meta(DeviceSerializer.Meta):
|
class Meta(DeviceSerializer.Meta):
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform',
|
'id', 'url', 'display', 'name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'asset_tag',
|
||||||
'serial', 'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status',
|
'site', 'location', 'rack', 'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4',
|
||||||
'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority',
|
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'local_context_data',
|
||||||
'comments', 'local_context_data', 'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
|
'tags', 'custom_fields', 'config_context', 'created', 'last_updated',
|
||||||
]
|
]
|
||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
@swagger_serializer_method(serializer_or_field=serializers.DictField)
|
||||||
|
@ -16,7 +16,7 @@ from rest_framework.routers import APIRootView
|
|||||||
from rest_framework.viewsets import GenericViewSet, ViewSet
|
from rest_framework.viewsets import GenericViewSet, ViewSet
|
||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit
|
||||||
from dcim import filters
|
from dcim import filtersets
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
|
from extras.api.views import ConfigContextQuerySetMixin, CustomFieldModelViewSet
|
||||||
from ipam.models import Prefix, VLAN
|
from ipam.models import Prefix, VLAN
|
||||||
@ -103,7 +103,7 @@ class RegionViewSet(CustomFieldModelViewSet):
|
|||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
serializer_class = serializers.RegionSerializer
|
serializer_class = serializers.RegionSerializer
|
||||||
filterset_class = filters.RegionFilterSet
|
filterset_class = filtersets.RegionFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -119,7 +119,7 @@ class SiteGroupViewSet(CustomFieldModelViewSet):
|
|||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
serializer_class = serializers.SiteGroupSerializer
|
serializer_class = serializers.SiteGroupSerializer
|
||||||
filterset_class = filters.SiteGroupFilterSet
|
filterset_class = filtersets.SiteGroupFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -138,7 +138,7 @@ class SiteViewSet(CustomFieldModelViewSet):
|
|||||||
virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
|
virtualmachine_count=count_related(VirtualMachine, 'cluster__site')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.SiteSerializer
|
serializer_class = serializers.SiteSerializer
|
||||||
filterset_class = filters.SiteFilterSet
|
filterset_class = filtersets.SiteFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -160,7 +160,7 @@ class LocationViewSet(CustomFieldModelViewSet):
|
|||||||
cumulative=True
|
cumulative=True
|
||||||
).prefetch_related('site')
|
).prefetch_related('site')
|
||||||
serializer_class = serializers.LocationSerializer
|
serializer_class = serializers.LocationSerializer
|
||||||
filterset_class = filters.LocationFilterSet
|
filterset_class = filtersets.LocationFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -172,7 +172,7 @@ class RackRoleViewSet(CustomFieldModelViewSet):
|
|||||||
rack_count=count_related(Rack, 'role')
|
rack_count=count_related(Rack, 'role')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.RackRoleSerializer
|
serializer_class = serializers.RackRoleSerializer
|
||||||
filterset_class = filters.RackRoleFilterSet
|
filterset_class = filtersets.RackRoleFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -187,7 +187,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
powerfeed_count=count_related(PowerFeed, 'rack')
|
powerfeed_count=count_related(PowerFeed, 'rack')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.RackSerializer
|
serializer_class = serializers.RackSerializer
|
||||||
filterset_class = filters.RackFilterSet
|
filterset_class = filtersets.RackFilterSet
|
||||||
|
|
||||||
@swagger_auto_schema(
|
@swagger_auto_schema(
|
||||||
responses={200: serializers.RackUnitSerializer(many=True)},
|
responses={200: serializers.RackUnitSerializer(many=True)},
|
||||||
@ -244,7 +244,7 @@ class RackViewSet(CustomFieldModelViewSet):
|
|||||||
class RackReservationViewSet(ModelViewSet):
|
class RackReservationViewSet(ModelViewSet):
|
||||||
queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
|
queryset = RackReservation.objects.prefetch_related('rack', 'user', 'tenant')
|
||||||
serializer_class = serializers.RackReservationSerializer
|
serializer_class = serializers.RackReservationSerializer
|
||||||
filterset_class = filters.RackReservationFilterSet
|
filterset_class = filtersets.RackReservationFilterSet
|
||||||
|
|
||||||
# Assign user from request
|
# Assign user from request
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
@ -262,7 +262,7 @@ class ManufacturerViewSet(CustomFieldModelViewSet):
|
|||||||
platform_count=count_related(Platform, 'manufacturer')
|
platform_count=count_related(Platform, 'manufacturer')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.ManufacturerSerializer
|
serializer_class = serializers.ManufacturerSerializer
|
||||||
filterset_class = filters.ManufacturerFilterSet
|
filterset_class = filtersets.ManufacturerFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -274,7 +274,7 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
|||||||
device_count=count_related(Device, 'device_type')
|
device_count=count_related(Device, 'device_type')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.DeviceTypeSerializer
|
serializer_class = serializers.DeviceTypeSerializer
|
||||||
filterset_class = filters.DeviceTypeFilterSet
|
filterset_class = filtersets.DeviceTypeFilterSet
|
||||||
brief_prefetch_fields = ['manufacturer']
|
brief_prefetch_fields = ['manufacturer']
|
||||||
|
|
||||||
|
|
||||||
@ -285,49 +285,49 @@ class DeviceTypeViewSet(CustomFieldModelViewSet):
|
|||||||
class ConsolePortTemplateViewSet(ModelViewSet):
|
class ConsolePortTemplateViewSet(ModelViewSet):
|
||||||
queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = ConsolePortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.ConsolePortTemplateSerializer
|
serializer_class = serializers.ConsolePortTemplateSerializer
|
||||||
filterset_class = filters.ConsolePortTemplateFilterSet
|
filterset_class = filtersets.ConsolePortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
class ConsoleServerPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = ConsoleServerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
serializer_class = serializers.ConsoleServerPortTemplateSerializer
|
||||||
filterset_class = filters.ConsoleServerPortTemplateFilterSet
|
filterset_class = filtersets.ConsoleServerPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateViewSet(ModelViewSet):
|
class PowerPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = PowerPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.PowerPortTemplateSerializer
|
serializer_class = serializers.PowerPortTemplateSerializer
|
||||||
filterset_class = filters.PowerPortTemplateFilterSet
|
filterset_class = filtersets.PowerPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateViewSet(ModelViewSet):
|
class PowerOutletTemplateViewSet(ModelViewSet):
|
||||||
queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = PowerOutletTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.PowerOutletTemplateSerializer
|
serializer_class = serializers.PowerOutletTemplateSerializer
|
||||||
filterset_class = filters.PowerOutletTemplateFilterSet
|
filterset_class = filtersets.PowerOutletTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateViewSet(ModelViewSet):
|
class InterfaceTemplateViewSet(ModelViewSet):
|
||||||
queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = InterfaceTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.InterfaceTemplateSerializer
|
serializer_class = serializers.InterfaceTemplateSerializer
|
||||||
filterset_class = filters.InterfaceTemplateFilterSet
|
filterset_class = filtersets.InterfaceTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateViewSet(ModelViewSet):
|
class FrontPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = FrontPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.FrontPortTemplateSerializer
|
serializer_class = serializers.FrontPortTemplateSerializer
|
||||||
filterset_class = filters.FrontPortTemplateFilterSet
|
filterset_class = filtersets.FrontPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateViewSet(ModelViewSet):
|
class RearPortTemplateViewSet(ModelViewSet):
|
||||||
queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = RearPortTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.RearPortTemplateSerializer
|
serializer_class = serializers.RearPortTemplateSerializer
|
||||||
filterset_class = filters.RearPortTemplateFilterSet
|
filterset_class = filtersets.RearPortTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateViewSet(ModelViewSet):
|
class DeviceBayTemplateViewSet(ModelViewSet):
|
||||||
queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
|
queryset = DeviceBayTemplate.objects.prefetch_related('device_type__manufacturer')
|
||||||
serializer_class = serializers.DeviceBayTemplateSerializer
|
serializer_class = serializers.DeviceBayTemplateSerializer
|
||||||
filterset_class = filters.DeviceBayTemplateFilterSet
|
filterset_class = filtersets.DeviceBayTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -340,7 +340,7 @@ class DeviceRoleViewSet(CustomFieldModelViewSet):
|
|||||||
virtualmachine_count=count_related(VirtualMachine, 'role')
|
virtualmachine_count=count_related(VirtualMachine, 'role')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.DeviceRoleSerializer
|
serializer_class = serializers.DeviceRoleSerializer
|
||||||
filterset_class = filters.DeviceRoleFilterSet
|
filterset_class = filtersets.DeviceRoleFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -353,7 +353,7 @@ class PlatformViewSet(CustomFieldModelViewSet):
|
|||||||
virtualmachine_count=count_related(VirtualMachine, 'platform')
|
virtualmachine_count=count_related(VirtualMachine, 'platform')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PlatformSerializer
|
serializer_class = serializers.PlatformSerializer
|
||||||
filterset_class = filters.PlatformFilterSet
|
filterset_class = filtersets.PlatformFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -365,7 +365,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
|
|||||||
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
|
'device_type__manufacturer', 'device_role', 'tenant', 'platform', 'site', 'location', 'rack', 'parent_bay',
|
||||||
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
'virtual_chassis__master', 'primary_ip4__nat_outside', 'primary_ip6__nat_outside', 'tags',
|
||||||
)
|
)
|
||||||
filterset_class = filters.DeviceFilterSet
|
filterset_class = filtersets.DeviceFilterSet
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
"""
|
"""
|
||||||
@ -510,7 +510,7 @@ class DeviceViewSet(ConfigContextQuerySetMixin, CustomFieldModelViewSet):
|
|||||||
class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
|
class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
|
||||||
queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
queryset = ConsolePort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
||||||
serializer_class = serializers.ConsolePortSerializer
|
serializer_class = serializers.ConsolePortSerializer
|
||||||
filterset_class = filters.ConsolePortFilterSet
|
filterset_class = filtersets.ConsolePortFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
@ -519,21 +519,21 @@ class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
|
|||||||
'device', '_path__destination', 'cable', '_cable_peer', 'tags'
|
'device', '_path__destination', 'cable', '_cable_peer', 'tags'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.ConsoleServerPortSerializer
|
serializer_class = serializers.ConsoleServerPortSerializer
|
||||||
filterset_class = filters.ConsoleServerPortFilterSet
|
filterset_class = filtersets.ConsoleServerPortFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
|
class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
|
||||||
queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
queryset = PowerPort.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
||||||
serializer_class = serializers.PowerPortSerializer
|
serializer_class = serializers.PowerPortSerializer
|
||||||
filterset_class = filters.PowerPortFilterSet
|
filterset_class = filtersets.PowerPortFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
|
class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
|
||||||
queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
queryset = PowerOutlet.objects.prefetch_related('device', '_path__destination', 'cable', '_cable_peer', 'tags')
|
||||||
serializer_class = serializers.PowerOutletSerializer
|
serializer_class = serializers.PowerOutletSerializer
|
||||||
filterset_class = filters.PowerOutletFilterSet
|
filterset_class = filtersets.PowerOutletFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
@ -542,35 +542,35 @@ class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
|
|||||||
'device', 'parent', 'lag', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags'
|
'device', 'parent', 'lag', '_path__destination', 'cable', '_cable_peer', 'ip_addresses', 'tags'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.InterfaceSerializer
|
serializer_class = serializers.InterfaceSerializer
|
||||||
filterset_class = filters.InterfaceFilterSet
|
filterset_class = filtersets.InterfaceFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
|
class FrontPortViewSet(PassThroughPortMixin, ModelViewSet):
|
||||||
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
|
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
|
||||||
serializer_class = serializers.FrontPortSerializer
|
serializer_class = serializers.FrontPortSerializer
|
||||||
filterset_class = filters.FrontPortFilterSet
|
filterset_class = filtersets.FrontPortFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
|
class RearPortViewSet(PassThroughPortMixin, ModelViewSet):
|
||||||
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
|
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
|
||||||
serializer_class = serializers.RearPortSerializer
|
serializer_class = serializers.RearPortSerializer
|
||||||
filterset_class = filters.RearPortFilterSet
|
filterset_class = filtersets.RearPortFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayViewSet(ModelViewSet):
|
class DeviceBayViewSet(ModelViewSet):
|
||||||
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
|
queryset = DeviceBay.objects.prefetch_related('installed_device').prefetch_related('tags')
|
||||||
serializer_class = serializers.DeviceBaySerializer
|
serializer_class = serializers.DeviceBaySerializer
|
||||||
filterset_class = filters.DeviceBayFilterSet
|
filterset_class = filtersets.DeviceBayFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemViewSet(ModelViewSet):
|
class InventoryItemViewSet(ModelViewSet):
|
||||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
|
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer').prefetch_related('tags')
|
||||||
serializer_class = serializers.InventoryItemSerializer
|
serializer_class = serializers.InventoryItemSerializer
|
||||||
filterset_class = filters.InventoryItemFilterSet
|
filterset_class = filtersets.InventoryItemFilterSet
|
||||||
brief_prefetch_fields = ['device']
|
brief_prefetch_fields = ['device']
|
||||||
|
|
||||||
|
|
||||||
@ -583,7 +583,7 @@ class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
_path__destination_id__isnull=False
|
_path__destination_id__isnull=False
|
||||||
)
|
)
|
||||||
serializer_class = serializers.ConsolePortSerializer
|
serializer_class = serializers.ConsolePortSerializer
|
||||||
filterset_class = filters.ConsoleConnectionFilterSet
|
filterset_class = filtersets.ConsoleConnectionFilterSet
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||||
@ -591,7 +591,7 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
_path__destination_id__isnull=False
|
_path__destination_id__isnull=False
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PowerPortSerializer
|
serializer_class = serializers.PowerPortSerializer
|
||||||
filterset_class = filters.PowerConnectionFilterSet
|
filterset_class = filtersets.PowerConnectionFilterSet
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
||||||
@ -603,7 +603,7 @@ class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
pk__lt=F('_path__destination_id')
|
pk__lt=F('_path__destination_id')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.InterfaceConnectionSerializer
|
serializer_class = serializers.InterfaceConnectionSerializer
|
||||||
filterset_class = filters.InterfaceConnectionFilterSet
|
filterset_class = filtersets.InterfaceConnectionFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -616,7 +616,7 @@ class CableViewSet(ModelViewSet):
|
|||||||
'termination_a', 'termination_b'
|
'termination_a', 'termination_b'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.CableSerializer
|
serializer_class = serializers.CableSerializer
|
||||||
filterset_class = filters.CableFilterSet
|
filterset_class = filtersets.CableFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -628,7 +628,7 @@ class VirtualChassisViewSet(ModelViewSet):
|
|||||||
member_count=count_related(Device, 'virtual_chassis')
|
member_count=count_related(Device, 'virtual_chassis')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.VirtualChassisSerializer
|
serializer_class = serializers.VirtualChassisSerializer
|
||||||
filterset_class = filters.VirtualChassisFilterSet
|
filterset_class = filtersets.VirtualChassisFilterSet
|
||||||
brief_prefetch_fields = ['master']
|
brief_prefetch_fields = ['master']
|
||||||
|
|
||||||
|
|
||||||
@ -643,7 +643,7 @@ class PowerPanelViewSet(ModelViewSet):
|
|||||||
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PowerPanelSerializer
|
serializer_class = serializers.PowerPanelSerializer
|
||||||
filterset_class = filters.PowerPanelFilterSet
|
filterset_class = filtersets.PowerPanelFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -655,7 +655,7 @@ class PowerFeedViewSet(PathEndpointMixin, CustomFieldModelViewSet):
|
|||||||
'power_panel', 'rack', '_path__destination', 'cable', '_cable_peer', 'tags'
|
'power_panel', 'rack', '_path__destination', 'cable', '_cable_peer', 'tags'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PowerFeedSerializer
|
serializer_class = serializers.PowerFeedSerializer
|
||||||
filterset_class = filters.PowerFeedFilterSet
|
filterset_class = filtersets.PowerFeedFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1001,6 +1001,7 @@ class CableTypeChoices(ChoiceSet):
|
|||||||
TYPE_MMF_OM2 = 'mmf-om2'
|
TYPE_MMF_OM2 = 'mmf-om2'
|
||||||
TYPE_MMF_OM3 = 'mmf-om3'
|
TYPE_MMF_OM3 = 'mmf-om3'
|
||||||
TYPE_MMF_OM4 = 'mmf-om4'
|
TYPE_MMF_OM4 = 'mmf-om4'
|
||||||
|
TYPE_MMF_OM5 = 'mmf-om5'
|
||||||
TYPE_SMF = 'smf'
|
TYPE_SMF = 'smf'
|
||||||
TYPE_SMF_OS1 = 'smf-os1'
|
TYPE_SMF_OS1 = 'smf-os1'
|
||||||
TYPE_SMF_OS2 = 'smf-os2'
|
TYPE_SMF_OS2 = 'smf-os2'
|
||||||
@ -1031,6 +1032,7 @@ class CableTypeChoices(ChoiceSet):
|
|||||||
(TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
(TYPE_MMF_OM2, 'Multimode Fiber (OM2)'),
|
||||||
(TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
|
(TYPE_MMF_OM3, 'Multimode Fiber (OM3)'),
|
||||||
(TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
|
(TYPE_MMF_OM4, 'Multimode Fiber (OM4)'),
|
||||||
|
(TYPE_MMF_OM5, 'Multimode Fiber (OM5)'),
|
||||||
(TYPE_SMF, 'Singlemode Fiber'),
|
(TYPE_SMF, 'Singlemode Fiber'),
|
||||||
(TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'),
|
(TYPE_SMF_OS1, 'Singlemode Fiber (OS1)'),
|
||||||
(TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'),
|
(TYPE_SMF_OS2, 'Singlemode Fiber (OS2)'),
|
||||||
|
@ -34,10 +34,11 @@ class RackElevationSVG:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_device_description(device):
|
def _get_device_description(device):
|
||||||
return '{} ({}) — {} ({}U) {} {}'.format(
|
return '{} ({}) — {} {} ({}U) {} {}'.format(
|
||||||
device.name,
|
device.name,
|
||||||
device.device_role,
|
device.device_role,
|
||||||
device.device_type.display_name,
|
device.device_type.manufacturer.name,
|
||||||
|
device.device_type.model,
|
||||||
device.device_type.u_height,
|
device.device_type.u_height,
|
||||||
device.asset_tag or '',
|
device.asset_tag or '',
|
||||||
device.serial or ''
|
device.serial or ''
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
from extras.filters import CustomFieldModelFilterSet, LocalConfigContextFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import TagFilter
|
||||||
from tenancy.filters import TenancyFilterSet
|
from extras.filtersets import LocalConfigContextFilterSet
|
||||||
|
from netbox.filtersets import (
|
||||||
|
BaseFilterSet, ChangeLoggedModelFilterSet, OrganizationalModelFilterSet, PrimaryModelFilterSet,
|
||||||
|
)
|
||||||
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from utilities.choices import ColorChoices
|
from utilities.choices import ColorChoices
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
BaseFilterSet, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter,
|
MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, TreeNodeMultipleChoiceFilter,
|
||||||
NameSlugSearchFilterSet, TagFilter, TreeNodeMultipleChoiceFilter,
|
|
||||||
)
|
)
|
||||||
from virtualization.models import Cluster
|
from virtualization.models import Cluster
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@ -57,7 +60,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class RegionFilterSet(OrganizationalModelFilterSet):
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
label='Parent region (ID)',
|
label='Parent region (ID)',
|
||||||
@ -74,7 +77,7 @@ class RegionFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilt
|
|||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class SiteGroupFilterSet(OrganizationalModelFilterSet):
|
||||||
parent_id = django_filters.ModelMultipleChoiceFilter(
|
parent_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=SiteGroup.objects.all(),
|
queryset=SiteGroup.objects.all(),
|
||||||
label='Parent site group (ID)',
|
label='Parent site group (ID)',
|
||||||
@ -91,7 +94,7 @@ class SiteGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF
|
|||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class SiteFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -154,7 +157,7 @@ class SiteFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class LocationFilterSet(OrganizationalModelFilterSet):
|
||||||
region_id = TreeNodeMultipleChoiceFilter(
|
region_id = TreeNodeMultipleChoiceFilter(
|
||||||
queryset=Region.objects.all(),
|
queryset=Region.objects.all(),
|
||||||
field_name='site__region',
|
field_name='site__region',
|
||||||
@ -218,14 +221,14 @@ class LocationFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class RackRoleFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RackRole
|
model = RackRole
|
||||||
fields = ['id', 'name', 'slug', 'color']
|
fields = ['id', 'name', 'slug', 'color']
|
||||||
|
|
||||||
|
|
||||||
class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class RackFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -323,7 +326,7 @@ class RackFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class RackReservationFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -383,14 +386,14 @@ class RackReservationFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModel
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class ManufacturerFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Manufacturer
|
model = Manufacturer
|
||||||
fields = ['id', 'name', 'slug', 'description']
|
fields = ['id', 'name', 'slug', 'description']
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class DeviceTypeFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -476,7 +479,7 @@ class DeviceTypeFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat
|
|||||||
return queryset.exclude(devicebaytemplates__isnull=value)
|
return queryset.exclude(devicebaytemplates__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class DeviceTypeComponentFilterSet(django_filters.FilterSet):
|
||||||
devicetype_id = django_filters.ModelMultipleChoiceFilter(
|
devicetype_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
queryset=DeviceType.objects.all(),
|
queryset=DeviceType.objects.all(),
|
||||||
field_name='device_type_id',
|
field_name='device_type_id',
|
||||||
@ -484,28 +487,28 @@ class DeviceTypeComponentFilterSet(NameSlugSearchFilterSet, CreatedUpdatedFilter
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class ConsolePortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = ['id', 'name', 'type']
|
fields = ['id', 'name', 'type']
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class ConsoleServerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = ['id', 'name', 'type']
|
fields = ['id', 'name', 'type']
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class PowerPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw']
|
fields = ['id', 'name', 'type', 'maximum_draw', 'allocated_draw']
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class PowerOutletTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
feed_leg = django_filters.MultipleChoiceFilter(
|
feed_leg = django_filters.MultipleChoiceFilter(
|
||||||
choices=PowerOutletFeedLegChoices,
|
choices=PowerOutletFeedLegChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -516,7 +519,7 @@ class PowerOutletTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
|||||||
fields = ['id', 'name', 'type', 'feed_leg']
|
fields = ['id', 'name', 'type', 'feed_leg']
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class InterfaceTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=InterfaceTypeChoices,
|
choices=InterfaceTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -527,7 +530,7 @@ class InterfaceTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
|||||||
fields = ['id', 'name', 'type', 'mgmt_only']
|
fields = ['id', 'name', 'type', 'mgmt_only']
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class FrontPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -538,7 +541,7 @@ class FrontPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
|||||||
fields = ['id', 'name', 'type']
|
fields = ['id', 'name', 'type']
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class RearPortTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -549,21 +552,21 @@ class RearPortTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
|||||||
fields = ['id', 'name', 'type', 'positions']
|
fields = ['id', 'name', 'type', 'positions']
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateFilterSet(BaseFilterSet, DeviceTypeComponentFilterSet):
|
class DeviceBayTemplateFilterSet(ChangeLoggedModelFilterSet, DeviceTypeComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
fields = ['id', 'name']
|
fields = ['id', 'name']
|
||||||
|
|
||||||
|
|
||||||
class DeviceRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class DeviceRoleFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceRole
|
model = DeviceRole
|
||||||
fields = ['id', 'name', 'slug', 'color', 'vm_role']
|
fields = ['id', 'name', 'slug', 'color', 'vm_role']
|
||||||
|
|
||||||
|
|
||||||
class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class PlatformFilterSet(OrganizationalModelFilterSet):
|
||||||
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
manufacturer_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
field_name='manufacturer',
|
field_name='manufacturer',
|
||||||
queryset=Manufacturer.objects.all(),
|
queryset=Manufacturer.objects.all(),
|
||||||
@ -581,13 +584,7 @@ class PlatformFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFi
|
|||||||
fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
|
fields = ['id', 'name', 'slug', 'napalm_driver', 'description']
|
||||||
|
|
||||||
|
|
||||||
class DeviceFilterSet(
|
class DeviceFilterSet(PrimaryModelFilterSet, TenancyFilterSet, LocalConfigContextFilterSet):
|
||||||
BaseFilterSet,
|
|
||||||
TenancyFilterSet,
|
|
||||||
LocalConfigContextFilterSet,
|
|
||||||
CustomFieldModelFilterSet,
|
|
||||||
CreatedUpdatedFilterSet
|
|
||||||
):
|
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -792,7 +789,7 @@ class DeviceFilterSet(
|
|||||||
return queryset.exclude(devicebays__isnull=value)
|
return queryset.exclude(devicebays__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
class DeviceComponentFilterSet(CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class DeviceComponentFilterSet(django_filters.FilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -876,7 +873,7 @@ class PathEndpointFilterSet(django_filters.FilterSet):
|
|||||||
return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
|
return queryset.filter(Q(_path__isnull=True) | Q(_path__is_active=False))
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
class ConsolePortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -887,12 +884,7 @@ class ConsolePortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina
|
|||||||
fields = ['id', 'name', 'label', 'description']
|
fields = ['id', 'name', 'label', 'description']
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortFilterSet(
|
class ConsoleServerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
||||||
BaseFilterSet,
|
|
||||||
DeviceComponentFilterSet,
|
|
||||||
CableTerminationFilterSet,
|
|
||||||
PathEndpointFilterSet
|
|
||||||
):
|
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -903,7 +895,7 @@ class ConsoleServerPortFilterSet(
|
|||||||
fields = ['id', 'name', 'label', 'description']
|
fields = ['id', 'name', 'label', 'description']
|
||||||
|
|
||||||
|
|
||||||
class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
class PowerPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -914,7 +906,7 @@ class PowerPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati
|
|||||||
fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description']
|
fields = ['id', 'name', 'label', 'maximum_draw', 'allocated_draw', 'description']
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
class PowerOutletFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -929,7 +921,7 @@ class PowerOutletFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTermina
|
|||||||
fields = ['id', 'name', 'label', 'feed_leg', 'description']
|
fields = ['id', 'name', 'label', 'feed_leg', 'description']
|
||||||
|
|
||||||
|
|
||||||
class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
class InterfaceFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -1027,7 +1019,7 @@ class InterfaceFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati
|
|||||||
}.get(value, queryset.none())
|
}.get(value, queryset.none())
|
||||||
|
|
||||||
|
|
||||||
class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
|
class FrontPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -1038,7 +1030,7 @@ class FrontPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminati
|
|||||||
fields = ['id', 'name', 'label', 'type', 'description']
|
fields = ['id', 'name', 'label', 'type', 'description']
|
||||||
|
|
||||||
|
|
||||||
class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
|
class RearPortFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet, CableTerminationFilterSet):
|
||||||
type = django_filters.MultipleChoiceFilter(
|
type = django_filters.MultipleChoiceFilter(
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
null_value=None
|
null_value=None
|
||||||
@ -1049,14 +1041,14 @@ class RearPortFilterSet(BaseFilterSet, DeviceComponentFilterSet, CableTerminatio
|
|||||||
fields = ['id', 'name', 'label', 'type', 'positions', 'description']
|
fields = ['id', 'name', 'label', 'type', 'positions', 'description']
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayFilterSet(BaseFilterSet, DeviceComponentFilterSet):
|
class DeviceBayFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = ['id', 'name', 'label', 'description']
|
fields = ['id', 'name', 'label', 'description']
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
|
class InventoryItemFilterSet(PrimaryModelFilterSet, DeviceComponentFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -1129,7 +1121,7 @@ class InventoryItemFilterSet(BaseFilterSet, DeviceComponentFilterSet):
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class VirtualChassisFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -1209,7 +1201,7 @@ class VirtualChassisFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedU
|
|||||||
return queryset.filter(qs_filter).distinct()
|
return queryset.filter(qs_filter).distinct()
|
||||||
|
|
||||||
|
|
||||||
class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class CableFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -1273,7 +1265,7 @@ class CableFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFil
|
|||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class ConnectionFilterSet:
|
class ConnectionFilterSet(BaseFilterSet):
|
||||||
|
|
||||||
def filter_site(self, queryset, name, value):
|
def filter_site(self, queryset, name, value):
|
||||||
if not value.strip():
|
if not value.strip():
|
||||||
@ -1286,7 +1278,7 @@ class ConnectionFilterSet:
|
|||||||
return queryset.filter(**{f'{name}__in': value})
|
return queryset.filter(**{f'{name}__in': value})
|
||||||
|
|
||||||
|
|
||||||
class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
|
class ConsoleConnectionFilterSet(ConnectionFilterSet):
|
||||||
site = django_filters.CharFilter(
|
site = django_filters.CharFilter(
|
||||||
method='filter_site',
|
method='filter_site',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -1304,7 +1296,7 @@ class ConsoleConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
|
|||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
|
class PowerConnectionFilterSet(ConnectionFilterSet):
|
||||||
site = django_filters.CharFilter(
|
site = django_filters.CharFilter(
|
||||||
method='filter_site',
|
method='filter_site',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -1322,7 +1314,7 @@ class PowerConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
|
|||||||
fields = ['name']
|
fields = ['name']
|
||||||
|
|
||||||
|
|
||||||
class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
|
class InterfaceConnectionFilterSet(ConnectionFilterSet):
|
||||||
site = django_filters.CharFilter(
|
site = django_filters.CharFilter(
|
||||||
method='filter_site',
|
method='filter_site',
|
||||||
label='Site (slug)',
|
label='Site (slug)',
|
||||||
@ -1340,7 +1332,7 @@ class InterfaceConnectionFilterSet(ConnectionFilterSet, BaseFilterSet):
|
|||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class PowerPanelFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -1402,13 +1394,7 @@ class PowerPanelFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdat
|
|||||||
return queryset.filter(qs_filter)
|
return queryset.filter(qs_filter)
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedFilterSet(
|
class PowerFeedFilterSet(PrimaryModelFilterSet, CableTerminationFilterSet, PathEndpointFilterSet):
|
||||||
BaseFilterSet,
|
|
||||||
CableTerminationFilterSet,
|
|
||||||
PathEndpointFilterSet,
|
|
||||||
CustomFieldModelFilterSet,
|
|
||||||
CreatedUpdatedFilterSet
|
|
||||||
):
|
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
@ -2153,7 +2153,7 @@ class DeviceForm(BootstrapMixin, TenancyForm, CustomFieldModelForm):
|
|||||||
ip_choices = [(None, '---------')]
|
ip_choices = [(None, '---------')]
|
||||||
|
|
||||||
# Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member
|
# Gather PKs of all interfaces belonging to this Device or a peer VirtualChassis member
|
||||||
interface_ids = self.instance.vc_interfaces().values_list('pk', flat=True)
|
interface_ids = self.instance.vc_interfaces(if_master=False).values_list('pk', flat=True)
|
||||||
|
|
||||||
# Collect interface IPs
|
# Collect interface IPs
|
||||||
interface_ips = IPAddress.objects.filter(
|
interface_ips = IPAddress.objects.filter(
|
||||||
|
@ -36,7 +36,7 @@ __all__ = (
|
|||||||
# Device Types
|
# Device Types
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Manufacturer(OrganizationalModel):
|
class Manufacturer(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell.
|
A Manufacturer represents a company which produces hardware devices; for example, Juniper or Dell.
|
||||||
@ -320,10 +320,6 @@ class DeviceType(PrimaryModel):
|
|||||||
if self.rear_image:
|
if self.rear_image:
|
||||||
self.rear_image.delete(save=False)
|
self.rear_image.delete(save=False)
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
return f'{self.manufacturer.name} {self.model}'
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_parent_device(self):
|
def is_parent_device(self):
|
||||||
return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT
|
return self.subdevice_role == SubdeviceRoleChoices.ROLE_PARENT
|
||||||
@ -337,7 +333,7 @@ class DeviceType(PrimaryModel):
|
|||||||
# Devices
|
# Devices
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class DeviceRole(OrganizationalModel):
|
class DeviceRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
|
Devices are organized by functional role; for example, "Core Switch" or "File Server". Each DeviceRole is assigned a
|
||||||
@ -388,7 +384,7 @@ class DeviceRole(OrganizationalModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Platform(OrganizationalModel):
|
class Platform(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
Platform refers to the software or firmware running on a Device. For example, "Cisco IOS-XR" or "Juniper Junos".
|
||||||
@ -622,7 +618,13 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.display_name or super().__str__()
|
if self.name:
|
||||||
|
return self.name
|
||||||
|
elif self.virtual_chassis:
|
||||||
|
return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})'
|
||||||
|
elif self.device_type:
|
||||||
|
return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})'
|
||||||
|
return super().__str__()
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:device', args=[self.pk])
|
return reverse('dcim:device', args=[self.pk])
|
||||||
@ -716,7 +718,7 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Validate primary IP addresses
|
# Validate primary IP addresses
|
||||||
vc_interfaces = self.vc_interfaces()
|
vc_interfaces = self.vc_interfaces(if_master=False)
|
||||||
if self.primary_ip4:
|
if self.primary_ip4:
|
||||||
if self.primary_ip4.family != 4:
|
if self.primary_ip4.family != 4:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
@ -823,17 +825,6 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
self.comments,
|
self.comments,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
if self.name:
|
|
||||||
return self.name
|
|
||||||
elif self.virtual_chassis:
|
|
||||||
return f'{self.virtual_chassis.name}:{self.vc_position} ({self.pk})'
|
|
||||||
elif self.device_type:
|
|
||||||
return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})'
|
|
||||||
else:
|
|
||||||
return '' # Device has not yet been created
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def identifier(self):
|
def identifier(self):
|
||||||
"""
|
"""
|
||||||
@ -856,9 +847,7 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def interfaces_count(self):
|
def interfaces_count(self):
|
||||||
if self.virtual_chassis and self.virtual_chassis.master == self:
|
return self.vc_interfaces().count()
|
||||||
return self.vc_interfaces().count()
|
|
||||||
return self.interfaces.count()
|
|
||||||
|
|
||||||
def get_vc_master(self):
|
def get_vc_master(self):
|
||||||
"""
|
"""
|
||||||
@ -866,7 +855,7 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
"""
|
"""
|
||||||
return self.virtual_chassis.master if self.virtual_chassis else None
|
return self.virtual_chassis.master if self.virtual_chassis else None
|
||||||
|
|
||||||
def vc_interfaces(self, if_master=False):
|
def vc_interfaces(self, if_master=True):
|
||||||
"""
|
"""
|
||||||
Return a QuerySet matching all Interfaces assigned to this Device or, if this Device is a VC master, to another
|
Return a QuerySet matching all Interfaces assigned to this Device or, if this Device is a VC master, to another
|
||||||
Device belonging to the same VirtualChassis.
|
Device belonging to the same VirtualChassis.
|
||||||
@ -874,7 +863,7 @@ class Device(PrimaryModel, ConfigContextModel):
|
|||||||
:param if_master: If True, return VC member interfaces only if this Device is the VC master.
|
:param if_master: If True, return VC member interfaces only if this Device is the VC master.
|
||||||
"""
|
"""
|
||||||
filter = Q(device=self)
|
filter = Q(device=self)
|
||||||
if self.virtual_chassis and (not if_master or self.virtual_chassis.master == self):
|
if self.virtual_chassis and (self.virtual_chassis.master == self or not if_master):
|
||||||
filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False)
|
filter |= Q(device__virtual_chassis=self.virtual_chassis, mgmt_only=False)
|
||||||
return Interface.objects.filter(filter)
|
return Interface.objects.filter(filter)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ __all__ = (
|
|||||||
# Racks
|
# Racks
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class RackRole(OrganizationalModel):
|
class RackRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
Racks can be organized by functional role, similar to Devices.
|
Racks can be organized by functional role, similar to Devices.
|
||||||
@ -209,7 +209,9 @@ class Rack(PrimaryModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.display_name or super().__str__()
|
if self.facility_id:
|
||||||
|
return f'{self.name} ({self.facility_id})'
|
||||||
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('dcim:rack', args=[self.pk])
|
return reverse('dcim:rack', args=[self.pk])
|
||||||
@ -277,12 +279,6 @@ class Rack(PrimaryModel):
|
|||||||
else:
|
else:
|
||||||
return reversed(range(1, self.u_height + 1))
|
return reversed(range(1, self.u_height + 1))
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
if self.facility_id:
|
|
||||||
return f'{self.name} ({self.facility_id})'
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return RackStatusChoices.CSS_CLASSES.get(self.status)
|
return RackStatusChoices.CSS_CLASSES.get(self.status)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ __all__ = (
|
|||||||
# Regions
|
# Regions
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Region(NestedGroupModel):
|
class Region(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
A region represents a geographic collection of sites. For example, you might create regions representing countries,
|
A region represents a geographic collection of sites. For example, you might create regions representing countries,
|
||||||
@ -78,7 +78,7 @@ class Region(NestedGroupModel):
|
|||||||
# Site groups
|
# Site groups
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class SiteGroup(NestedGroupModel):
|
class SiteGroup(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and
|
A site group is an arbitrary grouping of sites. For example, you might have corporate sites and customer sites; and
|
||||||
@ -285,7 +285,7 @@ class Site(PrimaryModel):
|
|||||||
# Locations
|
# Locations
|
||||||
#
|
#
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Location(NestedGroupModel):
|
class Location(NestedGroupModel):
|
||||||
"""
|
"""
|
||||||
A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
|
A Location represents a subgroup of Racks and/or Devices within a Site. A Location may represent a building within a
|
||||||
|
@ -520,6 +520,7 @@ class DeviceInterfaceTable(InterfaceTable):
|
|||||||
'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses',
|
'description', 'mark_connected', 'cable', 'cable_color', 'cable_peer', 'connection', 'tags', 'ip_addresses',
|
||||||
'untagged_vlan', 'tagged_vlans', 'actions',
|
'untagged_vlan', 'tagged_vlans', 'actions',
|
||||||
)
|
)
|
||||||
|
order_by = ('name',)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses',
|
'pk', 'name', 'label', 'enabled', 'type', 'parent', 'lag', 'mtu', 'mode', 'description', 'ip_addresses',
|
||||||
'cable', 'connection', 'actions',
|
'cable', 'connection', 'actions',
|
||||||
|
@ -251,7 +251,7 @@ class RackRoleTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class RackTest(APIViewTestCases.APIViewTestCase):
|
class RackTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Rack
|
model = Rack
|
||||||
brief_fields = ['device_count', 'display', 'display_name', 'id', 'name', 'url']
|
brief_fields = ['device_count', 'display', 'id', 'name', 'url']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'status': 'planned',
|
'status': 'planned',
|
||||||
}
|
}
|
||||||
@ -422,7 +422,7 @@ class ManufacturerTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
|
class DeviceTypeTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = DeviceType
|
model = DeviceType
|
||||||
brief_fields = ['device_count', 'display', 'display_name', 'id', 'manufacturer', 'model', 'slug', 'url']
|
brief_fields = ['device_count', 'display', 'id', 'manufacturer', 'model', 'slug', 'url']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'part_number': 'ABC123',
|
'part_number': 'ABC123',
|
||||||
}
|
}
|
||||||
@ -870,7 +870,7 @@ class PlatformTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class DeviceTest(APIViewTestCases.APIViewTestCase):
|
class DeviceTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = Device
|
model = Device
|
||||||
brief_fields = ['display', 'display_name', 'id', 'name', 'url']
|
brief_fields = ['display', 'id', 'name', 'url']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'status': 'failed',
|
'status': 'failed',
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,15 @@ from django.contrib.auth.models import User
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.choices import *
|
from dcim.choices import *
|
||||||
from dcim.filters import *
|
from dcim.filtersets import *
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
from utilities.testing import ChangeLoggedFilterSetTests
|
||||||
from virtualization.models import Cluster, ClusterType
|
from virtualization.models import Cluster, ClusterType
|
||||||
|
|
||||||
|
|
||||||
class RegionTestCase(TestCase):
|
class RegionTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Region.objects.all()
|
queryset = Region.objects.all()
|
||||||
filterset = RegionFilterSet
|
filterset = RegionFilterSet
|
||||||
|
|
||||||
@ -35,10 +36,6 @@ class RegionTestCase(TestCase):
|
|||||||
for region in child_regions:
|
for region in child_regions:
|
||||||
region.save()
|
region.save()
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Region 1', 'Region 2']}
|
params = {'name': ['Region 1', 'Region 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -59,7 +56,7 @@ class RegionTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class SiteGroupTestCase(TestCase):
|
class SiteGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = SiteGroup.objects.all()
|
queryset = SiteGroup.objects.all()
|
||||||
filterset = SiteGroupFilterSet
|
filterset = SiteGroupFilterSet
|
||||||
|
|
||||||
@ -85,10 +82,6 @@ class SiteGroupTestCase(TestCase):
|
|||||||
for sitegroup in child_sitegroups:
|
for sitegroup in child_sitegroups:
|
||||||
sitegroup.save()
|
sitegroup.save()
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Site Group 1', 'Site Group 2']}
|
params = {'name': ['Site Group 1', 'Site Group 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -109,7 +102,7 @@ class SiteGroupTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class SiteTestCase(TestCase):
|
class SiteTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Site.objects.all()
|
queryset = Site.objects.all()
|
||||||
filterset = SiteFilterSet
|
filterset = SiteFilterSet
|
||||||
|
|
||||||
@ -154,10 +147,6 @@ class SiteTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Site.objects.bulk_create(sites)
|
Site.objects.bulk_create(sites)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Site 1', 'Site 2']}
|
params = {'name': ['Site 1', 'Site 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -227,7 +216,7 @@ class SiteTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class LocationTestCase(TestCase):
|
class LocationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Location.objects.all()
|
queryset = Location.objects.all()
|
||||||
filterset = LocationFilterSet
|
filterset = LocationFilterSet
|
||||||
|
|
||||||
@ -273,10 +262,6 @@ class LocationTestCase(TestCase):
|
|||||||
for location in locations:
|
for location in locations:
|
||||||
location.save()
|
location.save()
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Location 1', 'Location 2']}
|
params = {'name': ['Location 1', 'Location 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -318,7 +303,7 @@ class LocationTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class RackRoleTestCase(TestCase):
|
class RackRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RackRole.objects.all()
|
queryset = RackRole.objects.all()
|
||||||
filterset = RackRoleFilterSet
|
filterset = RackRoleFilterSet
|
||||||
|
|
||||||
@ -332,10 +317,6 @@ class RackRoleTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
RackRole.objects.bulk_create(rack_roles)
|
RackRole.objects.bulk_create(rack_roles)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Rack Role 1', 'Rack Role 2']}
|
params = {'name': ['Rack Role 1', 'Rack Role 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -349,7 +330,7 @@ class RackRoleTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class RackTestCase(TestCase):
|
class RackTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Rack.objects.all()
|
queryset = Rack.objects.all()
|
||||||
filterset = RackFilterSet
|
filterset = RackFilterSet
|
||||||
|
|
||||||
@ -416,10 +397,6 @@ class RackTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Rack.objects.bulk_create(racks)
|
Rack.objects.bulk_create(racks)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Rack 1', 'Rack 2']}
|
params = {'name': ['Rack 1', 'Rack 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -523,7 +500,7 @@ class RackTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class RackReservationTestCase(TestCase):
|
class RackReservationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RackReservation.objects.all()
|
queryset = RackReservation.objects.all()
|
||||||
filterset = RackReservationFilterSet
|
filterset = RackReservationFilterSet
|
||||||
|
|
||||||
@ -581,10 +558,6 @@ class RackReservationTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
RackReservation.objects.bulk_create(reservations)
|
RackReservation.objects.bulk_create(reservations)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_site(self):
|
def test_site(self):
|
||||||
sites = Site.objects.all()[:2]
|
sites = Site.objects.all()[:2]
|
||||||
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
params = {'site_id': [sites[0].pk, sites[1].pk]}
|
||||||
@ -621,7 +594,7 @@ class RackReservationTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ManufacturerTestCase(TestCase):
|
class ManufacturerTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Manufacturer.objects.all()
|
queryset = Manufacturer.objects.all()
|
||||||
filterset = ManufacturerFilterSet
|
filterset = ManufacturerFilterSet
|
||||||
|
|
||||||
@ -635,10 +608,6 @@ class ManufacturerTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Manufacturer.objects.bulk_create(manufacturers)
|
Manufacturer.objects.bulk_create(manufacturers)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Manufacturer 1', 'Manufacturer 2']}
|
params = {'name': ['Manufacturer 1', 'Manufacturer 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -652,7 +621,7 @@ class ManufacturerTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class DeviceTypeTestCase(TestCase):
|
class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = DeviceType.objects.all()
|
queryset = DeviceType.objects.all()
|
||||||
filterset = DeviceTypeFilterSet
|
filterset = DeviceTypeFilterSet
|
||||||
|
|
||||||
@ -708,10 +677,6 @@ class DeviceTypeTestCase(TestCase):
|
|||||||
DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'),
|
DeviceBayTemplate(device_type=device_types[1], name='Device Bay 2'),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_model(self):
|
def test_model(self):
|
||||||
params = {'model': ['Model 1', 'Model 2']}
|
params = {'model': ['Model 1', 'Model 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -788,7 +753,7 @@ class DeviceTypeTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateTestCase(TestCase):
|
class ConsolePortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ConsolePortTemplate.objects.all()
|
queryset = ConsolePortTemplate.objects.all()
|
||||||
filterset = ConsolePortTemplateFilterSet
|
filterset = ConsolePortTemplateFilterSet
|
||||||
|
|
||||||
@ -810,10 +775,6 @@ class ConsolePortTemplateTestCase(TestCase):
|
|||||||
ConsolePortTemplate(device_type=device_types[2], name='Console Port 3'),
|
ConsolePortTemplate(device_type=device_types[2], name='Console Port 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Console Port 1', 'Console Port 2']}
|
params = {'name': ['Console Port 1', 'Console Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -824,7 +785,7 @@ class ConsolePortTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateTestCase(TestCase):
|
class ConsoleServerPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ConsoleServerPortTemplate.objects.all()
|
queryset = ConsoleServerPortTemplate.objects.all()
|
||||||
filterset = ConsoleServerPortTemplateFilterSet
|
filterset = ConsoleServerPortTemplateFilterSet
|
||||||
|
|
||||||
@ -846,10 +807,6 @@ class ConsoleServerPortTemplateTestCase(TestCase):
|
|||||||
ConsoleServerPortTemplate(device_type=device_types[2], name='Console Server Port 3'),
|
ConsoleServerPortTemplate(device_type=device_types[2], name='Console Server Port 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
|
params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -860,7 +817,7 @@ class ConsoleServerPortTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateTestCase(TestCase):
|
class PowerPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = PowerPortTemplate.objects.all()
|
queryset = PowerPortTemplate.objects.all()
|
||||||
filterset = PowerPortTemplateFilterSet
|
filterset = PowerPortTemplateFilterSet
|
||||||
|
|
||||||
@ -882,10 +839,6 @@ class PowerPortTemplateTestCase(TestCase):
|
|||||||
PowerPortTemplate(device_type=device_types[2], name='Power Port 3', maximum_draw=300, allocated_draw=150),
|
PowerPortTemplate(device_type=device_types[2], name='Power Port 3', maximum_draw=300, allocated_draw=150),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Power Port 1', 'Power Port 2']}
|
params = {'name': ['Power Port 1', 'Power Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -904,7 +857,7 @@ class PowerPortTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateTestCase(TestCase):
|
class PowerOutletTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = PowerOutletTemplate.objects.all()
|
queryset = PowerOutletTemplate.objects.all()
|
||||||
filterset = PowerOutletTemplateFilterSet
|
filterset = PowerOutletTemplateFilterSet
|
||||||
|
|
||||||
@ -926,10 +879,6 @@ class PowerOutletTemplateTestCase(TestCase):
|
|||||||
PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C),
|
PowerOutletTemplate(device_type=device_types[2], name='Power Outlet 3', feed_leg=PowerOutletFeedLegChoices.FEED_LEG_C),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
|
params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -944,7 +893,7 @@ class PowerOutletTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateTestCase(TestCase):
|
class InterfaceTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = InterfaceTemplate.objects.all()
|
queryset = InterfaceTemplate.objects.all()
|
||||||
filterset = InterfaceTemplateFilterSet
|
filterset = InterfaceTemplateFilterSet
|
||||||
|
|
||||||
@ -966,10 +915,6 @@ class InterfaceTemplateTestCase(TestCase):
|
|||||||
InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP, mgmt_only=False),
|
InterfaceTemplate(device_type=device_types[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_SFP, mgmt_only=False),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Interface 1', 'Interface 2']}
|
params = {'name': ['Interface 1', 'Interface 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -990,7 +935,7 @@ class InterfaceTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateTestCase(TestCase):
|
class FrontPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = FrontPortTemplate.objects.all()
|
queryset = FrontPortTemplate.objects.all()
|
||||||
filterset = FrontPortTemplateFilterSet
|
filterset = FrontPortTemplateFilterSet
|
||||||
|
|
||||||
@ -1019,10 +964,6 @@ class FrontPortTemplateTestCase(TestCase):
|
|||||||
FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2], type=PortTypeChoices.TYPE_BNC),
|
FrontPortTemplate(device_type=device_types[2], name='Front Port 3', rear_port=rear_ports[2], type=PortTypeChoices.TYPE_BNC),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Front Port 1', 'Front Port 2']}
|
params = {'name': ['Front Port 1', 'Front Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1037,7 +978,7 @@ class FrontPortTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateTestCase(TestCase):
|
class RearPortTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RearPortTemplate.objects.all()
|
queryset = RearPortTemplate.objects.all()
|
||||||
filterset = RearPortTemplateFilterSet
|
filterset = RearPortTemplateFilterSet
|
||||||
|
|
||||||
@ -1059,10 +1000,6 @@ class RearPortTemplateTestCase(TestCase):
|
|||||||
RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC, positions=3),
|
RearPortTemplate(device_type=device_types[2], name='Rear Port 3', type=PortTypeChoices.TYPE_BNC, positions=3),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Rear Port 1', 'Rear Port 2']}
|
params = {'name': ['Rear Port 1', 'Rear Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1081,7 +1018,7 @@ class RearPortTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateTestCase(TestCase):
|
class DeviceBayTemplateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = DeviceBayTemplate.objects.all()
|
queryset = DeviceBayTemplate.objects.all()
|
||||||
filterset = DeviceBayTemplateFilterSet
|
filterset = DeviceBayTemplateFilterSet
|
||||||
|
|
||||||
@ -1103,10 +1040,6 @@ class DeviceBayTemplateTestCase(TestCase):
|
|||||||
DeviceBayTemplate(device_type=device_types[2], name='Device Bay 3'),
|
DeviceBayTemplate(device_type=device_types[2], name='Device Bay 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Device Bay 1', 'Device Bay 2']}
|
params = {'name': ['Device Bay 1', 'Device Bay 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1117,7 +1050,7 @@ class DeviceBayTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class DeviceRoleTestCase(TestCase):
|
class DeviceRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = DeviceRole.objects.all()
|
queryset = DeviceRole.objects.all()
|
||||||
filterset = DeviceRoleFilterSet
|
filterset = DeviceRoleFilterSet
|
||||||
|
|
||||||
@ -1131,10 +1064,6 @@ class DeviceRoleTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
DeviceRole.objects.bulk_create(device_roles)
|
DeviceRole.objects.bulk_create(device_roles)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Device Role 1', 'Device Role 2']}
|
params = {'name': ['Device Role 1', 'Device Role 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1154,7 +1083,7 @@ class DeviceRoleTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class PlatformTestCase(TestCase):
|
class PlatformTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
filterset = PlatformFilterSet
|
filterset = PlatformFilterSet
|
||||||
|
|
||||||
@ -1175,10 +1104,6 @@ class PlatformTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Platform.objects.bulk_create(platforms)
|
Platform.objects.bulk_create(platforms)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Platform 1', 'Platform 2']}
|
params = {'name': ['Platform 1', 'Platform 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1203,7 +1128,7 @@ class PlatformTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class DeviceTestCase(TestCase):
|
class DeviceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
filterset = DeviceFilterSet
|
filterset = DeviceFilterSet
|
||||||
|
|
||||||
@ -1356,10 +1281,6 @@ class DeviceTestCase(TestCase):
|
|||||||
Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1)
|
Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=virtual_chassis, vc_position=1, vc_priority=1)
|
||||||
Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2)
|
Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=virtual_chassis, vc_position=2, vc_priority=2)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Device 1', 'Device 2']}
|
params = {'name': ['Device 1', 'Device 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1549,7 +1470,7 @@ class DeviceTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTestCase(TestCase):
|
class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
filterset = ConsolePortFilterSet
|
filterset = ConsolePortFilterSet
|
||||||
|
|
||||||
@ -1608,10 +1529,6 @@ class ConsolePortTestCase(TestCase):
|
|||||||
Cable(termination_a=console_ports[1], termination_b=console_server_ports[1]).save()
|
Cable(termination_a=console_ports[1], termination_b=console_server_ports[1]).save()
|
||||||
# Third port is not connected
|
# Third port is not connected
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Console Port 1', 'Console Port 2']}
|
params = {'name': ['Console Port 1', 'Console Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1665,7 +1582,7 @@ class ConsolePortTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTestCase(TestCase):
|
class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
filterset = ConsoleServerPortFilterSet
|
filterset = ConsoleServerPortFilterSet
|
||||||
|
|
||||||
@ -1724,10 +1641,6 @@ class ConsoleServerPortTestCase(TestCase):
|
|||||||
Cable(termination_a=console_server_ports[1], termination_b=console_ports[1]).save()
|
Cable(termination_a=console_server_ports[1], termination_b=console_ports[1]).save()
|
||||||
# Third port is not connected
|
# Third port is not connected
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
|
params = {'name': ['Console Server Port 1', 'Console Server Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1781,7 +1694,7 @@ class ConsoleServerPortTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTestCase(TestCase):
|
class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
filterset = PowerPortFilterSet
|
filterset = PowerPortFilterSet
|
||||||
|
|
||||||
@ -1840,10 +1753,6 @@ class PowerPortTestCase(TestCase):
|
|||||||
Cable(termination_a=power_ports[1], termination_b=power_outlets[1]).save()
|
Cable(termination_a=power_ports[1], termination_b=power_outlets[1]).save()
|
||||||
# Third port is not connected
|
# Third port is not connected
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Power Port 1', 'Power Port 2']}
|
params = {'name': ['Power Port 1', 'Power Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1905,7 +1814,7 @@ class PowerPortTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTestCase(TestCase):
|
class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
filterset = PowerOutletFilterSet
|
filterset = PowerOutletFilterSet
|
||||||
|
|
||||||
@ -1964,10 +1873,6 @@ class PowerOutletTestCase(TestCase):
|
|||||||
Cable(termination_a=power_outlets[1], termination_b=power_ports[1]).save()
|
Cable(termination_a=power_outlets[1], termination_b=power_ports[1]).save()
|
||||||
# Third port is not connected
|
# Third port is not connected
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
|
params = {'name': ['Power Outlet 1', 'Power Outlet 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2025,7 +1930,7 @@ class PowerOutletTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTestCase(TestCase):
|
class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
filterset = InterfaceFilterSet
|
filterset = InterfaceFilterSet
|
||||||
|
|
||||||
@ -2081,10 +1986,6 @@ class InterfaceTestCase(TestCase):
|
|||||||
Cable(termination_a=interfaces[1], termination_b=interfaces[4]).save()
|
Cable(termination_a=interfaces[1], termination_b=interfaces[4]).save()
|
||||||
# Third pair is not connected
|
# Third pair is not connected
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Interface 1', 'Interface 2']}
|
params = {'name': ['Interface 1', 'Interface 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2200,7 +2101,7 @@ class InterfaceTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTestCase(TestCase):
|
class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
filterset = FrontPortFilterSet
|
filterset = FrontPortFilterSet
|
||||||
|
|
||||||
@ -2266,10 +2167,6 @@ class FrontPortTestCase(TestCase):
|
|||||||
Cable(termination_a=front_ports[1], termination_b=front_ports[4]).save()
|
Cable(termination_a=front_ports[1], termination_b=front_ports[4]).save()
|
||||||
# Third port is not connected
|
# Third port is not connected
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Front Port 1', 'Front Port 2']}
|
params = {'name': ['Front Port 1', 'Front Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2321,7 +2218,7 @@ class FrontPortTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class RearPortTestCase(TestCase):
|
class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
filterset = RearPortFilterSet
|
filterset = RearPortFilterSet
|
||||||
|
|
||||||
@ -2377,10 +2274,6 @@ class RearPortTestCase(TestCase):
|
|||||||
Cable(termination_a=rear_ports[1], termination_b=rear_ports[4]).save()
|
Cable(termination_a=rear_ports[1], termination_b=rear_ports[4]).save()
|
||||||
# Third port is not connected
|
# Third port is not connected
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Rear Port 1', 'Rear Port 2']}
|
params = {'name': ['Rear Port 1', 'Rear Port 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2436,7 +2329,7 @@ class RearPortTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTestCase(TestCase):
|
class DeviceBayTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
filterset = DeviceBayFilterSet
|
filterset = DeviceBayFilterSet
|
||||||
|
|
||||||
@ -2483,10 +2376,6 @@ class DeviceBayTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
DeviceBay.objects.bulk_create(device_bays)
|
DeviceBay.objects.bulk_create(device_bays)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Device Bay 1', 'Device Bay 2']}
|
params = {'name': ['Device Bay 1', 'Device Bay 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2528,7 +2417,7 @@ class DeviceBayTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemTestCase(TestCase):
|
class InventoryItemTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
filterset = InventoryItemFilterSet
|
filterset = InventoryItemFilterSet
|
||||||
|
|
||||||
@ -2591,10 +2480,6 @@ class InventoryItemTestCase(TestCase):
|
|||||||
for i in child_inventory_items:
|
for i in child_inventory_items:
|
||||||
i.save()
|
i.save()
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Inventory Item 1', 'Inventory Item 2']}
|
params = {'name': ['Inventory Item 1', 'Inventory Item 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2666,7 +2551,7 @@ class InventoryItemTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisTestCase(TestCase):
|
class VirtualChassisTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VirtualChassis.objects.all()
|
queryset = VirtualChassis.objects.all()
|
||||||
filterset = VirtualChassisFilterSet
|
filterset = VirtualChassisFilterSet
|
||||||
|
|
||||||
@ -2721,10 +2606,6 @@ class VirtualChassisTestCase(TestCase):
|
|||||||
Device.objects.filter(pk=devices[3].pk).update(virtual_chassis=virtual_chassis[1])
|
Device.objects.filter(pk=devices[3].pk).update(virtual_chassis=virtual_chassis[1])
|
||||||
Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=virtual_chassis[2])
|
Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=virtual_chassis[2])
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_domain(self):
|
def test_domain(self):
|
||||||
params = {'domain': ['Domain 1', 'Domain 2']}
|
params = {'domain': ['Domain 1', 'Domain 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2762,7 +2643,7 @@ class VirtualChassisTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class CableTestCase(TestCase):
|
class CableTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Cable.objects.all()
|
queryset = Cable.objects.all()
|
||||||
filterset = CableFilterSet
|
filterset = CableFilterSet
|
||||||
|
|
||||||
@ -2827,10 +2708,6 @@ class CableTestCase(TestCase):
|
|||||||
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
Cable(termination_a=interfaces[9], termination_b=interfaces[10], label='Cable 5', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=10, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
||||||
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
Cable(termination_a=interfaces[11], termination_b=interfaces[0], label='Cable 6', type=CableTypeChoices.TYPE_CAT6, status=CableStatusChoices.STATUS_PLANNED, color='e91e63', length=20, length_unit=CableLengthUnitChoices.UNIT_METER).save()
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_label(self):
|
def test_label(self):
|
||||||
params = {'label': ['Cable 1', 'Cable 2']}
|
params = {'label': ['Cable 1', 'Cable 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2886,7 +2763,7 @@ class CableTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class PowerPanelTestCase(TestCase):
|
class PowerPanelTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = PowerPanel.objects.all()
|
queryset = PowerPanel.objects.all()
|
||||||
filterset = PowerPanelFilterSet
|
filterset = PowerPanelFilterSet
|
||||||
|
|
||||||
@ -2931,10 +2808,6 @@ class PowerPanelTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
PowerPanel.objects.bulk_create(power_panels)
|
PowerPanel.objects.bulk_create(power_panels)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Power Panel 1', 'Power Panel 2']}
|
params = {'name': ['Power Panel 1', 'Power Panel 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -2966,7 +2839,7 @@ class PowerPanelTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class PowerFeedTestCase(TestCase):
|
class PowerFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = PowerFeed.objects.all()
|
queryset = PowerFeed.objects.all()
|
||||||
filterset = PowerFeedFilterSet
|
filterset = PowerFeedFilterSet
|
||||||
|
|
||||||
@ -3029,10 +2902,6 @@ class PowerFeedTestCase(TestCase):
|
|||||||
Cable(termination_a=power_feeds[0], termination_b=power_ports[0]).save()
|
Cable(termination_a=power_feeds[0], termination_b=power_ports[0]).save()
|
||||||
Cable(termination_a=power_feeds[1], termination_b=power_ports[1]).save()
|
Cable(termination_a=power_feeds[1], termination_b=power_ports[1]).save()
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Power Feed 1', 'Power Feed 2']}
|
params = {'name': ['Power Feed 1', 'Power Feed 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
@ -27,7 +27,7 @@ from utilities.tables import paginate_table
|
|||||||
from utilities.utils import csv_format, count_related
|
from utilities.utils import csv_format, count_related
|
||||||
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
|
from utilities.views import GetReturnURLMixin, ObjectPermissionRequiredMixin
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from . import filters, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import DeviceFaceChoices
|
from .choices import DeviceFaceChoices
|
||||||
from .constants import NONCONNECTABLE_IFACE_TYPES
|
from .constants import NONCONNECTABLE_IFACE_TYPES
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -110,7 +110,7 @@ class RegionListView(generic.ObjectListView):
|
|||||||
'site_count',
|
'site_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.RegionFilterSet
|
filterset = filtersets.RegionFilterSet
|
||||||
filterset_form = forms.RegionFilterForm
|
filterset_form = forms.RegionFilterForm
|
||||||
table = tables.RegionTable
|
table = tables.RegionTable
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ class RegionBulkEditView(generic.BulkEditView):
|
|||||||
'site_count',
|
'site_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.RegionFilterSet
|
filterset = filtersets.RegionFilterSet
|
||||||
table = tables.RegionTable
|
table = tables.RegionTable
|
||||||
form = forms.RegionBulkEditForm
|
form = forms.RegionBulkEditForm
|
||||||
|
|
||||||
@ -179,7 +179,7 @@ class RegionBulkDeleteView(generic.BulkDeleteView):
|
|||||||
'site_count',
|
'site_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.RegionFilterSet
|
filterset = filtersets.RegionFilterSet
|
||||||
table = tables.RegionTable
|
table = tables.RegionTable
|
||||||
|
|
||||||
|
|
||||||
@ -195,7 +195,7 @@ class SiteGroupListView(generic.ObjectListView):
|
|||||||
'site_count',
|
'site_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.SiteGroupFilterSet
|
filterset = filtersets.SiteGroupFilterSet
|
||||||
filterset_form = forms.SiteGroupFilterForm
|
filterset_form = forms.SiteGroupFilterForm
|
||||||
table = tables.SiteGroupTable
|
table = tables.SiteGroupTable
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ class SiteGroupBulkEditView(generic.BulkEditView):
|
|||||||
'site_count',
|
'site_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.SiteGroupFilterSet
|
filterset = filtersets.SiteGroupFilterSet
|
||||||
table = tables.SiteGroupTable
|
table = tables.SiteGroupTable
|
||||||
form = forms.SiteGroupBulkEditForm
|
form = forms.SiteGroupBulkEditForm
|
||||||
|
|
||||||
@ -264,7 +264,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
'site_count',
|
'site_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.SiteGroupFilterSet
|
filterset = filtersets.SiteGroupFilterSet
|
||||||
table = tables.SiteGroupTable
|
table = tables.SiteGroupTable
|
||||||
|
|
||||||
|
|
||||||
@ -274,7 +274,7 @@ class SiteGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class SiteListView(generic.ObjectListView):
|
class SiteListView(generic.ObjectListView):
|
||||||
queryset = Site.objects.all()
|
queryset = Site.objects.all()
|
||||||
filterset = filters.SiteFilterSet
|
filterset = filtersets.SiteFilterSet
|
||||||
filterset_form = forms.SiteFilterForm
|
filterset_form = forms.SiteFilterForm
|
||||||
table = tables.SiteTable
|
table = tables.SiteTable
|
||||||
|
|
||||||
@ -329,14 +329,14 @@ class SiteBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class SiteBulkEditView(generic.BulkEditView):
|
class SiteBulkEditView(generic.BulkEditView):
|
||||||
queryset = Site.objects.prefetch_related('region', 'tenant')
|
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||||
filterset = filters.SiteFilterSet
|
filterset = filtersets.SiteFilterSet
|
||||||
table = tables.SiteTable
|
table = tables.SiteTable
|
||||||
form = forms.SiteBulkEditForm
|
form = forms.SiteBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class SiteBulkDeleteView(generic.BulkDeleteView):
|
class SiteBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Site.objects.prefetch_related('region', 'tenant')
|
queryset = Site.objects.prefetch_related('region', 'tenant')
|
||||||
filterset = filters.SiteFilterSet
|
filterset = filtersets.SiteFilterSet
|
||||||
table = tables.SiteTable
|
table = tables.SiteTable
|
||||||
|
|
||||||
|
|
||||||
@ -358,7 +358,7 @@ class LocationListView(generic.ObjectListView):
|
|||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
)
|
)
|
||||||
filterset = filters.LocationFilterSet
|
filterset = filtersets.LocationFilterSet
|
||||||
filterset_form = forms.LocationFilterForm
|
filterset_form = forms.LocationFilterForm
|
||||||
table = tables.LocationTable
|
table = tables.LocationTable
|
||||||
|
|
||||||
@ -417,7 +417,7 @@ class LocationBulkEditView(generic.BulkEditView):
|
|||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
).prefetch_related('site')
|
).prefetch_related('site')
|
||||||
filterset = filters.LocationFilterSet
|
filterset = filtersets.LocationFilterSet
|
||||||
table = tables.LocationTable
|
table = tables.LocationTable
|
||||||
form = forms.LocationBulkEditForm
|
form = forms.LocationBulkEditForm
|
||||||
|
|
||||||
@ -430,7 +430,7 @@ class LocationBulkDeleteView(generic.BulkDeleteView):
|
|||||||
'rack_count',
|
'rack_count',
|
||||||
cumulative=True
|
cumulative=True
|
||||||
).prefetch_related('site')
|
).prefetch_related('site')
|
||||||
filterset = filters.LocationFilterSet
|
filterset = filtersets.LocationFilterSet
|
||||||
table = tables.LocationTable
|
table = tables.LocationTable
|
||||||
|
|
||||||
|
|
||||||
@ -481,7 +481,7 @@ class RackRoleBulkEditView(generic.BulkEditView):
|
|||||||
queryset = RackRole.objects.annotate(
|
queryset = RackRole.objects.annotate(
|
||||||
rack_count=count_related(Rack, 'role')
|
rack_count=count_related(Rack, 'role')
|
||||||
)
|
)
|
||||||
filterset = filters.RackRoleFilterSet
|
filterset = filtersets.RackRoleFilterSet
|
||||||
table = tables.RackRoleTable
|
table = tables.RackRoleTable
|
||||||
form = forms.RackRoleBulkEditForm
|
form = forms.RackRoleBulkEditForm
|
||||||
|
|
||||||
@ -503,7 +503,7 @@ class RackListView(generic.ObjectListView):
|
|||||||
).annotate(
|
).annotate(
|
||||||
device_count=count_related(Device, 'rack')
|
device_count=count_related(Device, 'rack')
|
||||||
)
|
)
|
||||||
filterset = filters.RackFilterSet
|
filterset = filtersets.RackFilterSet
|
||||||
filterset_form = forms.RackFilterForm
|
filterset_form = forms.RackFilterForm
|
||||||
table = tables.RackDetailTable
|
table = tables.RackDetailTable
|
||||||
|
|
||||||
@ -516,7 +516,7 @@ class RackElevationListView(generic.ObjectListView):
|
|||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
|
||||||
racks = filters.RackFilterSet(request.GET, self.queryset).qs
|
racks = filtersets.RackFilterSet(request.GET, self.queryset).qs
|
||||||
total_count = racks.count()
|
total_count = racks.count()
|
||||||
|
|
||||||
# Determine ordering
|
# Determine ordering
|
||||||
@ -605,14 +605,14 @@ class RackBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class RackBulkEditView(generic.BulkEditView):
|
class RackBulkEditView(generic.BulkEditView):
|
||||||
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
|
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
|
||||||
filterset = filters.RackFilterSet
|
filterset = filtersets.RackFilterSet
|
||||||
table = tables.RackTable
|
table = tables.RackTable
|
||||||
form = forms.RackBulkEditForm
|
form = forms.RackBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class RackBulkDeleteView(generic.BulkDeleteView):
|
class RackBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
|
queryset = Rack.objects.prefetch_related('site', 'location', 'tenant', 'role')
|
||||||
filterset = filters.RackFilterSet
|
filterset = filtersets.RackFilterSet
|
||||||
table = tables.RackTable
|
table = tables.RackTable
|
||||||
|
|
||||||
|
|
||||||
@ -622,7 +622,7 @@ class RackBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class RackReservationListView(generic.ObjectListView):
|
class RackReservationListView(generic.ObjectListView):
|
||||||
queryset = RackReservation.objects.all()
|
queryset = RackReservation.objects.all()
|
||||||
filterset = filters.RackReservationFilterSet
|
filterset = filtersets.RackReservationFilterSet
|
||||||
filterset_form = forms.RackReservationFilterForm
|
filterset_form = forms.RackReservationFilterForm
|
||||||
table = tables.RackReservationTable
|
table = tables.RackReservationTable
|
||||||
|
|
||||||
@ -665,14 +665,14 @@ class RackReservationImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class RackReservationBulkEditView(generic.BulkEditView):
|
class RackReservationBulkEditView(generic.BulkEditView):
|
||||||
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
||||||
filterset = filters.RackReservationFilterSet
|
filterset = filtersets.RackReservationFilterSet
|
||||||
table = tables.RackReservationTable
|
table = tables.RackReservationTable
|
||||||
form = forms.RackReservationBulkEditForm
|
form = forms.RackReservationBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class RackReservationBulkDeleteView(generic.BulkDeleteView):
|
class RackReservationBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
queryset = RackReservation.objects.prefetch_related('rack', 'user')
|
||||||
filterset = filters.RackReservationFilterSet
|
filterset = filtersets.RackReservationFilterSet
|
||||||
table = tables.RackReservationTable
|
table = tables.RackReservationTable
|
||||||
|
|
||||||
|
|
||||||
@ -695,6 +695,8 @@ class ManufacturerView(generic.ObjectView):
|
|||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
devicetypes = DeviceType.objects.restrict(request.user, 'view').filter(
|
devicetypes = DeviceType.objects.restrict(request.user, 'view').filter(
|
||||||
manufacturer=instance
|
manufacturer=instance
|
||||||
|
).annotate(
|
||||||
|
instance_count=count_related(Device, 'device_type')
|
||||||
)
|
)
|
||||||
|
|
||||||
devicetypes_table = tables.DeviceTypeTable(devicetypes)
|
devicetypes_table = tables.DeviceTypeTable(devicetypes)
|
||||||
@ -725,7 +727,7 @@ class ManufacturerBulkEditView(generic.BulkEditView):
|
|||||||
queryset = Manufacturer.objects.annotate(
|
queryset = Manufacturer.objects.annotate(
|
||||||
devicetype_count=count_related(DeviceType, 'manufacturer')
|
devicetype_count=count_related(DeviceType, 'manufacturer')
|
||||||
)
|
)
|
||||||
filterset = filters.ManufacturerFilterSet
|
filterset = filtersets.ManufacturerFilterSet
|
||||||
table = tables.ManufacturerTable
|
table = tables.ManufacturerTable
|
||||||
form = forms.ManufacturerBulkEditForm
|
form = forms.ManufacturerBulkEditForm
|
||||||
|
|
||||||
@ -745,7 +747,7 @@ class DeviceTypeListView(generic.ObjectListView):
|
|||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=count_related(Device, 'device_type')
|
instance_count=count_related(Device, 'device_type')
|
||||||
)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filtersets.DeviceTypeFilterSet
|
||||||
filterset_form = forms.DeviceTypeFilterForm
|
filterset_form = forms.DeviceTypeFilterForm
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
|
|
||||||
@ -851,7 +853,7 @@ class DeviceTypeBulkEditView(generic.BulkEditView):
|
|||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=count_related(Device, 'device_type')
|
instance_count=count_related(Device, 'device_type')
|
||||||
)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filtersets.DeviceTypeFilterSet
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
form = forms.DeviceTypeBulkEditForm
|
form = forms.DeviceTypeBulkEditForm
|
||||||
|
|
||||||
@ -860,7 +862,7 @@ class DeviceTypeBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(
|
||||||
instance_count=count_related(Device, 'device_type')
|
instance_count=count_related(Device, 'device_type')
|
||||||
)
|
)
|
||||||
filterset = filters.DeviceTypeFilterSet
|
filterset = filtersets.DeviceTypeFilterSet
|
||||||
table = tables.DeviceTypeTable
|
table = tables.DeviceTypeTable
|
||||||
|
|
||||||
|
|
||||||
@ -1193,7 +1195,7 @@ class DeviceRoleBulkEditView(generic.BulkEditView):
|
|||||||
device_count=count_related(Device, 'device_role'),
|
device_count=count_related(Device, 'device_role'),
|
||||||
vm_count=count_related(VirtualMachine, 'role')
|
vm_count=count_related(VirtualMachine, 'role')
|
||||||
)
|
)
|
||||||
filterset = filters.DeviceRoleFilterSet
|
filterset = filtersets.DeviceRoleFilterSet
|
||||||
table = tables.DeviceRoleTable
|
table = tables.DeviceRoleTable
|
||||||
form = forms.DeviceRoleBulkEditForm
|
form = forms.DeviceRoleBulkEditForm
|
||||||
|
|
||||||
@ -1252,7 +1254,7 @@ class PlatformBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class PlatformBulkEditView(generic.BulkEditView):
|
class PlatformBulkEditView(generic.BulkEditView):
|
||||||
queryset = Platform.objects.all()
|
queryset = Platform.objects.all()
|
||||||
filterset = filters.PlatformFilterSet
|
filterset = filtersets.PlatformFilterSet
|
||||||
table = tables.PlatformTable
|
table = tables.PlatformTable
|
||||||
form = forms.PlatformBulkEditForm
|
form = forms.PlatformBulkEditForm
|
||||||
|
|
||||||
@ -1268,7 +1270,7 @@ class PlatformBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class DeviceListView(generic.ObjectListView):
|
class DeviceListView(generic.ObjectListView):
|
||||||
queryset = Device.objects.all()
|
queryset = Device.objects.all()
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
filterset_form = forms.DeviceFilterForm
|
filterset_form = forms.DeviceFilterForm
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
template_name = 'dcim/device_list.html'
|
template_name = 'dcim/device_list.html'
|
||||||
@ -1408,7 +1410,7 @@ class DeviceInterfacesView(generic.ObjectView):
|
|||||||
template_name = 'dcim/device/interfaces.html'
|
template_name = 'dcim/device/interfaces.html'
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
interfaces = instance.vc_interfaces(if_master=True).restrict(request.user, 'view').prefetch_related(
|
interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||||
Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
|
Prefetch('ip_addresses', queryset=IPAddress.objects.restrict(request.user)),
|
||||||
Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)),
|
Prefetch('member_interfaces', queryset=Interface.objects.restrict(request.user)),
|
||||||
'lag', 'cable', '_path__destination', 'tags',
|
'lag', 'cable', '_path__destination', 'tags',
|
||||||
@ -1530,7 +1532,7 @@ class DeviceLLDPNeighborsView(generic.ObjectView):
|
|||||||
template_name = 'dcim/device/lldp_neighbors.html'
|
template_name = 'dcim/device/lldp_neighbors.html'
|
||||||
|
|
||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
interfaces = instance.vc_interfaces(if_master=True).restrict(request.user, 'view').prefetch_related(
|
interfaces = instance.vc_interfaces().restrict(request.user, 'view').prefetch_related(
|
||||||
'_path__destination'
|
'_path__destination'
|
||||||
).exclude(
|
).exclude(
|
||||||
type__in=NONCONNECTABLE_IFACE_TYPES
|
type__in=NONCONNECTABLE_IFACE_TYPES
|
||||||
@ -1603,14 +1605,14 @@ class ChildDeviceBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class DeviceBulkEditView(generic.BulkEditView):
|
class DeviceBulkEditView(generic.BulkEditView):
|
||||||
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
form = forms.DeviceBulkEditForm
|
form = forms.DeviceBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkDeleteView(generic.BulkDeleteView):
|
class DeviceBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
|
|
||||||
|
|
||||||
@ -1620,7 +1622,7 @@ class DeviceBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConsolePortListView(generic.ObjectListView):
|
class ConsolePortListView(generic.ObjectListView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
filterset = filters.ConsolePortFilterSet
|
filterset = filtersets.ConsolePortFilterSet
|
||||||
filterset_form = forms.ConsolePortFilterForm
|
filterset_form = forms.ConsolePortFilterForm
|
||||||
table = tables.ConsolePortTable
|
table = tables.ConsolePortTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -1655,7 +1657,7 @@ class ConsolePortBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class ConsolePortBulkEditView(generic.BulkEditView):
|
class ConsolePortBulkEditView(generic.BulkEditView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
filterset = filters.ConsolePortFilterSet
|
filterset = filtersets.ConsolePortFilterSet
|
||||||
table = tables.ConsolePortTable
|
table = tables.ConsolePortTable
|
||||||
form = forms.ConsolePortBulkEditForm
|
form = forms.ConsolePortBulkEditForm
|
||||||
|
|
||||||
@ -1670,7 +1672,7 @@ class ConsolePortBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class ConsolePortBulkDeleteView(generic.BulkDeleteView):
|
class ConsolePortBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
filterset = filters.ConsolePortFilterSet
|
filterset = filtersets.ConsolePortFilterSet
|
||||||
table = tables.ConsolePortTable
|
table = tables.ConsolePortTable
|
||||||
|
|
||||||
|
|
||||||
@ -1680,7 +1682,7 @@ class ConsolePortBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConsoleServerPortListView(generic.ObjectListView):
|
class ConsoleServerPortListView(generic.ObjectListView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
filterset = filters.ConsoleServerPortFilterSet
|
filterset = filtersets.ConsoleServerPortFilterSet
|
||||||
filterset_form = forms.ConsoleServerPortFilterForm
|
filterset_form = forms.ConsoleServerPortFilterForm
|
||||||
table = tables.ConsoleServerPortTable
|
table = tables.ConsoleServerPortTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -1715,7 +1717,7 @@ class ConsoleServerPortBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class ConsoleServerPortBulkEditView(generic.BulkEditView):
|
class ConsoleServerPortBulkEditView(generic.BulkEditView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
filterset = filters.ConsoleServerPortFilterSet
|
filterset = filtersets.ConsoleServerPortFilterSet
|
||||||
table = tables.ConsoleServerPortTable
|
table = tables.ConsoleServerPortTable
|
||||||
form = forms.ConsoleServerPortBulkEditForm
|
form = forms.ConsoleServerPortBulkEditForm
|
||||||
|
|
||||||
@ -1730,7 +1732,7 @@ class ConsoleServerPortBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
|
class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
filterset = filters.ConsoleServerPortFilterSet
|
filterset = filtersets.ConsoleServerPortFilterSet
|
||||||
table = tables.ConsoleServerPortTable
|
table = tables.ConsoleServerPortTable
|
||||||
|
|
||||||
|
|
||||||
@ -1740,7 +1742,7 @@ class ConsoleServerPortBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerPortListView(generic.ObjectListView):
|
class PowerPortListView(generic.ObjectListView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
filterset = filters.PowerPortFilterSet
|
filterset = filtersets.PowerPortFilterSet
|
||||||
filterset_form = forms.PowerPortFilterForm
|
filterset_form = forms.PowerPortFilterForm
|
||||||
table = tables.PowerPortTable
|
table = tables.PowerPortTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -1775,7 +1777,7 @@ class PowerPortBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class PowerPortBulkEditView(generic.BulkEditView):
|
class PowerPortBulkEditView(generic.BulkEditView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
filterset = filters.PowerPortFilterSet
|
filterset = filtersets.PowerPortFilterSet
|
||||||
table = tables.PowerPortTable
|
table = tables.PowerPortTable
|
||||||
form = forms.PowerPortBulkEditForm
|
form = forms.PowerPortBulkEditForm
|
||||||
|
|
||||||
@ -1790,7 +1792,7 @@ class PowerPortBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class PowerPortBulkDeleteView(generic.BulkDeleteView):
|
class PowerPortBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
filterset = filters.PowerPortFilterSet
|
filterset = filtersets.PowerPortFilterSet
|
||||||
table = tables.PowerPortTable
|
table = tables.PowerPortTable
|
||||||
|
|
||||||
|
|
||||||
@ -1800,7 +1802,7 @@ class PowerPortBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerOutletListView(generic.ObjectListView):
|
class PowerOutletListView(generic.ObjectListView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
filterset = filters.PowerOutletFilterSet
|
filterset = filtersets.PowerOutletFilterSet
|
||||||
filterset_form = forms.PowerOutletFilterForm
|
filterset_form = forms.PowerOutletFilterForm
|
||||||
table = tables.PowerOutletTable
|
table = tables.PowerOutletTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -1835,7 +1837,7 @@ class PowerOutletBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class PowerOutletBulkEditView(generic.BulkEditView):
|
class PowerOutletBulkEditView(generic.BulkEditView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
filterset = filters.PowerOutletFilterSet
|
filterset = filtersets.PowerOutletFilterSet
|
||||||
table = tables.PowerOutletTable
|
table = tables.PowerOutletTable
|
||||||
form = forms.PowerOutletBulkEditForm
|
form = forms.PowerOutletBulkEditForm
|
||||||
|
|
||||||
@ -1850,7 +1852,7 @@ class PowerOutletBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class PowerOutletBulkDeleteView(generic.BulkDeleteView):
|
class PowerOutletBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
filterset = filters.PowerOutletFilterSet
|
filterset = filtersets.PowerOutletFilterSet
|
||||||
table = tables.PowerOutletTable
|
table = tables.PowerOutletTable
|
||||||
|
|
||||||
|
|
||||||
@ -1860,7 +1862,7 @@ class PowerOutletBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class InterfaceListView(generic.ObjectListView):
|
class InterfaceListView(generic.ObjectListView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
filterset = filters.InterfaceFilterSet
|
filterset = filtersets.InterfaceFilterSet
|
||||||
filterset_form = forms.InterfaceFilterForm
|
filterset_form = forms.InterfaceFilterForm
|
||||||
table = tables.InterfaceTable
|
table = tables.InterfaceTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -1997,7 +1999,7 @@ class InterfaceBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class InterfaceBulkEditView(generic.BulkEditView):
|
class InterfaceBulkEditView(generic.BulkEditView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
filterset = filters.InterfaceFilterSet
|
filterset = filtersets.InterfaceFilterSet
|
||||||
table = tables.InterfaceTable
|
table = tables.InterfaceTable
|
||||||
form = forms.InterfaceBulkEditForm
|
form = forms.InterfaceBulkEditForm
|
||||||
|
|
||||||
@ -2012,7 +2014,7 @@ class InterfaceBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class InterfaceBulkDeleteView(generic.BulkDeleteView):
|
class InterfaceBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
filterset = filters.InterfaceFilterSet
|
filterset = filtersets.InterfaceFilterSet
|
||||||
table = tables.InterfaceTable
|
table = tables.InterfaceTable
|
||||||
|
|
||||||
|
|
||||||
@ -2022,7 +2024,7 @@ class InterfaceBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class FrontPortListView(generic.ObjectListView):
|
class FrontPortListView(generic.ObjectListView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
filterset = filters.FrontPortFilterSet
|
filterset = filtersets.FrontPortFilterSet
|
||||||
filterset_form = forms.FrontPortFilterForm
|
filterset_form = forms.FrontPortFilterForm
|
||||||
table = tables.FrontPortTable
|
table = tables.FrontPortTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -2057,7 +2059,7 @@ class FrontPortBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class FrontPortBulkEditView(generic.BulkEditView):
|
class FrontPortBulkEditView(generic.BulkEditView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
filterset = filters.FrontPortFilterSet
|
filterset = filtersets.FrontPortFilterSet
|
||||||
table = tables.FrontPortTable
|
table = tables.FrontPortTable
|
||||||
form = forms.FrontPortBulkEditForm
|
form = forms.FrontPortBulkEditForm
|
||||||
|
|
||||||
@ -2072,7 +2074,7 @@ class FrontPortBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class FrontPortBulkDeleteView(generic.BulkDeleteView):
|
class FrontPortBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
filterset = filters.FrontPortFilterSet
|
filterset = filtersets.FrontPortFilterSet
|
||||||
table = tables.FrontPortTable
|
table = tables.FrontPortTable
|
||||||
|
|
||||||
|
|
||||||
@ -2082,7 +2084,7 @@ class FrontPortBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class RearPortListView(generic.ObjectListView):
|
class RearPortListView(generic.ObjectListView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
filterset = filters.RearPortFilterSet
|
filterset = filtersets.RearPortFilterSet
|
||||||
filterset_form = forms.RearPortFilterForm
|
filterset_form = forms.RearPortFilterForm
|
||||||
table = tables.RearPortTable
|
table = tables.RearPortTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -2117,7 +2119,7 @@ class RearPortBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class RearPortBulkEditView(generic.BulkEditView):
|
class RearPortBulkEditView(generic.BulkEditView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
filterset = filters.RearPortFilterSet
|
filterset = filtersets.RearPortFilterSet
|
||||||
table = tables.RearPortTable
|
table = tables.RearPortTable
|
||||||
form = forms.RearPortBulkEditForm
|
form = forms.RearPortBulkEditForm
|
||||||
|
|
||||||
@ -2132,7 +2134,7 @@ class RearPortBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class RearPortBulkDeleteView(generic.BulkDeleteView):
|
class RearPortBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
filterset = filters.RearPortFilterSet
|
filterset = filtersets.RearPortFilterSet
|
||||||
table = tables.RearPortTable
|
table = tables.RearPortTable
|
||||||
|
|
||||||
|
|
||||||
@ -2142,7 +2144,7 @@ class RearPortBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class DeviceBayListView(generic.ObjectListView):
|
class DeviceBayListView(generic.ObjectListView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
filterset = filters.DeviceBayFilterSet
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
filterset_form = forms.DeviceBayFilterForm
|
filterset_form = forms.DeviceBayFilterForm
|
||||||
table = tables.DeviceBayTable
|
table = tables.DeviceBayTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -2242,7 +2244,7 @@ class DeviceBayBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class DeviceBayBulkEditView(generic.BulkEditView):
|
class DeviceBayBulkEditView(generic.BulkEditView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
filterset = filters.DeviceBayFilterSet
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
table = tables.DeviceBayTable
|
table = tables.DeviceBayTable
|
||||||
form = forms.DeviceBayBulkEditForm
|
form = forms.DeviceBayBulkEditForm
|
||||||
|
|
||||||
@ -2253,7 +2255,7 @@ class DeviceBayBulkRenameView(generic.BulkRenameView):
|
|||||||
|
|
||||||
class DeviceBayBulkDeleteView(generic.BulkDeleteView):
|
class DeviceBayBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
filterset = filters.DeviceBayFilterSet
|
filterset = filtersets.DeviceBayFilterSet
|
||||||
table = tables.DeviceBayTable
|
table = tables.DeviceBayTable
|
||||||
|
|
||||||
|
|
||||||
@ -2263,7 +2265,7 @@ class DeviceBayBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class InventoryItemListView(generic.ObjectListView):
|
class InventoryItemListView(generic.ObjectListView):
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
filterset = filters.InventoryItemFilterSet
|
filterset = filtersets.InventoryItemFilterSet
|
||||||
filterset_form = forms.InventoryItemFilterForm
|
filterset_form = forms.InventoryItemFilterForm
|
||||||
table = tables.InventoryItemTable
|
table = tables.InventoryItemTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -2297,7 +2299,7 @@ class InventoryItemBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class InventoryItemBulkEditView(generic.BulkEditView):
|
class InventoryItemBulkEditView(generic.BulkEditView):
|
||||||
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
|
queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
|
||||||
filterset = filters.InventoryItemFilterSet
|
filterset = filtersets.InventoryItemFilterSet
|
||||||
table = tables.InventoryItemTable
|
table = tables.InventoryItemTable
|
||||||
form = forms.InventoryItemBulkEditForm
|
form = forms.InventoryItemBulkEditForm
|
||||||
|
|
||||||
@ -2322,7 +2324,7 @@ class DeviceBulkAddConsolePortView(generic.BulkComponentCreateView):
|
|||||||
form = forms.ConsolePortBulkCreateForm
|
form = forms.ConsolePortBulkCreateForm
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
model_form = forms.ConsolePortForm
|
model_form = forms.ConsolePortForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2333,7 +2335,7 @@ class DeviceBulkAddConsoleServerPortView(generic.BulkComponentCreateView):
|
|||||||
form = forms.ConsoleServerPortBulkCreateForm
|
form = forms.ConsoleServerPortBulkCreateForm
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
model_form = forms.ConsoleServerPortForm
|
model_form = forms.ConsoleServerPortForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2344,7 +2346,7 @@ class DeviceBulkAddPowerPortView(generic.BulkComponentCreateView):
|
|||||||
form = forms.PowerPortBulkCreateForm
|
form = forms.PowerPortBulkCreateForm
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
model_form = forms.PowerPortForm
|
model_form = forms.PowerPortForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2355,7 +2357,7 @@ class DeviceBulkAddPowerOutletView(generic.BulkComponentCreateView):
|
|||||||
form = forms.PowerOutletBulkCreateForm
|
form = forms.PowerOutletBulkCreateForm
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
model_form = forms.PowerOutletForm
|
model_form = forms.PowerOutletForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2366,7 +2368,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView):
|
|||||||
form = forms.InterfaceBulkCreateForm
|
form = forms.InterfaceBulkCreateForm
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
model_form = forms.InterfaceForm
|
model_form = forms.InterfaceForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2377,7 +2379,7 @@ class DeviceBulkAddInterfaceView(generic.BulkComponentCreateView):
|
|||||||
# form = forms.FrontPortBulkCreateForm
|
# form = forms.FrontPortBulkCreateForm
|
||||||
# queryset = FrontPort.objects.all()
|
# queryset = FrontPort.objects.all()
|
||||||
# model_form = forms.FrontPortForm
|
# model_form = forms.FrontPortForm
|
||||||
# filterset = filters.DeviceFilterSet
|
# filterset = filtersets.DeviceFilterSet
|
||||||
# table = tables.DeviceTable
|
# table = tables.DeviceTable
|
||||||
# default_return_url = 'dcim:device_list'
|
# default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2388,7 +2390,7 @@ class DeviceBulkAddRearPortView(generic.BulkComponentCreateView):
|
|||||||
form = forms.RearPortBulkCreateForm
|
form = forms.RearPortBulkCreateForm
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
model_form = forms.RearPortForm
|
model_form = forms.RearPortForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2399,7 +2401,7 @@ class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
|||||||
form = forms.DeviceBayBulkCreateForm
|
form = forms.DeviceBayBulkCreateForm
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
model_form = forms.DeviceBayForm
|
model_form = forms.DeviceBayForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2410,7 +2412,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
|
|||||||
form = forms.InventoryItemBulkCreateForm
|
form = forms.InventoryItemBulkCreateForm
|
||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
model_form = forms.InventoryItemForm
|
model_form = forms.InventoryItemForm
|
||||||
filterset = filters.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
|
|
||||||
@ -2421,7 +2423,7 @@ class DeviceBulkAddInventoryItemView(generic.BulkComponentCreateView):
|
|||||||
|
|
||||||
class CableListView(generic.ObjectListView):
|
class CableListView(generic.ObjectListView):
|
||||||
queryset = Cable.objects.all()
|
queryset = Cable.objects.all()
|
||||||
filterset = filters.CableFilterSet
|
filterset = filtersets.CableFilterSet
|
||||||
filterset_form = forms.CableFilterForm
|
filterset_form = forms.CableFilterForm
|
||||||
table = tables.CableTable
|
table = tables.CableTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -2554,14 +2556,14 @@ class CableBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class CableBulkEditView(generic.BulkEditView):
|
class CableBulkEditView(generic.BulkEditView):
|
||||||
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
|
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
|
||||||
filterset = filters.CableFilterSet
|
filterset = filtersets.CableFilterSet
|
||||||
table = tables.CableTable
|
table = tables.CableTable
|
||||||
form = forms.CableBulkEditForm
|
form = forms.CableBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class CableBulkDeleteView(generic.BulkDeleteView):
|
class CableBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
|
queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
|
||||||
filterset = filters.CableFilterSet
|
filterset = filtersets.CableFilterSet
|
||||||
table = tables.CableTable
|
table = tables.CableTable
|
||||||
|
|
||||||
|
|
||||||
@ -2571,7 +2573,7 @@ class CableBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConsoleConnectionsListView(generic.ObjectListView):
|
class ConsoleConnectionsListView(generic.ObjectListView):
|
||||||
queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device')
|
queryset = ConsolePort.objects.filter(_path__isnull=False).order_by('device')
|
||||||
filterset = filters.ConsoleConnectionFilterSet
|
filterset = filtersets.ConsoleConnectionFilterSet
|
||||||
filterset_form = forms.ConsoleConnectionFilterForm
|
filterset_form = forms.ConsoleConnectionFilterForm
|
||||||
table = tables.ConsoleConnectionTable
|
table = tables.ConsoleConnectionTable
|
||||||
template_name = 'dcim/connections_list.html'
|
template_name = 'dcim/connections_list.html'
|
||||||
@ -2601,7 +2603,7 @@ class ConsoleConnectionsListView(generic.ObjectListView):
|
|||||||
|
|
||||||
class PowerConnectionsListView(generic.ObjectListView):
|
class PowerConnectionsListView(generic.ObjectListView):
|
||||||
queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device')
|
queryset = PowerPort.objects.filter(_path__isnull=False).order_by('device')
|
||||||
filterset = filters.PowerConnectionFilterSet
|
filterset = filtersets.PowerConnectionFilterSet
|
||||||
filterset_form = forms.PowerConnectionFilterForm
|
filterset_form = forms.PowerConnectionFilterForm
|
||||||
table = tables.PowerConnectionTable
|
table = tables.PowerConnectionTable
|
||||||
template_name = 'dcim/connections_list.html'
|
template_name = 'dcim/connections_list.html'
|
||||||
@ -2635,7 +2637,7 @@ class InterfaceConnectionsListView(generic.ObjectListView):
|
|||||||
_path__isnull=False,
|
_path__isnull=False,
|
||||||
pk__lt=F('_path__destination_id')
|
pk__lt=F('_path__destination_id')
|
||||||
).order_by('device')
|
).order_by('device')
|
||||||
filterset = filters.InterfaceConnectionFilterSet
|
filterset = filtersets.InterfaceConnectionFilterSet
|
||||||
filterset_form = forms.InterfaceConnectionFilterForm
|
filterset_form = forms.InterfaceConnectionFilterForm
|
||||||
table = tables.InterfaceConnectionTable
|
table = tables.InterfaceConnectionTable
|
||||||
template_name = 'dcim/connections_list.html'
|
template_name = 'dcim/connections_list.html'
|
||||||
@ -2674,7 +2676,7 @@ class VirtualChassisListView(generic.ObjectListView):
|
|||||||
member_count=count_related(Device, 'virtual_chassis')
|
member_count=count_related(Device, 'virtual_chassis')
|
||||||
)
|
)
|
||||||
table = tables.VirtualChassisTable
|
table = tables.VirtualChassisTable
|
||||||
filterset = filters.VirtualChassisFilterSet
|
filterset = filtersets.VirtualChassisFilterSet
|
||||||
filterset_form = forms.VirtualChassisFilterForm
|
filterset_form = forms.VirtualChassisFilterForm
|
||||||
|
|
||||||
|
|
||||||
@ -2882,14 +2884,14 @@ class VirtualChassisBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class VirtualChassisBulkEditView(generic.BulkEditView):
|
class VirtualChassisBulkEditView(generic.BulkEditView):
|
||||||
queryset = VirtualChassis.objects.all()
|
queryset = VirtualChassis.objects.all()
|
||||||
filterset = filters.VirtualChassisFilterSet
|
filterset = filtersets.VirtualChassisFilterSet
|
||||||
table = tables.VirtualChassisTable
|
table = tables.VirtualChassisTable
|
||||||
form = forms.VirtualChassisBulkEditForm
|
form = forms.VirtualChassisBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
class VirtualChassisBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VirtualChassis.objects.all()
|
queryset = VirtualChassis.objects.all()
|
||||||
filterset = filters.VirtualChassisFilterSet
|
filterset = filtersets.VirtualChassisFilterSet
|
||||||
table = tables.VirtualChassisTable
|
table = tables.VirtualChassisTable
|
||||||
|
|
||||||
|
|
||||||
@ -2903,7 +2905,7 @@ class PowerPanelListView(generic.ObjectListView):
|
|||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
||||||
)
|
)
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filtersets.PowerPanelFilterSet
|
||||||
filterset_form = forms.PowerPanelFilterForm
|
filterset_form = forms.PowerPanelFilterForm
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
|
|
||||||
@ -2943,7 +2945,7 @@ class PowerPanelBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class PowerPanelBulkEditView(generic.BulkEditView):
|
class PowerPanelBulkEditView(generic.BulkEditView):
|
||||||
queryset = PowerPanel.objects.prefetch_related('site', 'location')
|
queryset = PowerPanel.objects.prefetch_related('site', 'location')
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filtersets.PowerPanelFilterSet
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
form = forms.PowerPanelBulkEditForm
|
form = forms.PowerPanelBulkEditForm
|
||||||
|
|
||||||
@ -2954,7 +2956,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
|||||||
).annotate(
|
).annotate(
|
||||||
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
powerfeed_count=count_related(PowerFeed, 'power_panel')
|
||||||
)
|
)
|
||||||
filterset = filters.PowerPanelFilterSet
|
filterset = filtersets.PowerPanelFilterSet
|
||||||
table = tables.PowerPanelTable
|
table = tables.PowerPanelTable
|
||||||
|
|
||||||
|
|
||||||
@ -2964,7 +2966,7 @@ class PowerPanelBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerFeedListView(generic.ObjectListView):
|
class PowerFeedListView(generic.ObjectListView):
|
||||||
queryset = PowerFeed.objects.all()
|
queryset = PowerFeed.objects.all()
|
||||||
filterset = filters.PowerFeedFilterSet
|
filterset = filtersets.PowerFeedFilterSet
|
||||||
filterset_form = forms.PowerFeedFilterForm
|
filterset_form = forms.PowerFeedFilterForm
|
||||||
table = tables.PowerFeedTable
|
table = tables.PowerFeedTable
|
||||||
|
|
||||||
@ -2990,7 +2992,7 @@ class PowerFeedBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class PowerFeedBulkEditView(generic.BulkEditView):
|
class PowerFeedBulkEditView(generic.BulkEditView):
|
||||||
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
||||||
filterset = filters.PowerFeedFilterSet
|
filterset = filtersets.PowerFeedFilterSet
|
||||||
table = tables.PowerFeedTable
|
table = tables.PowerFeedTable
|
||||||
form = forms.PowerFeedBulkEditForm
|
form = forms.PowerFeedBulkEditForm
|
||||||
|
|
||||||
@ -3001,5 +3003,5 @@ class PowerFeedBulkDisconnectView(BulkDisconnectView):
|
|||||||
|
|
||||||
class PowerFeedBulkDeleteView(generic.BulkDeleteView):
|
class PowerFeedBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
|
||||||
filterset = filters.PowerFeedFilterSet
|
filterset = filtersets.PowerFeedFilterSet
|
||||||
table = tables.PowerFeedTable
|
table = tables.PowerFeedTable
|
||||||
|
@ -453,12 +453,7 @@ class ObjectChangeSerializer(BaseModelSerializer):
|
|||||||
|
|
||||||
class ContentTypeSerializer(BaseModelSerializer):
|
class ContentTypeSerializer(BaseModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='extras-api:contenttype-detail')
|
||||||
display_name = serializers.SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ContentType
|
model = ContentType
|
||||||
fields = ['id', 'url', 'display', 'app_label', 'model', 'display_name']
|
fields = ['id', 'url', 'display', 'app_label', 'model']
|
||||||
|
|
||||||
@swagger_serializer_method(serializer_or_field=serializers.CharField)
|
|
||||||
def get_display_name(self, obj):
|
|
||||||
return obj.app_labeled_name
|
|
||||||
|
@ -9,7 +9,7 @@ from rest_framework.routers import APIRootView
|
|||||||
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
from rest_framework.viewsets import ReadOnlyModelViewSet, ViewSet
|
||||||
from rq import Worker
|
from rq import Worker
|
||||||
|
|
||||||
from extras import filters
|
from extras import filtersets
|
||||||
from extras.choices import JobResultStatusChoices
|
from extras.choices import JobResultStatusChoices
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from extras.models import CustomField
|
from extras.models import CustomField
|
||||||
@ -61,7 +61,7 @@ class WebhookViewSet(ModelViewSet):
|
|||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = Webhook.objects.all()
|
queryset = Webhook.objects.all()
|
||||||
serializer_class = serializers.WebhookSerializer
|
serializer_class = serializers.WebhookSerializer
|
||||||
filterset_class = filters.WebhookFilterSet
|
filterset_class = filtersets.WebhookFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -72,7 +72,7 @@ class CustomFieldViewSet(ModelViewSet):
|
|||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = CustomField.objects.all()
|
queryset = CustomField.objects.all()
|
||||||
serializer_class = serializers.CustomFieldSerializer
|
serializer_class = serializers.CustomFieldSerializer
|
||||||
filterset_class = filters.CustomFieldFilterSet
|
filterset_class = filtersets.CustomFieldFilterSet
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldModelViewSet(ModelViewSet):
|
class CustomFieldModelViewSet(ModelViewSet):
|
||||||
@ -101,7 +101,7 @@ class CustomLinkViewSet(ModelViewSet):
|
|||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = CustomLink.objects.all()
|
queryset = CustomLink.objects.all()
|
||||||
serializer_class = serializers.CustomLinkSerializer
|
serializer_class = serializers.CustomLinkSerializer
|
||||||
filterset_class = filters.CustomLinkFilterSet
|
filterset_class = filtersets.CustomLinkFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -112,7 +112,7 @@ class ExportTemplateViewSet(ModelViewSet):
|
|||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = ExportTemplate.objects.all()
|
queryset = ExportTemplate.objects.all()
|
||||||
serializer_class = serializers.ExportTemplateSerializer
|
serializer_class = serializers.ExportTemplateSerializer
|
||||||
filterset_class = filters.ExportTemplateFilterSet
|
filterset_class = filtersets.ExportTemplateFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -124,7 +124,7 @@ class TagViewSet(ModelViewSet):
|
|||||||
tagged_items=count_related(TaggedItem, 'tag')
|
tagged_items=count_related(TaggedItem, 'tag')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.TagSerializer
|
serializer_class = serializers.TagSerializer
|
||||||
filterset_class = filters.TagFilterSet
|
filterset_class = filtersets.TagFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -135,7 +135,7 @@ class ImageAttachmentViewSet(ModelViewSet):
|
|||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = ImageAttachment.objects.all()
|
queryset = ImageAttachment.objects.all()
|
||||||
serializer_class = serializers.ImageAttachmentSerializer
|
serializer_class = serializers.ImageAttachmentSerializer
|
||||||
filterset_class = filters.ImageAttachmentFilterSet
|
filterset_class = filtersets.ImageAttachmentFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -146,7 +146,7 @@ class JournalEntryViewSet(ModelViewSet):
|
|||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = JournalEntry.objects.all()
|
queryset = JournalEntry.objects.all()
|
||||||
serializer_class = serializers.JournalEntrySerializer
|
serializer_class = serializers.JournalEntrySerializer
|
||||||
filterset_class = filters.JournalEntryFilterSet
|
filterset_class = filtersets.JournalEntryFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -158,7 +158,7 @@ class ConfigContextViewSet(ModelViewSet):
|
|||||||
'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
|
'regions', 'site_groups', 'sites', 'roles', 'platforms', 'tenant_groups', 'tenants',
|
||||||
)
|
)
|
||||||
serializer_class = serializers.ConfigContextSerializer
|
serializer_class = serializers.ConfigContextSerializer
|
||||||
filterset_class = filters.ConfigContextFilterSet
|
filterset_class = filtersets.ConfigContextFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -358,7 +358,7 @@ class ObjectChangeViewSet(ReadOnlyModelViewSet):
|
|||||||
metadata_class = ContentTypeMetadata
|
metadata_class = ContentTypeMetadata
|
||||||
queryset = ObjectChange.objects.prefetch_related('user')
|
queryset = ObjectChange.objects.prefetch_related('user')
|
||||||
serializer_class = serializers.ObjectChangeSerializer
|
serializer_class = serializers.ObjectChangeSerializer
|
||||||
filterset_class = filters.ObjectChangeFilterSet
|
filterset_class = filtersets.ObjectChangeFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -371,7 +371,7 @@ class JobResultViewSet(ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = JobResult.objects.prefetch_related('user')
|
queryset = JobResult.objects.prefetch_related('user')
|
||||||
serializer_class = serializers.JobResultSerializer
|
serializer_class = serializers.JobResultSerializer
|
||||||
filterset_class = filters.JobResultFilterSet
|
filterset_class = filtersets.JobResultFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -384,4 +384,4 @@ class ContentTypeViewSet(ReadOnlyModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = ContentType.objects.order_by('app_label', 'model')
|
queryset = ContentType.objects.order_by('app_label', 'model')
|
||||||
serializer_class = serializers.ContentTypeSerializer
|
serializer_class = serializers.ContentTypeSerializer
|
||||||
filterset_class = filters.ContentTypeFilterSet
|
filterset_class = filtersets.ContentTypeFilterSet
|
||||||
|
@ -1,31 +1,12 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.forms import DateField, IntegerField, NullBooleanField
|
from django.forms import DateField, IntegerField, NullBooleanField
|
||||||
|
|
||||||
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
from .models import Tag
|
||||||
from tenancy.models import Tenant, TenantGroup
|
|
||||||
from utilities.filters import BaseFilterSet, ContentTypeFilter
|
|
||||||
from virtualization.models import Cluster, ClusterGroup
|
|
||||||
from .choices import *
|
from .choices import *
|
||||||
from .models import *
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConfigContextFilterSet',
|
|
||||||
'ContentTypeFilterSet',
|
|
||||||
'CreatedUpdatedFilterSet',
|
|
||||||
'CustomFieldFilter',
|
'CustomFieldFilter',
|
||||||
'CustomLinkFilterSet',
|
'TagFilter',
|
||||||
'CustomFieldModelFilterSet',
|
|
||||||
'ExportTemplateFilterSet',
|
|
||||||
'ImageAttachmentFilterSet',
|
|
||||||
'JournalEntryFilterSet',
|
|
||||||
'LocalConfigContextFilterSet',
|
|
||||||
'ObjectChangeFilterSet',
|
|
||||||
'TagFilterSet',
|
|
||||||
'WebhookFilterSet',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
EXACT_FILTER_TYPES = (
|
EXACT_FILTER_TYPES = (
|
||||||
@ -36,41 +17,6 @@ EXACT_FILTER_TYPES = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CreatedUpdatedFilterSet(django_filters.FilterSet):
|
|
||||||
created = django_filters.DateFilter()
|
|
||||||
created__gte = django_filters.DateFilter(
|
|
||||||
field_name='created',
|
|
||||||
lookup_expr='gte'
|
|
||||||
)
|
|
||||||
created__lte = django_filters.DateFilter(
|
|
||||||
field_name='created',
|
|
||||||
lookup_expr='lte'
|
|
||||||
)
|
|
||||||
last_updated = django_filters.DateTimeFilter()
|
|
||||||
last_updated__gte = django_filters.DateTimeFilter(
|
|
||||||
field_name='last_updated',
|
|
||||||
lookup_expr='gte'
|
|
||||||
)
|
|
||||||
last_updated__lte = django_filters.DateTimeFilter(
|
|
||||||
field_name='last_updated',
|
|
||||||
lookup_expr='lte'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WebhookFilterSet(BaseFilterSet):
|
|
||||||
content_types = ContentTypeFilter()
|
|
||||||
http_method = django_filters.MultipleChoiceFilter(
|
|
||||||
choices=WebhookHttpMethodChoices
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Webhook
|
|
||||||
fields = [
|
|
||||||
'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled',
|
|
||||||
'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldFilter(django_filters.Filter):
|
class CustomFieldFilter(django_filters.Filter):
|
||||||
"""
|
"""
|
||||||
Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name.
|
Filter objects by the presence of a CustomFieldValue. The filter's name is used as the CustomField name.
|
||||||
@ -94,310 +40,16 @@ class CustomFieldFilter(django_filters.Filter):
|
|||||||
self.lookup_expr = 'icontains'
|
self.lookup_expr = 'icontains'
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldModelFilterSet(django_filters.FilterSet):
|
class TagFilter(django_filters.ModelMultipleChoiceFilter):
|
||||||
"""
|
"""
|
||||||
Dynamically add a Filter for each CustomField applicable to the parent model.
|
Match on one or more assigned tags. If multiple tags are specified (e.g. ?tag=foo&tag=bar), the queryset is filtered
|
||||||
|
to objects matching all tags.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
kwargs.setdefault('field_name', 'tags__slug')
|
||||||
|
kwargs.setdefault('to_field_name', 'slug')
|
||||||
|
kwargs.setdefault('conjoined', True)
|
||||||
|
kwargs.setdefault('queryset', Tag.objects.all())
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
custom_fields = CustomField.objects.filter(
|
|
||||||
content_types=ContentType.objects.get_for_model(self._meta.model)
|
|
||||||
).exclude(
|
|
||||||
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
|
||||||
)
|
|
||||||
for cf in custom_fields:
|
|
||||||
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldFilterSet(django_filters.FilterSet):
|
|
||||||
content_types = ContentTypeFilter()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CustomField
|
|
||||||
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight']
|
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkFilterSet(BaseFilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CustomLink
|
|
||||||
fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window']
|
|
||||||
|
|
||||||
|
|
||||||
class ExportTemplateFilterSet(BaseFilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ExportTemplate
|
|
||||||
fields = ['id', 'content_type', 'name']
|
|
||||||
|
|
||||||
|
|
||||||
class ImageAttachmentFilterSet(BaseFilterSet):
|
|
||||||
content_type = ContentTypeFilter()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ImageAttachment
|
|
||||||
fields = ['id', 'content_type_id', 'object_id', 'name']
|
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
|
|
||||||
q = django_filters.CharFilter(
|
|
||||||
method='search',
|
|
||||||
label='Search',
|
|
||||||
)
|
|
||||||
created = django_filters.DateTimeFromToRangeFilter()
|
|
||||||
assigned_object_type = ContentTypeFilter()
|
|
||||||
created_by_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=User.objects.all(),
|
|
||||||
label='User (ID)',
|
|
||||||
)
|
|
||||||
created_by = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='created_by__username',
|
|
||||||
queryset=User.objects.all(),
|
|
||||||
to_field_name='username',
|
|
||||||
label='User (name)',
|
|
||||||
)
|
|
||||||
kind = django_filters.MultipleChoiceFilter(
|
|
||||||
choices=JournalEntryKindChoices
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = JournalEntry
|
|
||||||
fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind']
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(comments__icontains=value)
|
|
||||||
|
|
||||||
|
|
||||||
class TagFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
|
|
||||||
q = django_filters.CharFilter(
|
|
||||||
method='search',
|
|
||||||
label='Search',
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = Tag
|
|
||||||
fields = ['id', 'name', 'slug', 'color']
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(
|
|
||||||
Q(name__icontains=value) |
|
|
||||||
Q(slug__icontains=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigContextFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
|
|
||||||
q = django_filters.CharFilter(
|
|
||||||
method='search',
|
|
||||||
label='Search',
|
|
||||||
)
|
|
||||||
region_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='regions',
|
|
||||||
queryset=Region.objects.all(),
|
|
||||||
label='Region',
|
|
||||||
)
|
|
||||||
region = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='regions__slug',
|
|
||||||
queryset=Region.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Region (slug)',
|
|
||||||
)
|
|
||||||
site_group = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='site_groups__slug',
|
|
||||||
queryset=SiteGroup.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Site group (slug)',
|
|
||||||
)
|
|
||||||
site_group_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='site_groups',
|
|
||||||
queryset=SiteGroup.objects.all(),
|
|
||||||
label='Site group',
|
|
||||||
)
|
|
||||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='sites',
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
label='Site',
|
|
||||||
)
|
|
||||||
site = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='sites__slug',
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Site (slug)',
|
|
||||||
)
|
|
||||||
device_type_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='device_types',
|
|
||||||
queryset=DeviceType.objects.all(),
|
|
||||||
label='Device type',
|
|
||||||
)
|
|
||||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='roles',
|
|
||||||
queryset=DeviceRole.objects.all(),
|
|
||||||
label='Role',
|
|
||||||
)
|
|
||||||
role = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='roles__slug',
|
|
||||||
queryset=DeviceRole.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Role (slug)',
|
|
||||||
)
|
|
||||||
platform_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='platforms',
|
|
||||||
queryset=Platform.objects.all(),
|
|
||||||
label='Platform',
|
|
||||||
)
|
|
||||||
platform = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='platforms__slug',
|
|
||||||
queryset=Platform.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Platform (slug)',
|
|
||||||
)
|
|
||||||
cluster_group_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='cluster_groups',
|
|
||||||
queryset=ClusterGroup.objects.all(),
|
|
||||||
label='Cluster group',
|
|
||||||
)
|
|
||||||
cluster_group = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='cluster_groups__slug',
|
|
||||||
queryset=ClusterGroup.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Cluster group (slug)',
|
|
||||||
)
|
|
||||||
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='clusters',
|
|
||||||
queryset=Cluster.objects.all(),
|
|
||||||
label='Cluster',
|
|
||||||
)
|
|
||||||
tenant_group_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant_groups',
|
|
||||||
queryset=TenantGroup.objects.all(),
|
|
||||||
label='Tenant group',
|
|
||||||
)
|
|
||||||
tenant_group = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenant_groups__slug',
|
|
||||||
queryset=TenantGroup.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant group (slug)',
|
|
||||||
)
|
|
||||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenants',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
label='Tenant',
|
|
||||||
)
|
|
||||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tenants__slug',
|
|
||||||
queryset=Tenant.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tenant (slug)',
|
|
||||||
)
|
|
||||||
tag = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='tags__slug',
|
|
||||||
queryset=Tag.objects.all(),
|
|
||||||
to_field_name='slug',
|
|
||||||
label='Tag (slug)',
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ConfigContext
|
|
||||||
fields = ['id', 'name', 'is_active']
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(
|
|
||||||
Q(name__icontains=value) |
|
|
||||||
Q(description__icontains=value) |
|
|
||||||
Q(data__icontains=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Filter for Local Config Context Data
|
|
||||||
#
|
|
||||||
|
|
||||||
class LocalConfigContextFilterSet(django_filters.FilterSet):
|
|
||||||
local_context_data = django_filters.BooleanFilter(
|
|
||||||
method='_local_context_data',
|
|
||||||
label='Has local config context data',
|
|
||||||
)
|
|
||||||
|
|
||||||
def _local_context_data(self, queryset, name, value):
|
|
||||||
return queryset.exclude(local_context_data__isnull=value)
|
|
||||||
|
|
||||||
|
|
||||||
class ObjectChangeFilterSet(BaseFilterSet):
|
|
||||||
q = django_filters.CharFilter(
|
|
||||||
method='search',
|
|
||||||
label='Search',
|
|
||||||
)
|
|
||||||
time = django_filters.DateTimeFromToRangeFilter()
|
|
||||||
changed_object_type = ContentTypeFilter()
|
|
||||||
user_id = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
queryset=User.objects.all(),
|
|
||||||
label='User (ID)',
|
|
||||||
)
|
|
||||||
user = django_filters.ModelMultipleChoiceFilter(
|
|
||||||
field_name='user__username',
|
|
||||||
queryset=User.objects.all(),
|
|
||||||
to_field_name='username',
|
|
||||||
label='User name',
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ObjectChange
|
|
||||||
fields = [
|
|
||||||
'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id',
|
|
||||||
'object_repr',
|
|
||||||
]
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(
|
|
||||||
Q(user_name__icontains=value) |
|
|
||||||
Q(object_repr__icontains=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Job Results
|
|
||||||
#
|
|
||||||
|
|
||||||
class JobResultFilterSet(BaseFilterSet):
|
|
||||||
q = django_filters.CharFilter(
|
|
||||||
method='search',
|
|
||||||
label='Search',
|
|
||||||
)
|
|
||||||
created = django_filters.DateTimeFilter()
|
|
||||||
completed = django_filters.DateTimeFilter()
|
|
||||||
status = django_filters.MultipleChoiceFilter(
|
|
||||||
choices=JobResultStatusChoices,
|
|
||||||
null_value=None
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = JobResult
|
|
||||||
fields = [
|
|
||||||
'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name'
|
|
||||||
]
|
|
||||||
|
|
||||||
def search(self, queryset, name, value):
|
|
||||||
if not value.strip():
|
|
||||||
return queryset
|
|
||||||
return queryset.filter(
|
|
||||||
Q(user__username__icontains=value)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# ContentTypes
|
|
||||||
#
|
|
||||||
|
|
||||||
class ContentTypeFilterSet(django_filters.FilterSet):
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = ContentType
|
|
||||||
fields = ['id', 'app_label', 'model']
|
|
||||||
|
341
netbox/extras/filtersets.py
Normal file
341
netbox/extras/filtersets.py
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
import django_filters
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup
|
||||||
|
from netbox.filtersets import BaseFilterSet, ChangeLoggedModelFilterSet
|
||||||
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
from utilities.filters import ContentTypeFilter
|
||||||
|
from virtualization.models import Cluster, ClusterGroup
|
||||||
|
from .choices import *
|
||||||
|
from .models import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'ConfigContextFilterSet',
|
||||||
|
'ContentTypeFilterSet',
|
||||||
|
'CustomLinkFilterSet',
|
||||||
|
'ExportTemplateFilterSet',
|
||||||
|
'ImageAttachmentFilterSet',
|
||||||
|
'JournalEntryFilterSet',
|
||||||
|
'LocalConfigContextFilterSet',
|
||||||
|
'ObjectChangeFilterSet',
|
||||||
|
'TagFilterSet',
|
||||||
|
'WebhookFilterSet',
|
||||||
|
)
|
||||||
|
|
||||||
|
EXACT_FILTER_TYPES = (
|
||||||
|
CustomFieldTypeChoices.TYPE_BOOLEAN,
|
||||||
|
CustomFieldTypeChoices.TYPE_DATE,
|
||||||
|
CustomFieldTypeChoices.TYPE_INTEGER,
|
||||||
|
CustomFieldTypeChoices.TYPE_SELECT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WebhookFilterSet(BaseFilterSet):
|
||||||
|
content_types = ContentTypeFilter()
|
||||||
|
http_method = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=WebhookHttpMethodChoices
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Webhook
|
||||||
|
fields = [
|
||||||
|
'id', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled',
|
||||||
|
'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CustomFieldFilterSet(django_filters.FilterSet):
|
||||||
|
content_types = ContentTypeFilter()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomField
|
||||||
|
fields = ['id', 'content_types', 'name', 'required', 'filter_logic', 'weight']
|
||||||
|
|
||||||
|
|
||||||
|
class CustomLinkFilterSet(BaseFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomLink
|
||||||
|
fields = ['id', 'content_type', 'name', 'link_text', 'link_url', 'weight', 'group_name', 'new_window']
|
||||||
|
|
||||||
|
|
||||||
|
class ExportTemplateFilterSet(BaseFilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExportTemplate
|
||||||
|
fields = ['id', 'content_type', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
class ImageAttachmentFilterSet(BaseFilterSet):
|
||||||
|
created = django_filters.DateTimeFilter()
|
||||||
|
content_type = ContentTypeFilter()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ImageAttachment
|
||||||
|
fields = ['id', 'content_type_id', 'object_id', 'name']
|
||||||
|
|
||||||
|
|
||||||
|
class JournalEntryFilterSet(ChangeLoggedModelFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
created = django_filters.DateTimeFromToRangeFilter()
|
||||||
|
assigned_object_type = ContentTypeFilter()
|
||||||
|
created_by_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=User.objects.all(),
|
||||||
|
label='User (ID)',
|
||||||
|
)
|
||||||
|
created_by = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='created_by__username',
|
||||||
|
queryset=User.objects.all(),
|
||||||
|
to_field_name='username',
|
||||||
|
label='User (name)',
|
||||||
|
)
|
||||||
|
kind = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=JournalEntryKindChoices
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = JournalEntry
|
||||||
|
fields = ['id', 'assigned_object_type_id', 'assigned_object_id', 'created', 'kind']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(comments__icontains=value)
|
||||||
|
|
||||||
|
|
||||||
|
class TagFilterSet(ChangeLoggedModelFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Tag
|
||||||
|
fields = ['id', 'name', 'slug', 'color']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(name__icontains=value) |
|
||||||
|
Q(slug__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigContextFilterSet(ChangeLoggedModelFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
region_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='regions',
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
label='Region',
|
||||||
|
)
|
||||||
|
region = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='regions__slug',
|
||||||
|
queryset=Region.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Region (slug)',
|
||||||
|
)
|
||||||
|
site_group = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='site_groups__slug',
|
||||||
|
queryset=SiteGroup.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Site group (slug)',
|
||||||
|
)
|
||||||
|
site_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='site_groups',
|
||||||
|
queryset=SiteGroup.objects.all(),
|
||||||
|
label='Site group',
|
||||||
|
)
|
||||||
|
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='sites',
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
label='Site',
|
||||||
|
)
|
||||||
|
site = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='sites__slug',
|
||||||
|
queryset=Site.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Site (slug)',
|
||||||
|
)
|
||||||
|
device_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='device_types',
|
||||||
|
queryset=DeviceType.objects.all(),
|
||||||
|
label='Device type',
|
||||||
|
)
|
||||||
|
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='roles',
|
||||||
|
queryset=DeviceRole.objects.all(),
|
||||||
|
label='Role',
|
||||||
|
)
|
||||||
|
role = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='roles__slug',
|
||||||
|
queryset=DeviceRole.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Role (slug)',
|
||||||
|
)
|
||||||
|
platform_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='platforms',
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
label='Platform',
|
||||||
|
)
|
||||||
|
platform = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='platforms__slug',
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Platform (slug)',
|
||||||
|
)
|
||||||
|
cluster_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='cluster_groups',
|
||||||
|
queryset=ClusterGroup.objects.all(),
|
||||||
|
label='Cluster group',
|
||||||
|
)
|
||||||
|
cluster_group = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='cluster_groups__slug',
|
||||||
|
queryset=ClusterGroup.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Cluster group (slug)',
|
||||||
|
)
|
||||||
|
cluster_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='clusters',
|
||||||
|
queryset=Cluster.objects.all(),
|
||||||
|
label='Cluster',
|
||||||
|
)
|
||||||
|
tenant_group_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='tenant_groups',
|
||||||
|
queryset=TenantGroup.objects.all(),
|
||||||
|
label='Tenant group',
|
||||||
|
)
|
||||||
|
tenant_group = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='tenant_groups__slug',
|
||||||
|
queryset=TenantGroup.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Tenant group (slug)',
|
||||||
|
)
|
||||||
|
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='tenants',
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
label='Tenant',
|
||||||
|
)
|
||||||
|
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='tenants__slug',
|
||||||
|
queryset=Tenant.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Tenant (slug)',
|
||||||
|
)
|
||||||
|
tag = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='tags__slug',
|
||||||
|
queryset=Tag.objects.all(),
|
||||||
|
to_field_name='slug',
|
||||||
|
label='Tag (slug)',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ConfigContext
|
||||||
|
fields = ['id', 'name', 'is_active']
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(name__icontains=value) |
|
||||||
|
Q(description__icontains=value) |
|
||||||
|
Q(data__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Filter for Local Config Context Data
|
||||||
|
#
|
||||||
|
|
||||||
|
class LocalConfigContextFilterSet(django_filters.FilterSet):
|
||||||
|
local_context_data = django_filters.BooleanFilter(
|
||||||
|
method='_local_context_data',
|
||||||
|
label='Has local config context data',
|
||||||
|
)
|
||||||
|
|
||||||
|
def _local_context_data(self, queryset, name, value):
|
||||||
|
return queryset.exclude(local_context_data__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectChangeFilterSet(BaseFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
time = django_filters.DateTimeFromToRangeFilter()
|
||||||
|
changed_object_type = ContentTypeFilter()
|
||||||
|
user_id = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
queryset=User.objects.all(),
|
||||||
|
label='User (ID)',
|
||||||
|
)
|
||||||
|
user = django_filters.ModelMultipleChoiceFilter(
|
||||||
|
field_name='user__username',
|
||||||
|
queryset=User.objects.all(),
|
||||||
|
to_field_name='username',
|
||||||
|
label='User name',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ObjectChange
|
||||||
|
fields = [
|
||||||
|
'id', 'user', 'user_name', 'request_id', 'action', 'changed_object_type_id', 'changed_object_id',
|
||||||
|
'object_repr',
|
||||||
|
]
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(user_name__icontains=value) |
|
||||||
|
Q(object_repr__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Job Results
|
||||||
|
#
|
||||||
|
|
||||||
|
class JobResultFilterSet(BaseFilterSet):
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
created = django_filters.DateTimeFilter()
|
||||||
|
completed = django_filters.DateTimeFilter()
|
||||||
|
status = django_filters.MultipleChoiceFilter(
|
||||||
|
choices=JobResultStatusChoices,
|
||||||
|
null_value=None
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = JobResult
|
||||||
|
fields = [
|
||||||
|
'id', 'created', 'completed', 'status', 'user', 'obj_type', 'name'
|
||||||
|
]
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
Q(user__username__icontains=value)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# ContentTypes
|
||||||
|
#
|
||||||
|
|
||||||
|
class ContentTypeFilterSet(django_filters.FilterSet):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ContentType
|
||||||
|
fields = ['id', 'app_label', 'model']
|
@ -3,7 +3,7 @@ from django.core.exceptions import ValidationError
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from dcim.filters import SiteFilterSet
|
from dcim.filtersets import SiteFilterSet
|
||||||
from dcim.forms import SiteCSVForm
|
from dcim.forms import SiteCSVForm
|
||||||
from dcim.models import Site, Rack
|
from dcim.models import Site, Rack
|
||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
@ -6,14 +7,15 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
from dcim.models import DeviceRole, DeviceType, Manufacturer, Platform, Rack, Region, Site, SiteGroup
|
||||||
from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
|
from extras.choices import JournalEntryKindChoices, ObjectChangeActionChoices
|
||||||
from extras.filters import *
|
from extras.filtersets import *
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
from utilities.testing import BaseFilterSetTests, ChangeLoggedFilterSetTests
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
from virtualization.models import Cluster, ClusterGroup, ClusterType
|
||||||
|
|
||||||
|
|
||||||
class WebhookTestCase(TestCase):
|
class WebhookTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = Webhook.objects.all()
|
queryset = Webhook.objects.all()
|
||||||
filterset = WebhookFilterSet
|
filterset = WebhookFilterSet
|
||||||
|
|
||||||
@ -52,10 +54,6 @@ class WebhookTestCase(TestCase):
|
|||||||
webhooks[1].content_types.add(content_types[1])
|
webhooks[1].content_types.add(content_types[1])
|
||||||
webhooks[2].content_types.add(content_types[2])
|
webhooks[2].content_types.add(content_types[2])
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Webhook 1', 'Webhook 2']}
|
params = {'name': ['Webhook 1', 'Webhook 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -89,7 +87,7 @@ class WebhookTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class CustomLinkTestCase(TestCase):
|
class CustomLinkTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = CustomLink.objects.all()
|
queryset = CustomLink.objects.all()
|
||||||
filterset = CustomLinkFilterSet
|
filterset = CustomLinkFilterSet
|
||||||
|
|
||||||
@ -125,10 +123,6 @@ class CustomLinkTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
CustomLink.objects.bulk_create(custom_links)
|
CustomLink.objects.bulk_create(custom_links)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Custom Link 1', 'Custom Link 2']}
|
params = {'name': ['Custom Link 1', 'Custom Link 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -148,7 +142,7 @@ class CustomLinkTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class ExportTemplateTestCase(TestCase):
|
class ExportTemplateTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = ExportTemplate.objects.all()
|
queryset = ExportTemplate.objects.all()
|
||||||
filterset = ExportTemplateFilterSet
|
filterset = ExportTemplateFilterSet
|
||||||
|
|
||||||
@ -164,10 +158,6 @@ class ExportTemplateTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
ExportTemplate.objects.bulk_create(export_templates)
|
ExportTemplate.objects.bulk_create(export_templates)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Export Template 1', 'Export Template 2']}
|
params = {'name': ['Export Template 1', 'Export Template 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -177,7 +167,7 @@ class ExportTemplateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class ImageAttachmentTestCase(TestCase):
|
class ImageAttachmentTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = ImageAttachment.objects.all()
|
queryset = ImageAttachment.objects.all()
|
||||||
filterset = ImageAttachmentFilterSet
|
filterset = ImageAttachmentFilterSet
|
||||||
|
|
||||||
@ -235,10 +225,6 @@ class ImageAttachmentTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
ImageAttachment.objects.bulk_create(image_attachments)
|
ImageAttachment.objects.bulk_create(image_attachments)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Image Attachment 1', 'Image Attachment 2']}
|
params = {'name': ['Image Attachment 1', 'Image Attachment 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -254,8 +240,14 @@ class ImageAttachmentTestCase(TestCase):
|
|||||||
}
|
}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_created(self):
|
||||||
|
pk_list = self.queryset.values_list('pk', flat=True)[:2]
|
||||||
|
self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
|
||||||
|
params = {'created': '2021-01-01T00:00:00'}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
class JournalEntryTestCase(TestCase):
|
|
||||||
|
class JournalEntryTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = JournalEntry.objects.all()
|
queryset = JournalEntry.objects.all()
|
||||||
filterset = JournalEntryFilterSet
|
filterset = JournalEntryFilterSet
|
||||||
|
|
||||||
@ -320,10 +312,6 @@ class JournalEntryTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
JournalEntry.objects.bulk_create(journal_entries)
|
JournalEntry.objects.bulk_create(journal_entries)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_created_by(self):
|
def test_created_by(self):
|
||||||
users = User.objects.filter(username__in=['Alice', 'Bob'])
|
users = User.objects.filter(username__in=['Alice', 'Bob'])
|
||||||
params = {'created_by': [users[0].username, users[1].username]}
|
params = {'created_by': [users[0].username, users[1].username]}
|
||||||
@ -348,8 +336,17 @@ class JournalEntryTestCase(TestCase):
|
|||||||
params = {'kind': [JournalEntryKindChoices.KIND_INFO, JournalEntryKindChoices.KIND_SUCCESS]}
|
params = {'kind': [JournalEntryKindChoices.KIND_INFO, JournalEntryKindChoices.KIND_SUCCESS]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_created(self):
|
||||||
|
pk_list = self.queryset.values_list('pk', flat=True)[:2]
|
||||||
|
self.queryset.filter(pk__in=pk_list).update(created=datetime(2021, 1, 1, 0, 0, 0, tzinfo=timezone.utc))
|
||||||
|
params = {
|
||||||
|
'created_after': '2020-12-31T00:00:00',
|
||||||
|
'created_before': '2021-01-02T00:00:00',
|
||||||
|
}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
class ConfigContextTestCase(TestCase):
|
|
||||||
|
class ConfigContextTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
filterset = ConfigContextFilterSet
|
filterset = ConfigContextFilterSet
|
||||||
|
|
||||||
@ -449,10 +446,6 @@ class ConfigContextTestCase(TestCase):
|
|||||||
c.tenant_groups.set([tenant_groups[i]])
|
c.tenant_groups.set([tenant_groups[i]])
|
||||||
c.tenants.set([tenants[i]])
|
c.tenants.set([tenants[i]])
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Config Context 1', 'Config Context 2']}
|
params = {'name': ['Config Context 1', 'Config Context 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -530,7 +523,7 @@ class ConfigContextTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class TagTestCase(TestCase):
|
class TagTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Tag.objects.all()
|
queryset = Tag.objects.all()
|
||||||
filterset = TagFilterSet
|
filterset = TagFilterSet
|
||||||
|
|
||||||
@ -544,10 +537,6 @@ class TagTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Tag.objects.bulk_create(tags)
|
Tag.objects.bulk_create(tags)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Tag 1', 'Tag 2']}
|
params = {'name': ['Tag 1', 'Tag 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -561,7 +550,7 @@ class TagTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class ObjectChangeTestCase(TestCase):
|
class ObjectChangeTestCase(TestCase, BaseFilterSetTests):
|
||||||
queryset = ObjectChange.objects.all()
|
queryset = ObjectChange.objects.all()
|
||||||
filterset = ObjectChangeFilterSet
|
filterset = ObjectChangeFilterSet
|
||||||
|
|
||||||
@ -635,10 +624,6 @@ class ObjectChangeTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
ObjectChange.objects.bulk_create(object_changes)
|
ObjectChange.objects.bulk_create(object_changes)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:3]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
|
|
||||||
def test_user(self):
|
def test_user(self):
|
||||||
params = {'user_id': User.objects.filter(username__in=['user1', 'user2']).values_list('pk', flat=True)}
|
params = {'user_id': User.objects.filter(username__in=['user1', 'user2']).values_list('pk', flat=True)}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
@ -13,7 +13,7 @@ from utilities.forms import ConfirmationForm
|
|||||||
from utilities.tables import paginate_table
|
from utilities.tables import paginate_table
|
||||||
from utilities.utils import copy_safe_request, count_related, shallow_compare_dict
|
from utilities.utils import copy_safe_request, count_related, shallow_compare_dict
|
||||||
from utilities.views import ContentTypePermissionRequiredMixin
|
from utilities.views import ContentTypePermissionRequiredMixin
|
||||||
from . import filters, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .choices import JobResultStatusChoices
|
from .choices import JobResultStatusChoices
|
||||||
from .models import ConfigContext, ImageAttachment, JournalEntry, ObjectChange, JobResult, Tag, TaggedItem
|
from .models import ConfigContext, ImageAttachment, JournalEntry, ObjectChange, JobResult, Tag, TaggedItem
|
||||||
from .reports import get_report, get_reports, run_report
|
from .reports import get_report, get_reports, run_report
|
||||||
@ -28,7 +28,7 @@ class TagListView(generic.ObjectListView):
|
|||||||
queryset = Tag.objects.annotate(
|
queryset = Tag.objects.annotate(
|
||||||
items=count_related(TaggedItem, 'tag')
|
items=count_related(TaggedItem, 'tag')
|
||||||
)
|
)
|
||||||
filterset = filters.TagFilterSet
|
filterset = filtersets.TagFilterSet
|
||||||
filterset_form = forms.TagFilterForm
|
filterset_form = forms.TagFilterForm
|
||||||
table = tables.TagTable
|
table = tables.TagTable
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ class TagBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConfigContextListView(generic.ObjectListView):
|
class ConfigContextListView(generic.ObjectListView):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
filterset = filters.ConfigContextFilterSet
|
filterset = filtersets.ConfigContextFilterSet
|
||||||
filterset_form = forms.ConfigContextFilterForm
|
filterset_form = forms.ConfigContextFilterForm
|
||||||
table = tables.ConfigContextTable
|
table = tables.ConfigContextTable
|
||||||
action_buttons = ('add',)
|
action_buttons = ('add',)
|
||||||
@ -127,7 +127,7 @@ class ConfigContextEditView(generic.ObjectEditView):
|
|||||||
|
|
||||||
class ConfigContextBulkEditView(generic.BulkEditView):
|
class ConfigContextBulkEditView(generic.BulkEditView):
|
||||||
queryset = ConfigContext.objects.all()
|
queryset = ConfigContext.objects.all()
|
||||||
filterset = filters.ConfigContextFilterSet
|
filterset = filtersets.ConfigContextFilterSet
|
||||||
table = tables.ConfigContextTable
|
table = tables.ConfigContextTable
|
||||||
form = forms.ConfigContextBulkEditForm
|
form = forms.ConfigContextBulkEditForm
|
||||||
|
|
||||||
@ -173,7 +173,7 @@ class ObjectConfigContextView(generic.ObjectView):
|
|||||||
|
|
||||||
class ObjectChangeListView(generic.ObjectListView):
|
class ObjectChangeListView(generic.ObjectListView):
|
||||||
queryset = ObjectChange.objects.all()
|
queryset = ObjectChange.objects.all()
|
||||||
filterset = filters.ObjectChangeFilterSet
|
filterset = filtersets.ObjectChangeFilterSet
|
||||||
filterset_form = forms.ObjectChangeFilterForm
|
filterset_form = forms.ObjectChangeFilterForm
|
||||||
table = tables.ObjectChangeTable
|
table = tables.ObjectChangeTable
|
||||||
template_name = 'extras/objectchange_list.html'
|
template_name = 'extras/objectchange_list.html'
|
||||||
@ -300,7 +300,7 @@ class ImageAttachmentDeleteView(generic.ObjectDeleteView):
|
|||||||
|
|
||||||
class JournalEntryListView(generic.ObjectListView):
|
class JournalEntryListView(generic.ObjectListView):
|
||||||
queryset = JournalEntry.objects.all()
|
queryset = JournalEntry.objects.all()
|
||||||
filterset = filters.JournalEntryFilterSet
|
filterset = filtersets.JournalEntryFilterSet
|
||||||
filterset_form = forms.JournalEntryFilterForm
|
filterset_form = forms.JournalEntryFilterForm
|
||||||
table = tables.JournalEntryTable
|
table = tables.JournalEntryTable
|
||||||
action_buttons = ('export',)
|
action_buttons = ('export',)
|
||||||
@ -338,14 +338,14 @@ class JournalEntryDeleteView(generic.ObjectDeleteView):
|
|||||||
|
|
||||||
class JournalEntryBulkEditView(generic.BulkEditView):
|
class JournalEntryBulkEditView(generic.BulkEditView):
|
||||||
queryset = JournalEntry.objects.prefetch_related('created_by')
|
queryset = JournalEntry.objects.prefetch_related('created_by')
|
||||||
filterset = filters.JournalEntryFilterSet
|
filterset = filtersets.JournalEntryFilterSet
|
||||||
table = tables.JournalEntryTable
|
table = tables.JournalEntryTable
|
||||||
form = forms.JournalEntryBulkEditForm
|
form = forms.JournalEntryBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class JournalEntryBulkDeleteView(generic.BulkDeleteView):
|
class JournalEntryBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = JournalEntry.objects.prefetch_related('created_by')
|
queryset = JournalEntry.objects.prefetch_related('created_by')
|
||||||
filterset = filters.JournalEntryFilterSet
|
filterset = filtersets.JournalEntryFilterSet
|
||||||
table = tables.JournalEntryTable
|
table = tables.JournalEntryTable
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class NestedVRFSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.VRF
|
model = models.VRF
|
||||||
fields = ['id', 'url', 'display', 'name', 'rd', 'display_name', 'prefix_count']
|
fields = ['id', 'url', 'display', 'name', 'rd', 'prefix_count']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -92,7 +92,7 @@ class NestedVLANSerializer(WritableNestedSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.VLAN
|
model = models.VLAN
|
||||||
fields = ['id', 'url', 'display', 'vid', 'name', 'display_name']
|
fields = ['id', 'url', 'display', 'vid', 'name']
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -44,8 +44,7 @@ class VRFSerializer(PrimaryModelSerializer):
|
|||||||
model = VRF
|
model = VRF
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets',
|
'id', 'url', 'display', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets',
|
||||||
'export_targets', 'tags', 'display_name', 'custom_fields', 'created', 'last_updated', 'ipaddress_count',
|
'export_targets', 'tags', 'custom_fields', 'created', 'last_updated', 'ipaddress_count', 'prefix_count',
|
||||||
'prefix_count',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -167,7 +166,7 @@ class VLANSerializer(PrimaryModelSerializer):
|
|||||||
model = VLAN
|
model = VLAN
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'tags',
|
'id', 'url', 'display', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'tags',
|
||||||
'display_name', 'custom_fields', 'created', 'last_updated', 'prefix_count',
|
'custom_fields', 'created', 'last_updated', 'prefix_count',
|
||||||
]
|
]
|
||||||
validators = []
|
validators = []
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ from rest_framework.response import Response
|
|||||||
from rest_framework.routers import APIRootView
|
from rest_framework.routers import APIRootView
|
||||||
|
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from ipam import filters
|
from ipam import filtersets
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
||||||
from netbox.api.views import ModelViewSet
|
from netbox.api.views import ModelViewSet
|
||||||
from utilities.constants import ADVISORY_LOCK_KEYS
|
from utilities.constants import ADVISORY_LOCK_KEYS
|
||||||
@ -38,7 +38,7 @@ class VRFViewSet(CustomFieldModelViewSet):
|
|||||||
prefix_count=count_related(Prefix, 'vrf')
|
prefix_count=count_related(Prefix, 'vrf')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.VRFSerializer
|
serializer_class = serializers.VRFSerializer
|
||||||
filterset_class = filters.VRFFilterSet
|
filterset_class = filtersets.VRFFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -48,7 +48,7 @@ class VRFViewSet(CustomFieldModelViewSet):
|
|||||||
class RouteTargetViewSet(CustomFieldModelViewSet):
|
class RouteTargetViewSet(CustomFieldModelViewSet):
|
||||||
queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags')
|
queryset = RouteTarget.objects.prefetch_related('tenant').prefetch_related('tags')
|
||||||
serializer_class = serializers.RouteTargetSerializer
|
serializer_class = serializers.RouteTargetSerializer
|
||||||
filterset_class = filters.RouteTargetFilterSet
|
filterset_class = filtersets.RouteTargetFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -60,7 +60,7 @@ class RIRViewSet(CustomFieldModelViewSet):
|
|||||||
aggregate_count=count_related(Aggregate, 'rir')
|
aggregate_count=count_related(Aggregate, 'rir')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.RIRSerializer
|
serializer_class = serializers.RIRSerializer
|
||||||
filterset_class = filters.RIRFilterSet
|
filterset_class = filtersets.RIRFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -70,7 +70,7 @@ class RIRViewSet(CustomFieldModelViewSet):
|
|||||||
class AggregateViewSet(CustomFieldModelViewSet):
|
class AggregateViewSet(CustomFieldModelViewSet):
|
||||||
queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
|
queryset = Aggregate.objects.prefetch_related('rir').prefetch_related('tags')
|
||||||
serializer_class = serializers.AggregateSerializer
|
serializer_class = serializers.AggregateSerializer
|
||||||
filterset_class = filters.AggregateFilterSet
|
filterset_class = filtersets.AggregateFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -83,7 +83,7 @@ class RoleViewSet(CustomFieldModelViewSet):
|
|||||||
vlan_count=count_related(VLAN, 'role')
|
vlan_count=count_related(VLAN, 'role')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.RoleSerializer
|
serializer_class = serializers.RoleSerializer
|
||||||
filterset_class = filters.RoleFilterSet
|
filterset_class = filtersets.RoleFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -95,7 +95,7 @@ class PrefixViewSet(CustomFieldModelViewSet):
|
|||||||
'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
|
'site', 'vrf__tenant', 'tenant', 'vlan', 'role', 'tags'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.PrefixSerializer
|
serializer_class = serializers.PrefixSerializer
|
||||||
filterset_class = filters.PrefixFilterSet
|
filterset_class = filtersets.PrefixFilterSet
|
||||||
|
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
if self.action == "available_prefixes" and self.request.method == "POST":
|
if self.action == "available_prefixes" and self.request.method == "POST":
|
||||||
@ -275,7 +275,7 @@ class IPAddressViewSet(CustomFieldModelViewSet):
|
|||||||
'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
|
'vrf__tenant', 'tenant', 'nat_inside', 'nat_outside', 'tags', 'assigned_object'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.IPAddressSerializer
|
serializer_class = serializers.IPAddressSerializer
|
||||||
filterset_class = filters.IPAddressFilterSet
|
filterset_class = filtersets.IPAddressFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -287,7 +287,7 @@ class VLANGroupViewSet(CustomFieldModelViewSet):
|
|||||||
vlan_count=count_related(VLAN, 'group')
|
vlan_count=count_related(VLAN, 'group')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.VLANGroupSerializer
|
serializer_class = serializers.VLANGroupSerializer
|
||||||
filterset_class = filters.VLANGroupFilterSet
|
filterset_class = filtersets.VLANGroupFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -301,7 +301,7 @@ class VLANViewSet(CustomFieldModelViewSet):
|
|||||||
prefix_count=count_related(Prefix, 'vlan')
|
prefix_count=count_related(Prefix, 'vlan')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.VLANSerializer
|
serializer_class = serializers.VLANSerializer
|
||||||
filterset_class = filters.VLANFilterSet
|
filterset_class = filtersets.VLANFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -313,4 +313,4 @@ class ServiceViewSet(ModelViewSet):
|
|||||||
'device', 'virtual_machine', 'tags', 'ipaddresses'
|
'device', 'virtual_machine', 'tags', 'ipaddresses'
|
||||||
)
|
)
|
||||||
serializer_class = serializers.ServiceSerializer
|
serializer_class = serializers.ServiceSerializer
|
||||||
filterset_class = filters.ServiceFilterSet
|
filterset_class = filtersets.ServiceFilterSet
|
||||||
|
@ -6,11 +6,11 @@ from django.db.models import Q
|
|||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
from dcim.models import Device, Interface, Region, Site, SiteGroup
|
from dcim.models import Device, Interface, Region, Site, SiteGroup
|
||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import TagFilter
|
||||||
from tenancy.filters import TenancyFilterSet
|
from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
|
||||||
|
from tenancy.filtersets import TenancyFilterSet
|
||||||
from utilities.filters import (
|
from utilities.filters import (
|
||||||
BaseFilterSet, ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NameSlugSearchFilterSet,
|
ContentTypeFilter, MultiValueCharFilter, MultiValueNumberFilter, NumericArrayFilter, TreeNodeMultipleChoiceFilter,
|
||||||
NumericArrayFilter, TagFilter, TreeNodeMultipleChoiceFilter,
|
|
||||||
)
|
)
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from .choices import *
|
from .choices import *
|
||||||
@ -31,7 +31,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class VRFFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -74,7 +74,7 @@ class VRFFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, C
|
|||||||
fields = ['id', 'name', 'rd', 'enforce_unique']
|
fields = ['id', 'name', 'rd', 'enforce_unique']
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class RouteTargetFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -116,14 +116,14 @@ class RouteTargetFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilt
|
|||||||
fields = ['id', 'name']
|
fields = ['id', 'name']
|
||||||
|
|
||||||
|
|
||||||
class RIRFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class RIRFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RIR
|
model = RIR
|
||||||
fields = ['id', 'name', 'slug', 'is_private', 'description']
|
fields = ['id', 'name', 'slug', 'is_private', 'description']
|
||||||
|
|
||||||
|
|
||||||
class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class AggregateFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -173,7 +173,7 @@ class AggregateFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter
|
|||||||
return queryset.none()
|
return queryset.none()
|
||||||
|
|
||||||
|
|
||||||
class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class RoleFilterSet(OrganizationalModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -184,7 +184,7 @@ class RoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilter
|
|||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class PrefixFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -369,7 +369,7 @@ class PrefixFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class IPAddressFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -535,7 +535,7 @@ class IPAddressFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilter
|
|||||||
return queryset.exclude(assigned_object_id__isnull=value)
|
return queryset.exclude(assigned_object_id__isnull=value)
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class VLANGroupFilterSet(OrganizationalModelFilterSet):
|
||||||
scope_type = ContentTypeFilter()
|
scope_type = ContentTypeFilter()
|
||||||
region = django_filters.NumberFilter(
|
region = django_filters.NumberFilter(
|
||||||
method='filter_scope'
|
method='filter_scope'
|
||||||
@ -570,7 +570,7 @@ class VLANGroupFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedF
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class VLANFilterSet(PrimaryModelFilterSet, TenancyFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
||||||
@ -666,7 +666,7 @@ class VLANFilterSet(BaseFilterSet, TenancyFilterSet, CustomFieldModelFilterSet,
|
|||||||
return queryset.get_for_virtualmachine(value)
|
return queryset.get_for_virtualmachine(value)
|
||||||
|
|
||||||
|
|
||||||
class ServiceFilterSet(BaseFilterSet, CreatedUpdatedFilterSet):
|
class ServiceFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
@ -29,7 +29,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class RIR(OrganizationalModel):
|
class RIR(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address
|
A Regional Internet Registry (RIR) is responsible for the allocation of a large portion of the global IP address
|
||||||
@ -184,7 +184,7 @@ class Aggregate(PrimaryModel):
|
|||||||
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
return int(float(child_prefixes.size) / self.prefix.size * 100)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class Role(OrganizationalModel):
|
class Role(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
|
A Role represents the functional role of a Prefix or VLAN; for example, "Customer," "Infrastructure," or
|
||||||
@ -426,19 +426,11 @@ class Prefix(PrimaryModel):
|
|||||||
child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()])
|
child_ips = netaddr.IPSet([ip.address.ip for ip in self.get_child_ips()])
|
||||||
available_ips = prefix - child_ips
|
available_ips = prefix - child_ips
|
||||||
|
|
||||||
# All IP addresses within a pool are considered usable
|
# IPv6, pool, or IPv4 /31 sets are fully usable
|
||||||
if self.is_pool:
|
if self.family == 6 or self.is_pool or self.prefix.prefixlen == 31:
|
||||||
return available_ips
|
return available_ips
|
||||||
|
|
||||||
# All IP addresses within a point-to-point prefix (IPv4 /31 or IPv6 /127) are considered usable
|
# For "normal" IPv4 prefixes, omit first and last addresses
|
||||||
if (
|
|
||||||
self.prefix.version == 4 and self.prefix.prefixlen == 31 # RFC 3021
|
|
||||||
) or (
|
|
||||||
self.prefix.version == 6 and self.prefix.prefixlen == 127 # RFC 6164
|
|
||||||
):
|
|
||||||
return available_ips
|
|
||||||
|
|
||||||
# Omit first and last IP address from the available set
|
|
||||||
available_ips -= netaddr.IPSet([
|
available_ips -= netaddr.IPSet([
|
||||||
netaddr.IPAddress(self.prefix.first),
|
netaddr.IPAddress(self.prefix.first),
|
||||||
netaddr.IPAddress(self.prefix.last),
|
netaddr.IPAddress(self.prefix.last),
|
||||||
|
@ -21,7 +21,7 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class VLANGroup(OrganizationalModel):
|
class VLANGroup(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
A VLAN group is an arbitrary collection of VLANs within which VLAN IDs and names must be unique.
|
||||||
@ -172,7 +172,7 @@ class VLAN(PrimaryModel):
|
|||||||
verbose_name_plural = 'VLANs'
|
verbose_name_plural = 'VLANs'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.display_name or super().__str__()
|
return f'{self.name} ({self.vid})'
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ipam:vlan', args=[self.pk])
|
return reverse('ipam:vlan', args=[self.pk])
|
||||||
@ -199,10 +199,6 @@ class VLAN(PrimaryModel):
|
|||||||
self.description,
|
self.description,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
return f'{self.name} ({self.vid})'
|
|
||||||
|
|
||||||
def get_status_class(self):
|
def get_status_class(self):
|
||||||
return VLANStatusChoices.CSS_CLASSES.get(self.status)
|
return VLANStatusChoices.CSS_CLASSES.get(self.status)
|
||||||
|
|
||||||
|
@ -71,7 +71,9 @@ class VRF(PrimaryModel):
|
|||||||
verbose_name_plural = 'VRFs'
|
verbose_name_plural = 'VRFs'
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.display_name or super().__str__()
|
if self.rd:
|
||||||
|
return f'{self.name} ({self.rd})'
|
||||||
|
return self.name
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return reverse('ipam:vrf', args=[self.pk])
|
return reverse('ipam:vrf', args=[self.pk])
|
||||||
@ -85,12 +87,6 @@ class VRF(PrimaryModel):
|
|||||||
self.description,
|
self.description,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
if self.rd:
|
|
||||||
return f'{self.name} ({self.rd})'
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class RouteTarget(PrimaryModel):
|
class RouteTarget(PrimaryModel):
|
||||||
|
@ -64,6 +64,7 @@ class VLANQuerySet(RestrictedQuerySet):
|
|||||||
return self.filter(
|
return self.filter(
|
||||||
Q(group__in=VLANGroup.objects.filter(q)) |
|
Q(group__in=VLANGroup.objects.filter(q)) |
|
||||||
Q(site=device.site) |
|
Q(site=device.site) |
|
||||||
|
Q(group__scope_id__isnull=True, site__isnull=True) | # Global group VLANs
|
||||||
Q(group__isnull=True, site__isnull=True) # Global VLANs
|
Q(group__isnull=True, site__isnull=True) # Global VLANs
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -104,6 +105,7 @@ class VLANQuerySet(RestrictedQuerySet):
|
|||||||
# Return all applicable VLANs
|
# Return all applicable VLANs
|
||||||
q = (
|
q = (
|
||||||
Q(group__in=vlan_groups) |
|
Q(group__in=vlan_groups) |
|
||||||
|
Q(group__scope_id__isnull=True, site__isnull=True) | # Global group VLANs
|
||||||
Q(group__isnull=True, site__isnull=True) # Global VLANs
|
Q(group__isnull=True, site__isnull=True) # Global VLANs
|
||||||
)
|
)
|
||||||
if vm.cluster.site:
|
if vm.cluster.site:
|
||||||
|
@ -22,7 +22,7 @@ class AppTest(APITestCase):
|
|||||||
|
|
||||||
class VRFTest(APIViewTestCases.APIViewTestCase):
|
class VRFTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = VRF
|
model = VRF
|
||||||
brief_fields = ['display', 'display_name', 'id', 'name', 'prefix_count', 'rd', 'url']
|
brief_fields = ['display', 'id', 'name', 'prefix_count', 'rd', 'url']
|
||||||
create_data = [
|
create_data = [
|
||||||
{
|
{
|
||||||
'name': 'VRF 4',
|
'name': 'VRF 4',
|
||||||
@ -421,7 +421,7 @@ class VLANGroupTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
class VLANTest(APIViewTestCases.APIViewTestCase):
|
class VLANTest(APIViewTestCases.APIViewTestCase):
|
||||||
model = VLAN
|
model = VLAN
|
||||||
brief_fields = ['display', 'display_name', 'id', 'name', 'url', 'vid']
|
brief_fields = ['display', 'id', 'name', 'url', 'vid']
|
||||||
bulk_update_data = {
|
bulk_update_data = {
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,14 @@ from django.test import TestCase
|
|||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
|
from dcim.models import Device, DeviceRole, DeviceType, Interface, Location, Manufacturer, Rack, Region, Site, SiteGroup
|
||||||
from ipam.choices import *
|
from ipam.choices import *
|
||||||
from ipam.filters import *
|
from ipam.filtersets import *
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
from ipam.models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
||||||
|
from utilities.testing import ChangeLoggedFilterSetTests
|
||||||
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
from virtualization.models import Cluster, ClusterGroup, ClusterType, VirtualMachine, VMInterface
|
||||||
from tenancy.models import Tenant, TenantGroup
|
from tenancy.models import Tenant, TenantGroup
|
||||||
|
|
||||||
|
|
||||||
class VRFTestCase(TestCase):
|
class VRFTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VRF.objects.all()
|
queryset = VRF.objects.all()
|
||||||
filterset = VRFFilterSet
|
filterset = VRFFilterSet
|
||||||
|
|
||||||
@ -53,10 +54,6 @@ class VRFTestCase(TestCase):
|
|||||||
vrfs[2].import_targets.add(route_targets[2])
|
vrfs[2].import_targets.add(route_targets[2])
|
||||||
vrfs[2].export_targets.add(route_targets[2])
|
vrfs[2].export_targets.add(route_targets[2])
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['VRF 1', 'VRF 2']}
|
params = {'name': ['VRF 1', 'VRF 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -100,7 +97,7 @@ class VRFTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetTestCase(TestCase):
|
class RouteTargetTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RouteTarget.objects.all()
|
queryset = RouteTarget.objects.all()
|
||||||
filterset = RouteTargetFilterSet
|
filterset = RouteTargetFilterSet
|
||||||
|
|
||||||
@ -149,10 +146,6 @@ class RouteTargetTestCase(TestCase):
|
|||||||
vrfs[1].import_targets.add(route_targets[4], route_targets[5])
|
vrfs[1].import_targets.add(route_targets[4], route_targets[5])
|
||||||
vrfs[1].export_targets.add(route_targets[6], route_targets[7])
|
vrfs[1].export_targets.add(route_targets[6], route_targets[7])
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['65000:1001', '65000:1002', '65000:1003']}
|
params = {'name': ['65000:1001', '65000:1002', '65000:1003']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
@ -186,7 +179,7 @@ class RouteTargetTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
|
||||||
|
|
||||||
class RIRTestCase(TestCase):
|
class RIRTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = RIR.objects.all()
|
queryset = RIR.objects.all()
|
||||||
filterset = RIRFilterSet
|
filterset = RIRFilterSet
|
||||||
|
|
||||||
@ -203,10 +196,6 @@ class RIRTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
RIR.objects.bulk_create(rirs)
|
RIR.objects.bulk_create(rirs)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['RIR 1', 'RIR 2']}
|
params = {'name': ['RIR 1', 'RIR 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -226,7 +215,7 @@ class RIRTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
|
|
||||||
class AggregateTestCase(TestCase):
|
class AggregateTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Aggregate.objects.all()
|
queryset = Aggregate.objects.all()
|
||||||
filterset = AggregateFilterSet
|
filterset = AggregateFilterSet
|
||||||
|
|
||||||
@ -265,10 +254,6 @@ class AggregateTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Aggregate.objects.bulk_create(aggregates)
|
Aggregate.objects.bulk_create(aggregates)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_family(self):
|
def test_family(self):
|
||||||
params = {'family': '4'}
|
params = {'family': '4'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
@ -304,7 +289,7 @@ class AggregateTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class RoleTestCase(TestCase):
|
class RoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Role.objects.all()
|
queryset = Role.objects.all()
|
||||||
filterset = RoleFilterSet
|
filterset = RoleFilterSet
|
||||||
|
|
||||||
@ -318,10 +303,6 @@ class RoleTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Role.objects.bulk_create(roles)
|
Role.objects.bulk_create(roles)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Role 1', 'Role 2']}
|
params = {'name': ['Role 1', 'Role 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -331,7 +312,7 @@ class RoleTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class PrefixTestCase(TestCase):
|
class PrefixTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Prefix.objects.all()
|
queryset = Prefix.objects.all()
|
||||||
filterset = PrefixFilterSet
|
filterset = PrefixFilterSet
|
||||||
|
|
||||||
@ -421,10 +402,6 @@ class PrefixTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Prefix.objects.bulk_create(prefixes)
|
Prefix.objects.bulk_create(prefixes)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_family(self):
|
def test_family(self):
|
||||||
params = {'family': '6'}
|
params = {'family': '6'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
||||||
@ -528,7 +505,7 @@ class PrefixTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class IPAddressTestCase(TestCase):
|
class IPAddressTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = IPAddress.objects.all()
|
queryset = IPAddress.objects.all()
|
||||||
filterset = IPAddressFilterSet
|
filterset = IPAddressFilterSet
|
||||||
|
|
||||||
@ -607,10 +584,6 @@ class IPAddressTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
IPAddress.objects.bulk_create(ipaddresses)
|
IPAddress.objects.bulk_create(ipaddresses)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_family(self):
|
def test_family(self):
|
||||||
params = {'family': '6'}
|
params = {'family': '6'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
|
||||||
@ -708,7 +681,7 @@ class IPAddressTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
|
||||||
class VLANGroupTestCase(TestCase):
|
class VLANGroupTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VLANGroup.objects.all()
|
queryset = VLANGroup.objects.all()
|
||||||
filterset = VLANGroupFilterSet
|
filterset = VLANGroupFilterSet
|
||||||
|
|
||||||
@ -751,10 +724,6 @@ class VLANGroupTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
VLANGroup.objects.bulk_create(vlan_groups)
|
VLANGroup.objects.bulk_create(vlan_groups)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['VLAN Group 1', 'VLAN Group 2']}
|
params = {'name': ['VLAN Group 1', 'VLAN Group 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -796,7 +765,7 @@ class VLANGroupTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
class VLANTestCase(TestCase):
|
class VLANTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
filterset = VLANFilterSet
|
filterset = VLANFilterSet
|
||||||
|
|
||||||
@ -965,10 +934,6 @@ class VLANTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
VLAN.objects.bulk_create(vlans)
|
VLAN.objects.bulk_create(vlans)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['VLAN 101', 'VLAN 102']}
|
params = {'name': ['VLAN 101', 'VLAN 102']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1041,7 +1006,7 @@ class VLANTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) # 5 scoped + 1 global
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6) # 5 scoped + 1 global
|
||||||
|
|
||||||
|
|
||||||
class ServiceTestCase(TestCase):
|
class ServiceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Service.objects.all()
|
queryset = Service.objects.all()
|
||||||
filterset = ServiceFilterSet
|
filterset = ServiceFilterSet
|
||||||
|
|
||||||
@ -1080,10 +1045,6 @@ class ServiceTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
Service.objects.bulk_create(services)
|
Service.objects.bulk_create(services)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:3]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Service 1', 'Service 2']}
|
params = {'name': ['Service 1', 'Service 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
@ -7,7 +7,7 @@ from netbox.views import generic
|
|||||||
from utilities.tables import paginate_table
|
from utilities.tables import paginate_table
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from virtualization.models import VirtualMachine, VMInterface
|
from virtualization.models import VirtualMachine, VMInterface
|
||||||
from . import filters, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .constants import *
|
from .constants import *
|
||||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
from .models import Aggregate, IPAddress, Prefix, RIR, Role, RouteTarget, Service, VLAN, VLANGroup, VRF
|
||||||
from .utils import add_available_ipaddresses, add_available_prefixes, add_available_vlans
|
from .utils import add_available_ipaddresses, add_available_prefixes, add_available_vlans
|
||||||
@ -19,7 +19,7 @@ from .utils import add_available_ipaddresses, add_available_prefixes, add_availa
|
|||||||
|
|
||||||
class VRFListView(generic.ObjectListView):
|
class VRFListView(generic.ObjectListView):
|
||||||
queryset = VRF.objects.all()
|
queryset = VRF.objects.all()
|
||||||
filterset = filters.VRFFilterSet
|
filterset = filtersets.VRFFilterSet
|
||||||
filterset_form = forms.VRFFilterForm
|
filterset_form = forms.VRFFilterForm
|
||||||
table = tables.VRFTable
|
table = tables.VRFTable
|
||||||
|
|
||||||
@ -65,14 +65,14 @@ class VRFBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class VRFBulkEditView(generic.BulkEditView):
|
class VRFBulkEditView(generic.BulkEditView):
|
||||||
queryset = VRF.objects.prefetch_related('tenant')
|
queryset = VRF.objects.prefetch_related('tenant')
|
||||||
filterset = filters.VRFFilterSet
|
filterset = filtersets.VRFFilterSet
|
||||||
table = tables.VRFTable
|
table = tables.VRFTable
|
||||||
form = forms.VRFBulkEditForm
|
form = forms.VRFBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class VRFBulkDeleteView(generic.BulkDeleteView):
|
class VRFBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VRF.objects.prefetch_related('tenant')
|
queryset = VRF.objects.prefetch_related('tenant')
|
||||||
filterset = filters.VRFFilterSet
|
filterset = filtersets.VRFFilterSet
|
||||||
table = tables.VRFTable
|
table = tables.VRFTable
|
||||||
|
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class VRFBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class RouteTargetListView(generic.ObjectListView):
|
class RouteTargetListView(generic.ObjectListView):
|
||||||
queryset = RouteTarget.objects.all()
|
queryset = RouteTarget.objects.all()
|
||||||
filterset = filters.RouteTargetFilterSet
|
filterset = filtersets.RouteTargetFilterSet
|
||||||
filterset_form = forms.RouteTargetFilterForm
|
filterset_form = forms.RouteTargetFilterForm
|
||||||
table = tables.RouteTargetTable
|
table = tables.RouteTargetTable
|
||||||
|
|
||||||
@ -123,14 +123,14 @@ class RouteTargetBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class RouteTargetBulkEditView(generic.BulkEditView):
|
class RouteTargetBulkEditView(generic.BulkEditView):
|
||||||
queryset = RouteTarget.objects.prefetch_related('tenant')
|
queryset = RouteTarget.objects.prefetch_related('tenant')
|
||||||
filterset = filters.RouteTargetFilterSet
|
filterset = filtersets.RouteTargetFilterSet
|
||||||
table = tables.RouteTargetTable
|
table = tables.RouteTargetTable
|
||||||
form = forms.RouteTargetBulkEditForm
|
form = forms.RouteTargetBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class RouteTargetBulkDeleteView(generic.BulkDeleteView):
|
class RouteTargetBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = RouteTarget.objects.prefetch_related('tenant')
|
queryset = RouteTarget.objects.prefetch_related('tenant')
|
||||||
filterset = filters.RouteTargetFilterSet
|
filterset = filtersets.RouteTargetFilterSet
|
||||||
table = tables.RouteTargetTable
|
table = tables.RouteTargetTable
|
||||||
|
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ class RIRListView(generic.ObjectListView):
|
|||||||
queryset = RIR.objects.annotate(
|
queryset = RIR.objects.annotate(
|
||||||
aggregate_count=count_related(Aggregate, 'rir')
|
aggregate_count=count_related(Aggregate, 'rir')
|
||||||
)
|
)
|
||||||
filterset = filters.RIRFilterSet
|
filterset = filtersets.RIRFilterSet
|
||||||
filterset_form = forms.RIRFilterForm
|
filterset_form = forms.RIRFilterForm
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
template_name = 'ipam/rir_list.html'
|
template_name = 'ipam/rir_list.html'
|
||||||
@ -184,7 +184,7 @@ class RIRBulkEditView(generic.BulkEditView):
|
|||||||
queryset = RIR.objects.annotate(
|
queryset = RIR.objects.annotate(
|
||||||
aggregate_count=count_related(Aggregate, 'rir')
|
aggregate_count=count_related(Aggregate, 'rir')
|
||||||
)
|
)
|
||||||
filterset = filters.RIRFilterSet
|
filterset = filtersets.RIRFilterSet
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
form = forms.RIRBulkEditForm
|
form = forms.RIRBulkEditForm
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ class RIRBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = RIR.objects.annotate(
|
queryset = RIR.objects.annotate(
|
||||||
aggregate_count=count_related(Aggregate, 'rir')
|
aggregate_count=count_related(Aggregate, 'rir')
|
||||||
)
|
)
|
||||||
filterset = filters.RIRFilterSet
|
filterset = filtersets.RIRFilterSet
|
||||||
table = tables.RIRTable
|
table = tables.RIRTable
|
||||||
|
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ class AggregateListView(generic.ObjectListView):
|
|||||||
queryset = Aggregate.objects.annotate(
|
queryset = Aggregate.objects.annotate(
|
||||||
child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
|
child_count=RawSQL('SELECT COUNT(*) FROM ipam_prefix WHERE ipam_prefix.prefix <<= ipam_aggregate.prefix', ())
|
||||||
)
|
)
|
||||||
filterset = filters.AggregateFilterSet
|
filterset = filtersets.AggregateFilterSet
|
||||||
filterset_form = forms.AggregateFilterForm
|
filterset_form = forms.AggregateFilterForm
|
||||||
table = tables.AggregateDetailTable
|
table = tables.AggregateDetailTable
|
||||||
template_name = 'ipam/aggregate_list.html'
|
template_name = 'ipam/aggregate_list.html'
|
||||||
@ -280,14 +280,14 @@ class AggregateBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class AggregateBulkEditView(generic.BulkEditView):
|
class AggregateBulkEditView(generic.BulkEditView):
|
||||||
queryset = Aggregate.objects.prefetch_related('rir')
|
queryset = Aggregate.objects.prefetch_related('rir')
|
||||||
filterset = filters.AggregateFilterSet
|
filterset = filtersets.AggregateFilterSet
|
||||||
table = tables.AggregateTable
|
table = tables.AggregateTable
|
||||||
form = forms.AggregateBulkEditForm
|
form = forms.AggregateBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class AggregateBulkDeleteView(generic.BulkDeleteView):
|
class AggregateBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Aggregate.objects.prefetch_related('rir')
|
queryset = Aggregate.objects.prefetch_related('rir')
|
||||||
filterset = filters.AggregateFilterSet
|
filterset = filtersets.AggregateFilterSet
|
||||||
table = tables.AggregateTable
|
table = tables.AggregateTable
|
||||||
|
|
||||||
|
|
||||||
@ -337,7 +337,7 @@ class RoleBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class RoleBulkEditView(generic.BulkEditView):
|
class RoleBulkEditView(generic.BulkEditView):
|
||||||
queryset = Role.objects.all()
|
queryset = Role.objects.all()
|
||||||
filterset = filters.RoleFilterSet
|
filterset = filtersets.RoleFilterSet
|
||||||
table = tables.RoleTable
|
table = tables.RoleTable
|
||||||
form = forms.RoleBulkEditForm
|
form = forms.RoleBulkEditForm
|
||||||
|
|
||||||
@ -353,7 +353,7 @@ class RoleBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PrefixListView(generic.ObjectListView):
|
class PrefixListView(generic.ObjectListView):
|
||||||
queryset = Prefix.objects.annotate_tree()
|
queryset = Prefix.objects.annotate_tree()
|
||||||
filterset = filters.PrefixFilterSet
|
filterset = filtersets.PrefixFilterSet
|
||||||
filterset_form = forms.PrefixFilterForm
|
filterset_form = forms.PrefixFilterForm
|
||||||
table = tables.PrefixDetailTable
|
table = tables.PrefixDetailTable
|
||||||
template_name = 'ipam/prefix_list.html'
|
template_name = 'ipam/prefix_list.html'
|
||||||
@ -493,14 +493,14 @@ class PrefixBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class PrefixBulkEditView(generic.BulkEditView):
|
class PrefixBulkEditView(generic.BulkEditView):
|
||||||
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||||
filterset = filters.PrefixFilterSet
|
filterset = filtersets.PrefixFilterSet
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
form = forms.PrefixBulkEditForm
|
form = forms.PrefixBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class PrefixBulkDeleteView(generic.BulkDeleteView):
|
class PrefixBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
queryset = Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||||
filterset = filters.PrefixFilterSet
|
filterset = filtersets.PrefixFilterSet
|
||||||
table = tables.PrefixTable
|
table = tables.PrefixTable
|
||||||
|
|
||||||
|
|
||||||
@ -510,7 +510,7 @@ class PrefixBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class IPAddressListView(generic.ObjectListView):
|
class IPAddressListView(generic.ObjectListView):
|
||||||
queryset = IPAddress.objects.all()
|
queryset = IPAddress.objects.all()
|
||||||
filterset = filters.IPAddressFilterSet
|
filterset = filtersets.IPAddressFilterSet
|
||||||
filterset_form = forms.IPAddressFilterForm
|
filterset_form = forms.IPAddressFilterForm
|
||||||
table = tables.IPAddressDetailTable
|
table = tables.IPAddressDetailTable
|
||||||
|
|
||||||
@ -613,7 +613,7 @@ class IPAddressAssignView(generic.ObjectView):
|
|||||||
|
|
||||||
addresses = self.queryset.prefetch_related('vrf', 'tenant')
|
addresses = self.queryset.prefetch_related('vrf', 'tenant')
|
||||||
# Limit to 100 results
|
# Limit to 100 results
|
||||||
addresses = filters.IPAddressFilterSet(request.POST, addresses).qs[:100]
|
addresses = filtersets.IPAddressFilterSet(request.POST, addresses).qs[:100]
|
||||||
table = tables.IPAddressAssignTable(addresses)
|
table = tables.IPAddressAssignTable(addresses)
|
||||||
|
|
||||||
return render(request, 'ipam/ipaddress_assign.html', {
|
return render(request, 'ipam/ipaddress_assign.html', {
|
||||||
@ -643,14 +643,14 @@ class IPAddressBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class IPAddressBulkEditView(generic.BulkEditView):
|
class IPAddressBulkEditView(generic.BulkEditView):
|
||||||
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
|
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
|
||||||
filterset = filters.IPAddressFilterSet
|
filterset = filtersets.IPAddressFilterSet
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
form = forms.IPAddressBulkEditForm
|
form = forms.IPAddressBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class IPAddressBulkDeleteView(generic.BulkDeleteView):
|
class IPAddressBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
|
queryset = IPAddress.objects.prefetch_related('vrf__tenant', 'tenant')
|
||||||
filterset = filters.IPAddressFilterSet
|
filterset = filtersets.IPAddressFilterSet
|
||||||
table = tables.IPAddressTable
|
table = tables.IPAddressTable
|
||||||
|
|
||||||
|
|
||||||
@ -662,7 +662,7 @@ class VLANGroupListView(generic.ObjectListView):
|
|||||||
queryset = VLANGroup.objects.annotate(
|
queryset = VLANGroup.objects.annotate(
|
||||||
vlan_count=count_related(VLAN, 'group')
|
vlan_count=count_related(VLAN, 'group')
|
||||||
)
|
)
|
||||||
filterset = filters.VLANGroupFilterSet
|
filterset = filtersets.VLANGroupFilterSet
|
||||||
filterset_form = forms.VLANGroupFilterForm
|
filterset_form = forms.VLANGroupFilterForm
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
|
|
||||||
@ -673,7 +673,7 @@ class VLANGroupView(generic.ObjectView):
|
|||||||
def get_extra_context(self, request, instance):
|
def get_extra_context(self, request, instance):
|
||||||
vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related(
|
vlans = VLAN.objects.restrict(request.user, 'view').filter(group=instance).prefetch_related(
|
||||||
Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
|
Prefetch('prefixes', queryset=Prefix.objects.restrict(request.user))
|
||||||
)
|
).order_by('vid')
|
||||||
vlans_count = vlans.count()
|
vlans_count = vlans.count()
|
||||||
vlans = add_available_vlans(instance, vlans)
|
vlans = add_available_vlans(instance, vlans)
|
||||||
|
|
||||||
@ -684,9 +684,17 @@ class VLANGroupView(generic.ObjectView):
|
|||||||
vlans_table.columns.hide('group')
|
vlans_table.columns.hide('group')
|
||||||
paginate_table(vlans_table, request)
|
paginate_table(vlans_table, request)
|
||||||
|
|
||||||
|
# Compile permissions list for rendering the object table
|
||||||
|
permissions = {
|
||||||
|
'add': request.user.has_perm('ipam.add_vlan'),
|
||||||
|
'change': request.user.has_perm('ipam.change_vlan'),
|
||||||
|
'delete': request.user.has_perm('ipam.delete_vlan'),
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'vlans_count': vlans_count,
|
'vlans_count': vlans_count,
|
||||||
'vlans_table': vlans_table,
|
'vlans_table': vlans_table,
|
||||||
|
'permissions': permissions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -710,7 +718,7 @@ class VLANGroupBulkEditView(generic.BulkEditView):
|
|||||||
queryset = VLANGroup.objects.annotate(
|
queryset = VLANGroup.objects.annotate(
|
||||||
vlan_count=count_related(VLAN, 'group')
|
vlan_count=count_related(VLAN, 'group')
|
||||||
)
|
)
|
||||||
filterset = filters.VLANGroupFilterSet
|
filterset = filtersets.VLANGroupFilterSet
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
form = forms.VLANGroupBulkEditForm
|
form = forms.VLANGroupBulkEditForm
|
||||||
|
|
||||||
@ -719,7 +727,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
queryset = VLANGroup.objects.annotate(
|
queryset = VLANGroup.objects.annotate(
|
||||||
vlan_count=count_related(VLAN, 'group')
|
vlan_count=count_related(VLAN, 'group')
|
||||||
)
|
)
|
||||||
filterset = filters.VLANGroupFilterSet
|
filterset = filtersets.VLANGroupFilterSet
|
||||||
table = tables.VLANGroupTable
|
table = tables.VLANGroupTable
|
||||||
|
|
||||||
|
|
||||||
@ -729,7 +737,7 @@ class VLANGroupBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class VLANListView(generic.ObjectListView):
|
class VLANListView(generic.ObjectListView):
|
||||||
queryset = VLAN.objects.all()
|
queryset = VLAN.objects.all()
|
||||||
filterset = filters.VLANFilterSet
|
filterset = filtersets.VLANFilterSet
|
||||||
filterset_form = forms.VLANFilterForm
|
filterset_form = forms.VLANFilterForm
|
||||||
table = tables.VLANDetailTable
|
table = tables.VLANDetailTable
|
||||||
|
|
||||||
@ -797,14 +805,14 @@ class VLANBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class VLANBulkEditView(generic.BulkEditView):
|
class VLANBulkEditView(generic.BulkEditView):
|
||||||
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||||
filterset = filters.VLANFilterSet
|
filterset = filtersets.VLANFilterSet
|
||||||
table = tables.VLANTable
|
table = tables.VLANTable
|
||||||
form = forms.VLANBulkEditForm
|
form = forms.VLANBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class VLANBulkDeleteView(generic.BulkDeleteView):
|
class VLANBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
queryset = VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role')
|
||||||
filterset = filters.VLANFilterSet
|
filterset = filtersets.VLANFilterSet
|
||||||
table = tables.VLANTable
|
table = tables.VLANTable
|
||||||
|
|
||||||
|
|
||||||
@ -814,7 +822,7 @@ class VLANBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ServiceListView(generic.ObjectListView):
|
class ServiceListView(generic.ObjectListView):
|
||||||
queryset = Service.objects.all()
|
queryset = Service.objects.all()
|
||||||
filterset = filters.ServiceFilterSet
|
filterset = filtersets.ServiceFilterSet
|
||||||
filterset_form = forms.ServiceFilterForm
|
filterset_form = forms.ServiceFilterForm
|
||||||
table = tables.ServiceTable
|
table = tables.ServiceTable
|
||||||
action_buttons = ('import', 'export')
|
action_buttons = ('import', 'export')
|
||||||
@ -855,12 +863,12 @@ class ServiceDeleteView(generic.ObjectDeleteView):
|
|||||||
|
|
||||||
class ServiceBulkEditView(generic.BulkEditView):
|
class ServiceBulkEditView(generic.BulkEditView):
|
||||||
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
filterset = filters.ServiceFilterSet
|
filterset = filtersets.ServiceFilterSet
|
||||||
table = tables.ServiceTable
|
table = tables.ServiceTable
|
||||||
form = forms.ServiceBulkEditForm
|
form = forms.ServiceBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class ServiceBulkDeleteView(generic.BulkDeleteView):
|
class ServiceBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
queryset = Service.objects.prefetch_related('device', 'virtual_machine')
|
||||||
filterset = filters.ServiceFilterSet
|
filterset = filtersets.ServiceFilterSet
|
||||||
table = tables.ServiceTable
|
table = tables.ServiceTable
|
||||||
|
@ -246,6 +246,9 @@ RQ_DEFAULT_TIMEOUT = 300
|
|||||||
# this setting is derived from the installed location.
|
# this setting is derived from the installed location.
|
||||||
# SCRIPTS_ROOT = '/opt/netbox/netbox/scripts'
|
# SCRIPTS_ROOT = '/opt/netbox/netbox/scripts'
|
||||||
|
|
||||||
|
# The name to use for the session cookie.
|
||||||
|
SESSION_COOKIE_NAME = 'sessionid'
|
||||||
|
|
||||||
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
|
# By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use
|
||||||
# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
|
# local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only
|
||||||
# database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
|
# database access.) Note that the user as which NetBox runs must have read and write permissions to this path.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from circuits.filters import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet
|
from circuits.filtersets import CircuitFilterSet, ProviderFilterSet, ProviderNetworkFilterSet
|
||||||
from circuits.models import Circuit, ProviderNetwork, Provider
|
from circuits.models import Circuit, ProviderNetwork, Provider
|
||||||
from circuits.tables import CircuitTable, ProviderNetworkTable, ProviderTable
|
from circuits.tables import CircuitTable, ProviderNetworkTable, ProviderTable
|
||||||
from dcim.filters import (
|
from dcim.filtersets import (
|
||||||
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
|
CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, LocationFilterSet,
|
||||||
SiteFilterSet, VirtualChassisFilterSet,
|
SiteFilterSet, VirtualChassisFilterSet,
|
||||||
)
|
)
|
||||||
@ -12,17 +12,17 @@ from dcim.tables import (
|
|||||||
CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable,
|
CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, LocationTable, SiteTable,
|
||||||
VirtualChassisTable,
|
VirtualChassisTable,
|
||||||
)
|
)
|
||||||
from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet
|
from ipam.filtersets import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet
|
||||||
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
|
from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF
|
||||||
from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
|
from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable
|
||||||
from secrets.filters import SecretFilterSet
|
from secrets.filtersets import SecretFilterSet
|
||||||
from secrets.models import Secret
|
from secrets.models import Secret
|
||||||
from secrets.tables import SecretTable
|
from secrets.tables import SecretTable
|
||||||
from tenancy.filters import TenantFilterSet
|
from tenancy.filtersets import TenantFilterSet
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
from tenancy.tables import TenantTable
|
from tenancy.tables import TenantTable
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from virtualization.filters import ClusterFilterSet, VirtualMachineFilterSet
|
from virtualization.filtersets import ClusterFilterSet, VirtualMachineFilterSet
|
||||||
from virtualization.models import Cluster, VirtualMachine
|
from virtualization.models import Cluster, VirtualMachine
|
||||||
from virtualization.tables import ClusterTable, VirtualMachineDetailTable
|
from virtualization.tables import ClusterTable, VirtualMachineDetailTable
|
||||||
|
|
||||||
|
238
netbox/netbox/filtersets.py
Normal file
238
netbox/netbox/filtersets.py
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import django_filters
|
||||||
|
from copy import deepcopy
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.db import models
|
||||||
|
from django_filters.utils import get_model_field, resolve_field
|
||||||
|
|
||||||
|
from dcim.forms import MACAddressField
|
||||||
|
from extras.choices import CustomFieldFilterLogicChoices
|
||||||
|
from extras.filters import CustomFieldFilter, TagFilter
|
||||||
|
from extras.models import CustomField
|
||||||
|
from utilities.constants import (
|
||||||
|
FILTER_CHAR_BASED_LOOKUP_MAP, FILTER_NEGATION_LOOKUP_MAP, FILTER_TREENODE_NEGATION_LOOKUP_MAP,
|
||||||
|
FILTER_NUMERIC_BASED_LOOKUP_MAP
|
||||||
|
)
|
||||||
|
from utilities import filters
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'BaseFilterSet',
|
||||||
|
'ChangeLoggedModelFilterSet',
|
||||||
|
'OrganizationalModelFilterSet',
|
||||||
|
'PrimaryModelFilterSet',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# FilterSets
|
||||||
|
#
|
||||||
|
|
||||||
|
class BaseFilterSet(django_filters.FilterSet):
|
||||||
|
"""
|
||||||
|
A base FilterSet which provides common functionality to all NetBox FilterSets
|
||||||
|
"""
|
||||||
|
FILTER_DEFAULTS = deepcopy(django_filters.filterset.FILTER_FOR_DBFIELD_DEFAULTS)
|
||||||
|
FILTER_DEFAULTS.update({
|
||||||
|
models.AutoField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.CharField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
models.DateField: {
|
||||||
|
'filter_class': filters.MultiValueDateFilter
|
||||||
|
},
|
||||||
|
models.DateTimeField: {
|
||||||
|
'filter_class': filters.MultiValueDateTimeFilter
|
||||||
|
},
|
||||||
|
models.DecimalField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.EmailField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
models.FloatField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.IntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.PositiveIntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.PositiveSmallIntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.SlugField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
models.SmallIntegerField: {
|
||||||
|
'filter_class': filters.MultiValueNumberFilter
|
||||||
|
},
|
||||||
|
models.TimeField: {
|
||||||
|
'filter_class': filters.MultiValueTimeFilter
|
||||||
|
},
|
||||||
|
models.URLField: {
|
||||||
|
'filter_class': filters.MultiValueCharFilter
|
||||||
|
},
|
||||||
|
MACAddressField: {
|
||||||
|
'filter_class': filters.MultiValueMACAddressFilter
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_filter_lookup_dict(existing_filter):
|
||||||
|
# Choose the lookup expression map based on the filter type
|
||||||
|
if isinstance(existing_filter, (
|
||||||
|
filters.MultiValueDateFilter,
|
||||||
|
filters.MultiValueDateTimeFilter,
|
||||||
|
filters.MultiValueNumberFilter,
|
||||||
|
filters.MultiValueTimeFilter
|
||||||
|
)):
|
||||||
|
lookup_map = FILTER_NUMERIC_BASED_LOOKUP_MAP
|
||||||
|
|
||||||
|
elif isinstance(existing_filter, (
|
||||||
|
filters.TreeNodeMultipleChoiceFilter,
|
||||||
|
)):
|
||||||
|
# TreeNodeMultipleChoiceFilter only support negation but must maintain the `in` lookup expression
|
||||||
|
lookup_map = FILTER_TREENODE_NEGATION_LOOKUP_MAP
|
||||||
|
|
||||||
|
elif isinstance(existing_filter, (
|
||||||
|
django_filters.ModelChoiceFilter,
|
||||||
|
django_filters.ModelMultipleChoiceFilter,
|
||||||
|
TagFilter
|
||||||
|
)) or existing_filter.extra.get('choices'):
|
||||||
|
# These filter types support only negation
|
||||||
|
lookup_map = FILTER_NEGATION_LOOKUP_MAP
|
||||||
|
|
||||||
|
elif isinstance(existing_filter, (
|
||||||
|
django_filters.filters.CharFilter,
|
||||||
|
django_filters.MultipleChoiceFilter,
|
||||||
|
filters.MultiValueCharFilter,
|
||||||
|
filters.MultiValueMACAddressFilter
|
||||||
|
)):
|
||||||
|
lookup_map = FILTER_CHAR_BASED_LOOKUP_MAP
|
||||||
|
|
||||||
|
else:
|
||||||
|
lookup_map = None
|
||||||
|
|
||||||
|
return lookup_map
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_filters(cls):
|
||||||
|
"""
|
||||||
|
Override filter generation to support dynamic lookup expressions for certain filter types.
|
||||||
|
|
||||||
|
For specific filter types, new filters are created based on defined lookup expressions in
|
||||||
|
the form `<field_name>__<lookup_expr>`
|
||||||
|
"""
|
||||||
|
filters = super().get_filters()
|
||||||
|
|
||||||
|
new_filters = {}
|
||||||
|
for existing_filter_name, existing_filter in filters.items():
|
||||||
|
# Loop over existing filters to extract metadata by which to create new filters
|
||||||
|
|
||||||
|
# If the filter makes use of a custom filter method or lookup expression skip it
|
||||||
|
# as we cannot sanely handle these cases in a generic mannor
|
||||||
|
if existing_filter.method is not None or existing_filter.lookup_expr not in ['exact', 'in']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Choose the lookup expression map based on the filter type
|
||||||
|
lookup_map = cls._get_filter_lookup_dict(existing_filter)
|
||||||
|
if lookup_map is None:
|
||||||
|
# Do not augment this filter type with more lookup expressions
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get properties of the existing filter for later use
|
||||||
|
field_name = existing_filter.field_name
|
||||||
|
field = get_model_field(cls._meta.model, field_name)
|
||||||
|
|
||||||
|
# Create new filters for each lookup expression in the map
|
||||||
|
for lookup_name, lookup_expr in lookup_map.items():
|
||||||
|
new_filter_name = '{}__{}'.format(existing_filter_name, lookup_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if existing_filter_name in cls.declared_filters:
|
||||||
|
# The filter field has been explicity defined on the filterset class so we must manually
|
||||||
|
# create the new filter with the same type because there is no guarantee the defined type
|
||||||
|
# is the same as the default type for the field
|
||||||
|
resolve_field(field, lookup_expr) # Will raise FieldLookupError if the lookup is invalid
|
||||||
|
new_filter = type(existing_filter)(
|
||||||
|
field_name=field_name,
|
||||||
|
lookup_expr=lookup_expr,
|
||||||
|
label=existing_filter.label,
|
||||||
|
exclude=existing_filter.exclude,
|
||||||
|
distinct=existing_filter.distinct,
|
||||||
|
**existing_filter.extra
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# The filter field is listed in Meta.fields so we can safely rely on default behaviour
|
||||||
|
# Will raise FieldLookupError if the lookup is invalid
|
||||||
|
new_filter = cls.filter_for_field(field, field_name, lookup_expr)
|
||||||
|
except django_filters.exceptions.FieldLookupError:
|
||||||
|
# The filter could not be created because the lookup expression is not supported on the field
|
||||||
|
continue
|
||||||
|
|
||||||
|
if lookup_name.startswith('n'):
|
||||||
|
# This is a negation filter which requires a queryset.exclude() clause
|
||||||
|
# Of course setting the negation of the existing filter's exclude attribute handles both cases
|
||||||
|
new_filter.exclude = not existing_filter.exclude
|
||||||
|
|
||||||
|
new_filters[new_filter_name] = new_filter
|
||||||
|
|
||||||
|
filters.update(new_filters)
|
||||||
|
return filters
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeLoggedModelFilterSet(BaseFilterSet):
|
||||||
|
created = django_filters.DateFilter()
|
||||||
|
created__gte = django_filters.DateFilter(
|
||||||
|
field_name='created',
|
||||||
|
lookup_expr='gte'
|
||||||
|
)
|
||||||
|
created__lte = django_filters.DateFilter(
|
||||||
|
field_name='created',
|
||||||
|
lookup_expr='lte'
|
||||||
|
)
|
||||||
|
last_updated = django_filters.DateTimeFilter()
|
||||||
|
last_updated__gte = django_filters.DateTimeFilter(
|
||||||
|
field_name='last_updated',
|
||||||
|
lookup_expr='gte'
|
||||||
|
)
|
||||||
|
last_updated__lte = django_filters.DateTimeFilter(
|
||||||
|
field_name='last_updated',
|
||||||
|
lookup_expr='lte'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PrimaryModelFilterSet(ChangeLoggedModelFilterSet):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Dynamically add a Filter for each CustomField applicable to the parent model
|
||||||
|
custom_fields = CustomField.objects.filter(
|
||||||
|
content_types=ContentType.objects.get_for_model(self._meta.model)
|
||||||
|
).exclude(
|
||||||
|
filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED
|
||||||
|
)
|
||||||
|
for cf in custom_fields:
|
||||||
|
self.filters['cf_{}'.format(cf.name)] = CustomFieldFilter(field_name=cf.name, custom_field=cf)
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationalModelFilterSet(PrimaryModelFilterSet):
|
||||||
|
"""
|
||||||
|
A base class for adding the search method to models which only expose the `name` and `slug` fields
|
||||||
|
"""
|
||||||
|
q = django_filters.CharFilter(
|
||||||
|
method='search',
|
||||||
|
label='Search',
|
||||||
|
)
|
||||||
|
|
||||||
|
def search(self, queryset, name, value):
|
||||||
|
if not value.strip():
|
||||||
|
return queryset
|
||||||
|
return queryset.filter(
|
||||||
|
models.Q(name__icontains=value) |
|
||||||
|
models.Q(slug__icontains=value)
|
||||||
|
)
|
@ -114,6 +114,7 @@ REPORTS_ROOT = getattr(configuration, 'REPORTS_ROOT', os.path.join(BASE_DIR, 're
|
|||||||
RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300)
|
RQ_DEFAULT_TIMEOUT = getattr(configuration, 'RQ_DEFAULT_TIMEOUT', 300)
|
||||||
SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/')
|
SCRIPTS_ROOT = getattr(configuration, 'SCRIPTS_ROOT', os.path.join(BASE_DIR, 'scripts')).rstrip('/')
|
||||||
SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None)
|
SESSION_FILE_PATH = getattr(configuration, 'SESSION_FILE_PATH', None)
|
||||||
|
SESSION_COOKIE_NAME = getattr(configuration, 'SESSION_COOKIE_NAME', 'sessionid')
|
||||||
SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d')
|
SHORT_DATE_FORMAT = getattr(configuration, 'SHORT_DATE_FORMAT', 'Y-m-d')
|
||||||
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
SHORT_DATETIME_FORMAT = getattr(configuration, 'SHORT_DATETIME_FORMAT', 'Y-m-d H:i')
|
||||||
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
|
SHORT_TIME_FORMAT = getattr(configuration, 'SHORT_TIME_FORMAT', 'H:i:s')
|
||||||
|
@ -109,12 +109,13 @@ class HomeView(View):
|
|||||||
for section_label, section_items in sections:
|
for section_label, section_items in sections:
|
||||||
stat = {"label": section_label, "items": []}
|
stat = {"label": section_label, "items": []}
|
||||||
for perm, item_label, description, get_count in section_items:
|
for perm, item_label, description, get_count in section_items:
|
||||||
|
app, scope = perm.split(".")
|
||||||
|
url = ":".join((app, scope.replace("view_", "") + "_list"))
|
||||||
|
item = {"label": item_label, "description": description, "count": None, "url": url, "disabled": True}
|
||||||
if perm in perms:
|
if perm in perms:
|
||||||
app, scope = perm.split(".")
|
item["count"] = get_count()
|
||||||
url = ":".join((app, scope.replace("view_", "") + "_list"))
|
item["disabled"] = False
|
||||||
stat["items"].append(
|
stat["items"].append(item)
|
||||||
{"label": item_label, "description": description, "count": get_count(), "url": url}
|
|
||||||
)
|
|
||||||
stats.append(stat)
|
stats.append(stat)
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
|
10
netbox/project-static/_dark.scss
Normal file
10
netbox/project-static/_dark.scss
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Entry for netbox-dark.css stylesheet.
|
||||||
|
|
||||||
|
body[data-netbox-color-mode='dark'] {
|
||||||
|
// Imports are scoped under the body when its data-netbox-color-mode attribute is set to 'dark'.
|
||||||
|
@import './theme-dark.scss';
|
||||||
|
@import './bootstrap.scss';
|
||||||
|
@import './select.scss';
|
||||||
|
@import './flatpickr-dark.scss';
|
||||||
|
@import './netbox.scss';
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
// Stylesheet for rendering SVG rack elevations
|
// Entry for rack_elevation.css stylesheet.
|
||||||
|
|
||||||
@import './theme-light.scss';
|
@import './theme-light.scss';
|
||||||
|
|
||||||
* {
|
* {
|
4
netbox/project-static/_external.scss
Normal file
4
netbox/project-static/_external.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// Entry for all 3rd party library imports that do not rely on Bootstrap or NetBox styles.
|
||||||
|
|
||||||
|
@import '@mdi/font/css/materialdesignicons.min.css';
|
||||||
|
@import 'flatpickr/dist/flatpickr.css';
|
6
netbox/project-static/_light.scss
Normal file
6
netbox/project-static/_light.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Entry for netbox-light.css stylesheet.
|
||||||
|
|
||||||
|
@import './theme-light.scss';
|
||||||
|
@import './bootstrap.scss';
|
||||||
|
@import './select.scss';
|
||||||
|
@import './netbox.scss';
|
@ -27,8 +27,10 @@ if (args.includes('--no-cache')) {
|
|||||||
// Style (SCSS) bundle jobs. Generally, everything should be bundled into netbox.css from main.scss
|
// Style (SCSS) bundle jobs. Generally, everything should be bundled into netbox.css from main.scss
|
||||||
// unless there is a specific reason to do otherwise.
|
// unless there is a specific reason to do otherwise.
|
||||||
const styles = [
|
const styles = [
|
||||||
['main.scss', 'netbox.css'],
|
['_external.scss', 'netbox-external.css'],
|
||||||
['rack_elevation.scss', 'rack_elevation.css'],
|
['_light.scss', 'netbox-light.css'],
|
||||||
|
['_dark.scss', 'netbox-dark.css'],
|
||||||
|
['_elevations.scss', 'rack_elevation.css'],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Script (JavaScript) bundle jobs. Generally, everything should be bundled into netbox.js from
|
// Script (JavaScript) bundle jobs. Generally, everything should be bundled into netbox.js from
|
||||||
|
BIN
netbox/project-static/dist/netbox-dark.css
vendored
Normal file
BIN
netbox/project-static/dist/netbox-dark.css
vendored
Normal file
Binary file not shown.
1
netbox/project-static/dist/netbox-dark.css.map
vendored
Normal file
1
netbox/project-static/dist/netbox-dark.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
netbox/project-static/dist/netbox-external.css
vendored
Normal file
BIN
netbox/project-static/dist/netbox-external.css
vendored
Normal file
Binary file not shown.
1
netbox/project-static/dist/netbox-external.css.map
vendored
Normal file
1
netbox/project-static/dist/netbox-external.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
netbox/project-static/dist/netbox-light.css
vendored
Normal file
BIN
netbox/project-static/dist/netbox-light.css
vendored
Normal file
Binary file not shown.
1
netbox/project-static/dist/netbox-light.css.map
vendored
Normal file
1
netbox/project-static/dist/netbox-light.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
netbox/project-static/dist/netbox.css
vendored
BIN
netbox/project-static/dist/netbox.css
vendored
Binary file not shown.
1
netbox/project-static/dist/netbox.css.map
vendored
1
netbox/project-static/dist/netbox.css.map
vendored
File diff suppressed because one or more lines are too long
BIN
netbox/project-static/dist/netbox.js
vendored
BIN
netbox/project-static/dist/netbox.js
vendored
Binary file not shown.
BIN
netbox/project-static/dist/netbox.js.map
vendored
BIN
netbox/project-static/dist/netbox.js.map
vendored
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -1,27 +0,0 @@
|
|||||||
// Main Entry Point for all Netbox Styles.
|
|
||||||
// Note: The order of these imports is critical for proper inheritance.
|
|
||||||
|
|
||||||
// Light Mode Styles.
|
|
||||||
@import './theme-light.scss';
|
|
||||||
@import './bootstrap.scss';
|
|
||||||
|
|
||||||
@import '@mdi/font/css/materialdesignicons.min.css';
|
|
||||||
|
|
||||||
@import './select.scss';
|
|
||||||
@import 'flatpickr/dist/flatpickr.css';
|
|
||||||
|
|
||||||
@import './netbox.scss';
|
|
||||||
|
|
||||||
// Dark Mode Styles.
|
|
||||||
body[data-netbox-color-mode='dark'] {
|
|
||||||
@import './theme-dark.scss';
|
|
||||||
@import './bootstrap.scss';
|
|
||||||
|
|
||||||
@import '@mdi/font/css/materialdesignicons.min.css';
|
|
||||||
|
|
||||||
@import './select.scss';
|
|
||||||
@import 'flatpickr/dist/flatpickr.css';
|
|
||||||
@import './flatpickr-dark.scss';
|
|
||||||
|
|
||||||
@import './netbox.scss';
|
|
||||||
}
|
|
@ -11,7 +11,7 @@
|
|||||||
--nbx-sidebar-title-color: #{$text-muted};
|
--nbx-sidebar-title-color: #{$text-muted};
|
||||||
--nbx-breadcrumb-bg: #{$light};
|
--nbx-breadcrumb-bg: #{$light};
|
||||||
--nbx-body-bg: #{$white};
|
--nbx-body-bg: #{$white};
|
||||||
--nbx-body-color: #{$black};
|
--nbx-body-color: #{$gray-800};
|
||||||
--nbx-pre-bg: #{$gray-100};
|
--nbx-pre-bg: #{$gray-100};
|
||||||
--nbx-pre-border-color: #{$gray-600};
|
--nbx-pre-border-color: #{$gray-600};
|
||||||
--nbx-change-added: #{rgba($green, 0.4)};
|
--nbx-change-added: #{rgba($green, 0.4)};
|
||||||
@ -20,6 +20,9 @@
|
|||||||
--nbx-cable-node-border-color: #{$gray-200};
|
--nbx-cable-node-border-color: #{$gray-200};
|
||||||
--nbx-cable-termination-bg: #{$gray-200};
|
--nbx-cable-termination-bg: #{$gray-200};
|
||||||
--nbx-cable-termination-border-color: #{$gray-300};
|
--nbx-cable-termination-border-color: #{$gray-300};
|
||||||
|
--nbx-search-filter-border-left-color: #{$gray-300};
|
||||||
|
--nbx-color-mode-toggle-color: #{$primary};
|
||||||
|
--nbx-stat-badge-bg: #{$gray-600};
|
||||||
|
|
||||||
body[data-netbox-color-mode='dark'] {
|
body[data-netbox-color-mode='dark'] {
|
||||||
--nbx-logo-color-1: #{$white};
|
--nbx-logo-color-1: #{$white};
|
||||||
@ -30,7 +33,7 @@
|
|||||||
--nbx-sidebar-title-color: #{$gray-300};
|
--nbx-sidebar-title-color: #{$gray-300};
|
||||||
--nbx-breadcrumb-bg: #{$gray-800};
|
--nbx-breadcrumb-bg: #{$gray-800};
|
||||||
--nbx-body-bg: #{$gray-900};
|
--nbx-body-bg: #{$gray-900};
|
||||||
--nbx-body-color: #{$white};
|
--nbx-body-color: #{$gray-50};
|
||||||
--nbx-pre-bg: #{$gray-700};
|
--nbx-pre-bg: #{$gray-700};
|
||||||
--nbx-pre-border-color: #{$gray-600};
|
--nbx-pre-border-color: #{$gray-600};
|
||||||
--nbx-change-added: #{rgba($green-300, 0.4)};
|
--nbx-change-added: #{rgba($green-300, 0.4)};
|
||||||
@ -39,6 +42,9 @@
|
|||||||
--nbx-cable-node-border-color: #{$gray-600};
|
--nbx-cable-node-border-color: #{$gray-600};
|
||||||
--nbx-cable-termination-bg: #{$gray-800};
|
--nbx-cable-termination-bg: #{$gray-800};
|
||||||
--nbx-cable-termination-border-color: #{$gray-700};
|
--nbx-cable-termination-border-color: #{$gray-700};
|
||||||
|
--nbx-search-filter-border-left-color: #{$gray-600};
|
||||||
|
--nbx-color-mode-toggle-color: #{$yellow-300};
|
||||||
|
--nbx-stat-badge-bg: #{$gray-600};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +52,31 @@
|
|||||||
transition: background-color, color 0.15s ease-in-out;
|
transition: background-color, color 0.15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-xs {
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
line-height: $line-height-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically space out adjacent columns.
|
||||||
|
.col:not(:last-child):not(:only-child) {
|
||||||
|
margin-bottom: $spacer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use proper contrasting color for badge & progress-bar foreground color.
|
||||||
|
@each $color, $value in $theme-colors {
|
||||||
|
.badge,
|
||||||
|
.progress-bar {
|
||||||
|
&.bg-#{$color} {
|
||||||
|
color: color-contrast($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure progress bars (utilization graph) in tables aren't too narrow to display the percentage.
|
||||||
|
table td > .progress {
|
||||||
|
min-width: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--nbx-body-bg);
|
background-color: var(--nbx-body-bg);
|
||||||
color: var(--nbx-body-color);
|
color: var(--nbx-body-color);
|
||||||
@ -58,9 +89,16 @@ body {
|
|||||||
fill: #1685fc;
|
fill: #1685fc;
|
||||||
stroke: #1685fc;
|
stroke: #1685fc;
|
||||||
}
|
}
|
||||||
|
span.badge.stat-badge {
|
||||||
|
margin-left: map.get($spacers, 2);
|
||||||
|
background-color: var(--nbx-stat-badge-bg);
|
||||||
|
}
|
||||||
|
|
||||||
&[data-netbox-color-mode='light'] {
|
&[data-netbox-color-mode='light'] {
|
||||||
.btn.btn-primary {
|
.btn.btn-primary,
|
||||||
color: $white;
|
.progress-bar.bg-primary,
|
||||||
|
.badge.bg-primary {
|
||||||
|
color: $gray-50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&[data-netbox-color-mode='dark'] {
|
&[data-netbox-color-mode='dark'] {
|
||||||
@ -92,6 +130,32 @@ body {
|
|||||||
stroke: $gray-200;
|
stroke: $gray-200;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
& table,
|
||||||
|
&[data-netbox-color-mode] table {
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.table > :not(caption) > * > * {
|
||||||
|
padding-left: $table-cell-padding-x-sm !important;
|
||||||
|
padding-right: $table-cell-padding-x-sm !important;
|
||||||
|
}
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
font-size: $font-size-xs;
|
||||||
|
line-height: $line-height-sm;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
&.attr-table {
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
font-size: $font-size-sm;
|
||||||
|
line-height: $line-height-sm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.title-container {
|
div.title-container {
|
||||||
@ -113,17 +177,35 @@ div.title-container {
|
|||||||
|
|
||||||
nav.search {
|
nav.search {
|
||||||
background-color: var(--nbx-body-bg);
|
background-color: var(--nbx-body-bg);
|
||||||
|
form button.dropdown-toggle {
|
||||||
|
border-color: $input-border-color;
|
||||||
|
font-weight: $input-group-addon-font-weight;
|
||||||
|
line-height: $input-line-height;
|
||||||
|
color: $input-group-addon-color;
|
||||||
|
background-color: $input-group-addon-bg;
|
||||||
|
border: $input-border-width solid $input-group-addon-border-color;
|
||||||
|
@include border-radius($input-border-radius);
|
||||||
|
border-left: 1px solid var(--nbx-search-filter-border-left-color);
|
||||||
|
&:focus {
|
||||||
|
box-shadow: unset !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main.login-container {
|
main.login-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 4rem);
|
height: calc(100vh - 4rem);
|
||||||
width: 100vw;
|
width: 100%;
|
||||||
|
max-width: 100vw;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
|
|
||||||
|
& + footer.footer button.color-mode-toggle {
|
||||||
|
color: var(--nbx-color-mode-toggle-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer.login-footer {
|
footer.login-footer {
|
||||||
@ -161,7 +243,8 @@ h3.accordion-item-title,
|
|||||||
h4.accordion-item-title,
|
h4.accordion-item-title,
|
||||||
h5.accordion-item-title,
|
h5.accordion-item-title,
|
||||||
h6.accordion-item-title {
|
h6.accordion-item-title {
|
||||||
padding: 0 0.5rem;
|
// padding: 0 0.5rem;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--nbx-sidebar-title-color);
|
color: var(--nbx-sidebar-title-color);
|
||||||
@ -201,12 +284,6 @@ li.dropdown-item.dropdown-item-btns {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.sidebar {
|
|
||||||
top: 5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-sticky {
|
.sidebar-sticky {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -225,15 +302,26 @@ li.dropdown-item.dropdown-item-btns {
|
|||||||
nav.nav.nav-pills {
|
nav.nav.nav-pills {
|
||||||
.nav-item.nav-link {
|
.nav-item.nav-link {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
font-size: $font-size-base;
|
// font-size: $font-size-base;
|
||||||
|
font-size: $font-size-sm;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $body-color;
|
// color: $body-color;
|
||||||
background-color: var(--nbx-sidebar-link-hover-bg);
|
// background-color: var(--nbx-sidebar-link-hover-bg);
|
||||||
|
background-color: $accordion-button-active-bg;
|
||||||
|
color: $accordion-button-active-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prevent scrolling of body content when nav menu is open on mobile.
|
||||||
|
.sidebar.collapse.show ~ .content-container {
|
||||||
|
@media (max-width: map.get($grid-breakpoints, 'md')) {
|
||||||
|
position: fixed;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -242,26 +330,51 @@ nav.nav.nav-pills {
|
|||||||
z-index: 100; /* Behind the navbar */
|
z-index: 100; /* Behind the navbar */
|
||||||
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1);
|
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1);
|
||||||
background-color: var(--nbx-sidebar-bg);
|
background-color: var(--nbx-sidebar-bg);
|
||||||
.sidebar-nav-link {
|
|
||||||
color: var(--nbx-sidebar-link-color);
|
@media (max-width: map.get($grid-breakpoints, 'md')) {
|
||||||
|
top: 8.125rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.accordion-item > a.accordion-button.nav-link {
|
||||||
|
&:hover {
|
||||||
|
// color: $body-color;
|
||||||
|
// background-color: var(--nbx-sidebar-link-hover-bg);
|
||||||
|
color: $accordion-button-active-color;
|
||||||
|
background-color: $accordion-button-active-bg;
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
border-color: unset;
|
||||||
|
box-shadow: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.accordion-body {
|
.accordion-body {
|
||||||
max-height: calc(100vh - 24rem);
|
max-height: calc(100vh - 24rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
.nav-item {
|
.nav-item {
|
||||||
.nav-link {
|
.nav-link {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.6rem;
|
||||||
font-size: $font-size-base;
|
// font-size: $font-size-base;
|
||||||
|
font-size: $font-size-sm;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $body-color;
|
// color: $body-color;
|
||||||
background-color: var(--nbx-sidebar-link-hover-bg);
|
// background-color: var(--nbx-sidebar-link-hover-bg);
|
||||||
|
color: $accordion-button-active-color;
|
||||||
|
background-color: $accordion-button-active-bg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Ensure navigation accounts for the height of the header on mobile when nav is expanded.
|
||||||
|
&.collapse.show div.position-sticky {
|
||||||
|
@media (max-width: map.get($grid-breakpoints, 'md')) {
|
||||||
|
height: calc(100vh - 16.125rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
div.position-sticky {
|
div.position-sticky {
|
||||||
height: calc(100% - 8rem);
|
height: calc(100vh - 8rem);
|
||||||
}
|
}
|
||||||
div.sidebar-bottom {
|
div.sidebar-bottom {
|
||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
@ -499,10 +612,6 @@ table tbody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
table td,
|
|
||||||
table th {
|
|
||||||
font-size: $font-size-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cable Tracing
|
// Cable Tracing
|
||||||
.cable-trace {
|
.cable-trace {
|
||||||
|
@ -54,7 +54,6 @@
|
|||||||
"plugins": [
|
"plugins": [
|
||||||
"@babel/plugin-proposal-optional-chaining",
|
"@babel/plugin-proposal-optional-chaining",
|
||||||
"@babel/proposal-class-properties",
|
"@babel/proposal-class-properties",
|
||||||
"@babel/proposal-object-rest-spread",
|
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator"
|
"@babel/plugin-proposal-nullish-coalescing-operator"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -60,6 +60,7 @@ div.form-floating div.ss-main div.ss-multi-selected {
|
|||||||
div.ss-multi-selected .ss-values .ss-disabled,
|
div.ss-multi-selected .ss-values .ss-disabled,
|
||||||
div.ss-single-selected span.placeholder .ss-disabled {
|
div.ss-single-selected span.placeholder .ss-disabled {
|
||||||
color: var(--nbx-select-placeholder-color);
|
color: var(--nbx-select-placeholder-color);
|
||||||
|
font-size: $font-size-xs;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ss-single-selected {
|
.ss-single-selected {
|
||||||
@ -109,12 +110,19 @@ div.form-floating div.ss-main div.ss-multi-selected {
|
|||||||
border-bottom-left-radius: $form-select-border-radius;
|
border-bottom-left-radius: $form-select-border-radius;
|
||||||
border-bottom-right-radius: $form-select-border-radius;
|
border-bottom-right-radius: $form-select-border-radius;
|
||||||
}
|
}
|
||||||
|
.ss-option.ss-disabled {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
.ss-option.ss-disabled:hover {
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
border-bottom-left-radius: $form-select-border-radius;
|
border-bottom-left-radius: $form-select-border-radius;
|
||||||
border-bottom-right-radius: $form-select-border-radius;
|
border-bottom-right-radius: $form-select-border-radius;
|
||||||
.ss-search {
|
.ss-search {
|
||||||
input[type='search'] {
|
input[type='search'] {
|
||||||
background-color: $form-select-bg;
|
background-color: $form-select-bg;
|
||||||
|
color: $input-color;
|
||||||
border: $form-select-border-width solid $form-select-border-color;
|
border: $form-select-border-width solid $form-select-border-color;
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: $form-select-focus-border-color;
|
border-color: $form-select-focus-border-color;
|
||||||
|
@ -265,6 +265,19 @@ function initSelectAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handlePerPageSelect(event: Event) {
|
||||||
|
const select = event.currentTarget as HTMLSelectElement;
|
||||||
|
if (select.form !== null) {
|
||||||
|
select.form.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPerPage() {
|
||||||
|
for (const element of getElements<HTMLSelectElement>('select.per-page')) {
|
||||||
|
element.addEventListener('change', handlePerPageSelect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function initButtons() {
|
export function initButtons() {
|
||||||
for (const func of [
|
for (const func of [
|
||||||
initRackElevation,
|
initRackElevation,
|
||||||
@ -272,6 +285,7 @@ export function initButtons() {
|
|||||||
initReslug,
|
initReslug,
|
||||||
initSelectAll,
|
initSelectAll,
|
||||||
initPreferenceUpdate,
|
initPreferenceUpdate,
|
||||||
|
initPerPage,
|
||||||
]) {
|
]) {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,44 @@
|
|||||||
import debounce from 'just-debounce-it';
|
import debounce from 'just-debounce-it';
|
||||||
import { getElements, getRowValues, findFirstAdjacent } from './util';
|
import { getElements, getRowValues, findFirstAdjacent, isTruthy } from './util';
|
||||||
|
|
||||||
interface SearchFilterButton extends EventTarget {
|
/**
|
||||||
dataset: { searchValue: string };
|
* Change the display value and hidden input values of the search filter based on dropdown
|
||||||
}
|
* selection.
|
||||||
|
*
|
||||||
function isSearchButton(el: any): el is SearchFilterButton {
|
* @param event "click" event for each dropdown item.
|
||||||
return el?.dataset?.searchValue ?? null !== null;
|
* @param button Each dropdown item element.
|
||||||
|
*/
|
||||||
|
function handleSearchDropdownClick(event: Event, button: HTMLButtonElement) {
|
||||||
|
const dropdown = event.currentTarget as HTMLButtonElement;
|
||||||
|
const selectedValue = findFirstAdjacent<HTMLSpanElement>(dropdown, 'span.search-obj-selected');
|
||||||
|
const selectedType = findFirstAdjacent<HTMLInputElement>(dropdown, 'input.search-obj-type');
|
||||||
|
const searchValue = dropdown.getAttribute('data-search-value');
|
||||||
|
let selected = '' as string;
|
||||||
|
|
||||||
|
if (selectedValue !== null && selectedType !== null) {
|
||||||
|
if (isTruthy(searchValue) && selected !== searchValue) {
|
||||||
|
selected = searchValue;
|
||||||
|
selectedValue.innerHTML = button.textContent ?? 'Error';
|
||||||
|
selectedType.value = searchValue;
|
||||||
|
} else {
|
||||||
|
selected = '';
|
||||||
|
selectedValue.innerHTML = 'All Objects';
|
||||||
|
selectedType.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Search Bar Elements.
|
||||||
|
*/
|
||||||
function initSearchBar() {
|
function initSearchBar() {
|
||||||
const dropdown = document.getElementById('object-type-selector');
|
for (const dropdown of getElements<HTMLUListElement>(
|
||||||
const selectedValue = document.getElementById('selected-value') as HTMLSpanElement;
|
'div.search-container ul.search-obj-selector',
|
||||||
const selectedType = document.getElementById('search-obj-type') as HTMLInputElement;
|
)) {
|
||||||
let selected = '';
|
for (const button of dropdown.querySelectorAll<HTMLButtonElement>(
|
||||||
|
'li > button.dropdown-item',
|
||||||
if (dropdown !== null) {
|
)) {
|
||||||
const buttons = dropdown.querySelectorAll('li > button.dropdown-item');
|
button.addEventListener('click', event => handleSearchDropdownClick(event, button));
|
||||||
for (const button of buttons) {
|
|
||||||
if (button !== null) {
|
|
||||||
function handleClick(event: Event) {
|
|
||||||
if (isSearchButton(event.target)) {
|
|
||||||
const objectType = event.target.dataset.searchValue;
|
|
||||||
if (objectType !== '' && selected !== objectType) {
|
|
||||||
selected = objectType;
|
|
||||||
selectedValue.innerHTML = button.textContent ?? 'Error';
|
|
||||||
selectedType.value = objectType;
|
|
||||||
} else {
|
|
||||||
selected = '';
|
|
||||||
selectedType.innerHTML = 'All Objects';
|
|
||||||
selectedType.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
button.addEventListener('click', handleClick);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,7 @@ export function initApiSelect() {
|
|||||||
// element's value.
|
// element's value.
|
||||||
const event = new Event(`netbox.select.onload.${select.name}`);
|
const event = new Event(`netbox.select.onload.${select.name}`);
|
||||||
// Query Parameters - will have attributes added below.
|
// Query Parameters - will have attributes added below.
|
||||||
const query = {} as Record<string, string>;
|
const query = { limit: 0 } as Record<string, string | number>;
|
||||||
|
|
||||||
if (hasUrl(select)) {
|
if (hasUrl(select)) {
|
||||||
// Store the original URL, so it can be referred back to as filter-by elements change.
|
// Store the original URL, so it can be referred back to as filter-by elements change.
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
// Base Netbox Theme Overrides and Settings - color mode agnostic.
|
// Base NetBox Theme Overrides and Settings - color mode agnostic.
|
||||||
|
|
||||||
@import 'bootstrap/scss/functions';
|
@import 'bootstrap/scss/functions';
|
||||||
|
|
||||||
$alt: #13293d;
|
$alt: #13293d;
|
||||||
@ -11,6 +12,7 @@ $green: #10b981;
|
|||||||
$blue: #3b82f6;
|
$blue: #3b82f6;
|
||||||
$purple: #8b5cf6;
|
$purple: #8b5cf6;
|
||||||
$pink: #ec4899;
|
$pink: #ec4899;
|
||||||
|
$cyan: #06b6d4;
|
||||||
|
|
||||||
$gray-50: #f9fafb;
|
$gray-50: #f9fafb;
|
||||||
$gray-100: #f3f4f6;
|
$gray-100: #f3f4f6;
|
||||||
@ -67,6 +69,17 @@ $blue-700: #1d4ed8;
|
|||||||
$blue-800: #1e40af;
|
$blue-800: #1e40af;
|
||||||
$blue-900: #1e3a8a;
|
$blue-900: #1e3a8a;
|
||||||
|
|
||||||
|
$cyan-50: #ecfeff;
|
||||||
|
$cyan-100: #cffafe;
|
||||||
|
$cyan-200: #a5f3fc;
|
||||||
|
$cyan-300: #67e8f9;
|
||||||
|
$cyan-400: #22d3ee;
|
||||||
|
$cyan-500: #06b6d4;
|
||||||
|
$cyan-600: #0891b2;
|
||||||
|
$cyan-700: #0e7490;
|
||||||
|
$cyan-800: #155e75;
|
||||||
|
$cyan-900: #164e63;
|
||||||
|
|
||||||
$indigo-50: #eef2ff;
|
$indigo-50: #eef2ff;
|
||||||
$indigo-100: #e0e7ff;
|
$indigo-100: #e0e7ff;
|
||||||
$indigo-200: #c7d2fe;
|
$indigo-200: #c7d2fe;
|
||||||
@ -119,6 +132,13 @@ $font-weight-lighter: 200;
|
|||||||
$font-weight-medium: 600;
|
$font-weight-medium: 600;
|
||||||
$font-weight-bolder: 800;
|
$font-weight-bolder: 800;
|
||||||
|
|
||||||
|
$font-size-xs: 0.9rem;
|
||||||
|
|
||||||
|
$line-height-base: 1.5;
|
||||||
|
$line-height-xs: 1;
|
||||||
|
$line-height-sm: 1.25;
|
||||||
|
$line-height-lg: 1.75;
|
||||||
|
|
||||||
$theme-color-addons: (
|
$theme-color-addons: (
|
||||||
'alt': $alt,
|
'alt': $alt,
|
||||||
'gray': $gray-400,
|
'gray': $gray-400,
|
||||||
@ -173,6 +193,16 @@ $theme-color-addons: (
|
|||||||
'blue-700': $blue-700,
|
'blue-700': $blue-700,
|
||||||
'blue-800': $blue-800,
|
'blue-800': $blue-800,
|
||||||
'blue-900': $blue-900,
|
'blue-900': $blue-900,
|
||||||
|
'cyan-50': $cyan-50,
|
||||||
|
'cyan-100': $cyan-100,
|
||||||
|
'cyan-200': $cyan-200,
|
||||||
|
'cyan-300': $cyan-300,
|
||||||
|
'cyan-400': $cyan-400,
|
||||||
|
'cyan-500': $cyan-500,
|
||||||
|
'cyan-600': $cyan-600,
|
||||||
|
'cyan-700': $cyan-700,
|
||||||
|
'cyan-800': $cyan-800,
|
||||||
|
'cyan-900': $cyan-900,
|
||||||
'indigo-50': $indigo-50,
|
'indigo-50': $indigo-50,
|
||||||
'indigo-100': $indigo-100,
|
'indigo-100': $indigo-100,
|
||||||
'indigo-200': $indigo-200,
|
'indigo-200': $indigo-200,
|
||||||
@ -210,3 +240,6 @@ $font-family-sans-serif: ui-sans-serif, system-ui, -apple-system, BlinkMacSystem
|
|||||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||||
$font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
$font-family-monospace: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
|
||||||
'Courier New', monospace;
|
'Courier New', monospace;
|
||||||
|
|
||||||
|
$accordion-padding-y: 0.8125rem;
|
||||||
|
$accordion-padding-x: 0.8125rem;
|
||||||
|
@ -47,11 +47,11 @@ $box-shadow: 0 0.5rem 1rem rgba($black, 0.15);
|
|||||||
$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075);
|
$box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075);
|
||||||
$box-shadow-lg: 0 1rem 3rem rgba($black, 0.175);
|
$box-shadow-lg: 0 1rem 3rem rgba($black, 0.175);
|
||||||
$box-shadow-inset: inset 0 1px 2px rgba($black, 0.075);
|
$box-shadow-inset: inset 0 1px 2px rgba($black, 0.075);
|
||||||
// $component-active-color: $white;
|
$text-muted: $gray-400;
|
||||||
// $component-active-bg: $primary;
|
|
||||||
$text-muted: $gray-500;
|
|
||||||
$blockquote-footer-color: $gray-600;
|
$blockquote-footer-color: $gray-600;
|
||||||
$mark-bg: #fcf8e3;
|
$mark-bg: #fcf8e3;
|
||||||
|
$link-color: $primary;
|
||||||
|
$link-hover-color: $blue-200;
|
||||||
|
|
||||||
// Tables
|
// Tables
|
||||||
$table-color: $gray-100;
|
$table-color: $gray-100;
|
||||||
@ -129,6 +129,10 @@ $nav-tabs-link-active-border-color: $gray-800 $gray-800 $nav-tabs-link-active-bg
|
|||||||
$nav-pills-link-active-color: $component-active-color;
|
$nav-pills-link-active-color: $component-active-color;
|
||||||
$nav-pills-link-active-bg: $component-active-bg;
|
$nav-pills-link-active-bg: $component-active-bg;
|
||||||
|
|
||||||
|
$navbar-light-color: $gray-500;
|
||||||
|
$navbar-light-toggler-icon-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'><path stroke='#{$navbar-light-color}' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/></svg>");
|
||||||
|
$navbar-light-toggler-border-color: $gray-700;
|
||||||
|
|
||||||
// Dropdowns
|
// Dropdowns
|
||||||
$dropdown-color: $body-color;
|
$dropdown-color: $body-color;
|
||||||
$dropdown-bg: $gray-900;
|
$dropdown-bg: $gray-900;
|
||||||
@ -170,8 +174,8 @@ $accordion-bg: transparent;
|
|||||||
$accordion-border-color: rgba($white, 0.125);
|
$accordion-border-color: rgba($white, 0.125);
|
||||||
$accordion-button-color: $accordion-color;
|
$accordion-button-color: $accordion-color;
|
||||||
$accordion-button-bg: $accordion-bg;
|
$accordion-button-bg: $accordion-bg;
|
||||||
$accordion-button-active-bg: tint-color($component-active-bg, 5%);
|
$accordion-button-active-bg: rgba($blue-300, 0.15);
|
||||||
$accordion-button-active-color: shade-color($primary, 10%);
|
$accordion-button-active-color: $gray-300;
|
||||||
$accordion-button-focus-border-color: $input-focus-border-color;
|
$accordion-button-focus-border-color: $input-focus-border-color;
|
||||||
$accordion-icon-color: $accordion-color;
|
$accordion-icon-color: $accordion-color;
|
||||||
$accordion-icon-active-color: $accordion-button-active-color;
|
$accordion-icon-active-color: $accordion-button-active-color;
|
||||||
@ -259,7 +263,7 @@ $btn-close-bg: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' v
|
|||||||
$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);
|
$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);
|
||||||
|
|
||||||
// Code
|
// Code
|
||||||
$code-color: $pink-300;
|
$code-color: $gray-200;
|
||||||
$kbd-color: $white;
|
$kbd-color: $white;
|
||||||
$kbd-bg: $gray-300;
|
$kbd-bg: $gray-300;
|
||||||
$pre-color: null;
|
$pre-color: null;
|
||||||
|
@ -17,5 +17,12 @@ $card-cap-color: $gray-800;
|
|||||||
|
|
||||||
$accordion-bg: transparent;
|
$accordion-bg: transparent;
|
||||||
$accordion-button-bg: $accordion-bg;
|
$accordion-button-bg: $accordion-bg;
|
||||||
|
$accordion-button-active-bg: $blue-100;
|
||||||
|
$accordion-button-active-color: $gray-800;
|
||||||
|
|
||||||
$breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='currentColor'/%3E%3C/svg%3E");
|
$breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='currentColor'/%3E%3C/svg%3E");
|
||||||
|
|
||||||
|
$code-color: $gray-900;
|
||||||
|
|
||||||
|
$list-group-color: $gray-700;
|
||||||
|
$list-group-disabled-color: $gray-500;
|
||||||
|
@ -10,7 +10,7 @@ from rest_framework.viewsets import ViewSet
|
|||||||
|
|
||||||
from extras.api.views import CustomFieldModelViewSet
|
from extras.api.views import CustomFieldModelViewSet
|
||||||
from netbox.api.views import ModelViewSet
|
from netbox.api.views import ModelViewSet
|
||||||
from secrets import filters
|
from secrets import filtersets
|
||||||
from secrets.exceptions import InvalidKey
|
from secrets.exceptions import InvalidKey
|
||||||
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
from secrets.models import Secret, SecretRole, SessionKey, UserKey
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
@ -39,7 +39,7 @@ class SecretRoleViewSet(CustomFieldModelViewSet):
|
|||||||
secret_count=count_related(Secret, 'role')
|
secret_count=count_related(Secret, 'role')
|
||||||
)
|
)
|
||||||
serializer_class = serializers.SecretRoleSerializer
|
serializer_class = serializers.SecretRoleSerializer
|
||||||
filterset_class = filters.SecretRoleFilterSet
|
filterset_class = filtersets.SecretRoleFilterSet
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -49,7 +49,7 @@ class SecretRoleViewSet(CustomFieldModelViewSet):
|
|||||||
class SecretViewSet(ModelViewSet):
|
class SecretViewSet(ModelViewSet):
|
||||||
queryset = Secret.objects.prefetch_related('role', 'tags')
|
queryset = Secret.objects.prefetch_related('role', 'tags')
|
||||||
serializer_class = serializers.SecretSerializer
|
serializer_class = serializers.SecretSerializer
|
||||||
filterset_class = filters.SecretFilterSet
|
filterset_class = filtersets.SecretFilterSet
|
||||||
|
|
||||||
master_key = None
|
master_key = None
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import django_filters
|
|||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
from dcim.models import Device
|
from dcim.models import Device
|
||||||
from extras.filters import CustomFieldModelFilterSet, CreatedUpdatedFilterSet
|
from extras.filters import TagFilter
|
||||||
from utilities.filters import BaseFilterSet, NameSlugSearchFilterSet, TagFilter
|
from netbox.filtersets import OrganizationalModelFilterSet, PrimaryModelFilterSet
|
||||||
from virtualization.models import VirtualMachine
|
from virtualization.models import VirtualMachine
|
||||||
from .models import Secret, SecretRole
|
from .models import Secret, SecretRole
|
||||||
|
|
||||||
@ -14,14 +14,14 @@ __all__ = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SecretRoleFilterSet(BaseFilterSet, NameSlugSearchFilterSet, CreatedUpdatedFilterSet):
|
class SecretRoleFilterSet(OrganizationalModelFilterSet):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = SecretRole
|
model = SecretRole
|
||||||
fields = ['id', 'name', 'slug']
|
fields = ['id', 'name', 'slug']
|
||||||
|
|
||||||
|
|
||||||
class SecretFilterSet(BaseFilterSet, CustomFieldModelFilterSet, CreatedUpdatedFilterSet):
|
class SecretFilterSet(PrimaryModelFilterSet):
|
||||||
q = django_filters.CharFilter(
|
q = django_filters.CharFilter(
|
||||||
method='search',
|
method='search',
|
||||||
label='Search',
|
label='Search',
|
@ -233,7 +233,7 @@ class SessionKey(BigIDModel):
|
|||||||
return session_key
|
return session_key
|
||||||
|
|
||||||
|
|
||||||
@extras_features('custom_fields', 'export_templates', 'webhooks')
|
@extras_features('custom_fields', 'custom_links', 'export_templates', 'webhooks')
|
||||||
class SecretRole(OrganizationalModel):
|
class SecretRole(OrganizationalModel):
|
||||||
"""
|
"""
|
||||||
A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles
|
A SecretRole represents an arbitrary functional classification of Secrets. For example, a user might define roles
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
from dcim.models import Device, DeviceRole, DeviceType, Manufacturer, Site
|
||||||
from secrets.filters import *
|
from secrets.filtersets import *
|
||||||
from secrets.models import Secret, SecretRole
|
from secrets.models import Secret, SecretRole
|
||||||
|
from utilities.testing import ChangeLoggedFilterSetTests
|
||||||
from virtualization.models import Cluster, ClusterType, VirtualMachine
|
from virtualization.models import Cluster, ClusterType, VirtualMachine
|
||||||
|
|
||||||
|
|
||||||
class SecretRoleTestCase(TestCase):
|
class SecretRoleTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = SecretRole.objects.all()
|
queryset = SecretRole.objects.all()
|
||||||
filterset = SecretRoleFilterSet
|
filterset = SecretRoleFilterSet
|
||||||
|
|
||||||
@ -20,10 +21,6 @@ class SecretRoleTestCase(TestCase):
|
|||||||
)
|
)
|
||||||
SecretRole.objects.bulk_create(roles)
|
SecretRole.objects.bulk_create(roles)
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Secret Role 1', 'Secret Role 2']}
|
params = {'name': ['Secret Role 1', 'Secret Role 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -33,7 +30,7 @@ class SecretRoleTestCase(TestCase):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
class SecretTestCase(TestCase):
|
class SecretTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
queryset = Secret.objects.all()
|
queryset = Secret.objects.all()
|
||||||
filterset = SecretFilterSet
|
filterset = SecretFilterSet
|
||||||
|
|
||||||
@ -80,10 +77,6 @@ class SecretTestCase(TestCase):
|
|||||||
for s in secrets:
|
for s in secrets:
|
||||||
s.save()
|
s.save()
|
||||||
|
|
||||||
def test_id(self):
|
|
||||||
params = {'id': self.queryset.values_list('pk', flat=True)[:2]}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
params = {'name': ['Secret 1', 'Secret 2']}
|
params = {'name': ['Secret 1', 'Secret 2']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
@ -2,14 +2,14 @@ import base64
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import get_object_or_404, redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from netbox.views import generic
|
from netbox.views import generic
|
||||||
from utilities.tables import paginate_table
|
from utilities.tables import paginate_table
|
||||||
from utilities.utils import count_related
|
from utilities.utils import count_related
|
||||||
from . import filters, forms, tables
|
from . import filtersets, forms, tables
|
||||||
from .models import SecretRole, Secret, SessionKey, UserKey
|
from .models import SecretRole, Secret, SessionKey, UserKey
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ class SecretRoleBulkEditView(generic.BulkEditView):
|
|||||||
queryset = SecretRole.objects.annotate(
|
queryset = SecretRole.objects.annotate(
|
||||||
secret_count=count_related(Secret, 'role')
|
secret_count=count_related(Secret, 'role')
|
||||||
)
|
)
|
||||||
filterset = filters.SecretRoleFilterSet
|
filterset = filtersets.SecretRoleFilterSet
|
||||||
table = tables.SecretRoleTable
|
table = tables.SecretRoleTable
|
||||||
form = forms.SecretRoleBulkEditForm
|
form = forms.SecretRoleBulkEditForm
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ class SecretRoleBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class SecretListView(generic.ObjectListView):
|
class SecretListView(generic.ObjectListView):
|
||||||
queryset = Secret.objects.all()
|
queryset = Secret.objects.all()
|
||||||
filterset = filters.SecretFilterSet
|
filterset = filtersets.SecretFilterSet
|
||||||
filterset_form = forms.SecretFilterForm
|
filterset_form = forms.SecretFilterForm
|
||||||
table = tables.SecretTable
|
table = tables.SecretTable
|
||||||
action_buttons = ('add', 'import', 'export')
|
action_buttons = ('add', 'import', 'export')
|
||||||
@ -220,12 +220,12 @@ class SecretBulkImportView(generic.BulkImportView):
|
|||||||
|
|
||||||
class SecretBulkEditView(generic.BulkEditView):
|
class SecretBulkEditView(generic.BulkEditView):
|
||||||
queryset = Secret.objects.prefetch_related('role')
|
queryset = Secret.objects.prefetch_related('role')
|
||||||
filterset = filters.SecretFilterSet
|
filterset = filtersets.SecretFilterSet
|
||||||
table = tables.SecretTable
|
table = tables.SecretTable
|
||||||
form = forms.SecretBulkEditForm
|
form = forms.SecretBulkEditForm
|
||||||
|
|
||||||
|
|
||||||
class SecretBulkDeleteView(generic.BulkDeleteView):
|
class SecretBulkDeleteView(generic.BulkDeleteView):
|
||||||
queryset = Secret.objects.prefetch_related('role')
|
queryset = Secret.objects.prefetch_related('role')
|
||||||
filterset = filters.SecretFilterSet
|
filterset = filtersets.SecretFilterSet
|
||||||
table = tables.SecretTable
|
table = tables.SecretTable
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 offset-md-3">
|
<div class="col col-md-6 offset-md-3">
|
||||||
<div class="card bg-danger mt-5">
|
<div class="card bg-danger mt-5">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
<i class="mdi mdi-alert"></i> Server Error
|
<i class="mdi mdi-alert"></i> Server Error
|
||||||
|
@ -2,11 +2,21 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>{% block title %}Home{% endblock %} - NetBox</title>
|
<title>{% block title %}Home{% endblock %} | NetBox</title>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="{% static 'netbox.css'%}"
|
href="{% static 'netbox-external.css'%}"
|
||||||
onerror="window.location='{% url 'media_failure' %}?filename=netbox.css'"
|
onerror="window.location='{% url 'media_failure' %}?filename=netbox-external.css'"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{% static 'netbox-light.css'%}"
|
||||||
|
onerror="window.location='{% url 'media_failure' %}?filename=netbox-light.css'"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="{% static 'netbox-dark.css'%}"
|
||||||
|
onerror="window.location='{% url 'media_failure' %}?filename=netbox-dark.css'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="{% static 'netbox.ico' %}" />
|
<link rel="icon" type="image/png" href="{% static 'netbox.ico' %}" />
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
<div class="d-flex flex-column container-fluid mt-auto justify-content-end sidebar-bottom">
|
<div class="d-flex flex-column container-fluid mt-auto justify-content-end sidebar-bottom">
|
||||||
<div class="d-flex flex-column align-items-center px-2 fw-light">
|
|
||||||
<small class="text-muted">{% now 'Y-m-d H:i:s T' %}</small>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex flex-column align-items-center px-2">
|
|
||||||
<small class="text-muted">
|
|
||||||
{{ settings.HOSTNAME }} (v{{ settings.VERSION }})
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<nav class="nav justify-content-between mb-2 mt-4 px-2">
|
<nav class="nav justify-content-between mb-2 mt-4 px-2">
|
||||||
<a type="button" target="_blank" class="nav-link" href="https://netbox.readthedocs.io/">
|
<a type="button" target="_blank" class="nav-link" href="https://netbox.readthedocs.io/">
|
||||||
<i title="Docs" class="mdi mdi-book-open-variant text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
|
<i title="Docs" class="mdi mdi-book-open-variant text-primary" data-bs-placement="top" data-bs-toggle="tooltip"></i>
|
||||||
|
@ -10,19 +10,13 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Circuit
|
Circuit
|
||||||
</h5>
|
</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-hover attr-table">
|
<table class="table table-hover attr-table">
|
||||||
<tr>
|
|
||||||
<td colspan="2">
|
|
||||||
<span class="badge bg-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Provider</th>
|
<th scope="row">Provider</th>
|
||||||
<td>
|
<td>
|
||||||
@ -50,6 +44,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope="row">Status</th>
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-{{ object.get_status_class }}">{{ object.get_status_display }}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Install Date</th>
|
<th scope="row">Install Date</th>
|
||||||
<td>{{ object.install_date|placeholder }}</td>
|
<td>{{ object.install_date|placeholder }}</td>
|
||||||
@ -81,14 +81,14 @@
|
|||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
{% include 'circuits/inc/circuit_termination.html' with termination=termination_a side='A' %}
|
{% include 'circuits/inc/circuit_termination.html' with termination=object.termination_a side='A' %}
|
||||||
{% include 'circuits/inc/circuit_termination.html' with termination=termination_z side='Z' %}
|
{% include 'circuits/inc/circuit_termination.html' with termination=object.termination_z side='Z' %}
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Circuit Type
|
Circuit Type
|
||||||
@ -35,13 +35,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
{% include 'inc/custom_fields_panel.html' %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Circuits
|
Circuits
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
{% load plugins %}
|
{% load plugins %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
<li><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
<li class="breadcrumb-item"><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
||||||
<li>{{ object }}</li>
|
<li class="breadcrumb-item">{{ object }}</li>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col col-md-4">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Provider
|
Provider
|
||||||
@ -54,7 +54,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
{% include 'inc/custom_fields_panel.html' %}
|
{% include 'inc/custom_fields_panel.html' %}
|
||||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='circuits:provider_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='circuits:provider_list' %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -71,7 +71,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% plugin_right_page object %}
|
{% plugin_right_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col col-md-8">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Circuits
|
Circuits
|
||||||
@ -91,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Provider Network
|
Provider Network
|
||||||
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Comments
|
Comments
|
||||||
@ -56,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Circuits
|
Circuits
|
||||||
@ -69,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Cable
|
Cable
|
||||||
@ -73,7 +73,7 @@
|
|||||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:cable_list' %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all url='dcim:cable_list' %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Termination A
|
Termination A
|
||||||
@ -94,7 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
{{ field }}
|
{{ field }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col-md-5">
|
<div class="col col-md-5">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
A Side
|
A Side
|
||||||
@ -68,10 +68,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 d-flex flex-column justify-content-center align-items-center">
|
<div class="col col-md-2 d-flex flex-column justify-content-center align-items-center">
|
||||||
<i class="mdi mdi-swap-horizontal-bold mdi-48px"></i>
|
<i class="mdi mdi-swap-horizontal-bold mdi-48px"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col col-md-5">
|
||||||
<div class="card h-100">
|
<div class="card h-100">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
B Side
|
B Side
|
||||||
@ -123,12 +123,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-3 justify-content-center">
|
<div class="row my-3 justify-content-center">
|
||||||
<div class="col-md-8">
|
<div class="col col-md-8">
|
||||||
{% include 'dcim/inc/cable_form.html' %}
|
{% include 'dcim/inc/cable_form.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row my-3">
|
<div class="row my-3">
|
||||||
<div class="col-md-12 text-center">
|
<div class="col col-md-12 text-center">
|
||||||
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
<a href="{{ return_url }}" class="btn btn-outline-danger">Cancel</a>
|
||||||
<button type="submit" name="_update" class="btn btn-primary">Connect</button>
|
<button type="submit" name="_update" class="btn btn-primary">Connect</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-5 col-sm-12">
|
<div class="col col-md-5">
|
||||||
<div class="cable-trace">
|
<div class="cable-trace">
|
||||||
{% with traced_path=path.origin.trace %}
|
{% with traced_path=path.origin.trace %}
|
||||||
{% for near_end, cable, far_end in traced_path %}
|
{% for near_end, cable, far_end in traced_path %}
|
||||||
@ -87,7 +87,7 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7 col-sm-12">
|
<div class="col col-md-7">
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-md-9">
|
<div class="col col-md-9">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% include 'responsive_table.html' %}
|
{% include 'responsive_table.html' %}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
{% include 'inc/paginator.html' with paginator=table.paginator page=table.page %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 float-end right-side-panel noprint">
|
<div class="col col-md-3 float-end right-side-panel noprint">
|
||||||
{% include 'inc/search_panel.html' %}
|
{% include 'inc/search_panel.html' %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Console Port
|
Console Port
|
||||||
@ -49,7 +49,7 @@
|
|||||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Connection
|
Connection
|
||||||
@ -145,7 +145,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Console Server Port
|
Console Server Port
|
||||||
@ -49,7 +49,7 @@
|
|||||||
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
{% include 'extras/inc/tags_panel.html' with tags=object.tags.all %}
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Connection
|
Connection
|
||||||
@ -145,7 +145,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div role="tabpanel" class="tab-pane active" id="details">
|
<div role="tabpanel" class="tab-pane active" id="details">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
Device
|
Device
|
||||||
@ -94,7 +94,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="row">Device Type</th>
|
<th scope="row">Device Type</th>
|
||||||
<td>
|
<td>
|
||||||
<span><a href="{{ object.device_type.get_absolute_url }}">{{ object.device_type.display_name }}</a> ({{ object.device_type.u_height }}U)</span>
|
<span><a href="{{ object.device_type.get_absolute_url }}">{{ object.device_type }}</a> ({{ object.device_type.u_height }}U)</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -228,7 +228,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col col-md-6">
|
||||||
{% if object.powerports.exists and object.poweroutlets.exists %}
|
{% if object.powerports.exists and object.poweroutlets.exists %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5 class="card-header">
|
<h5 class="card-header">
|
||||||
@ -356,7 +356,7 @@
|
|||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ rd.device_type.display_name }}</td>
|
<td>{{ rd.device_type }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
@ -369,7 +369,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col col-md-12">
|
||||||
{% plugin_full_width_page object %}
|
{% plugin_full_width_page object %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-10 col-md-offset-1">
|
<div class="col col-md-10">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-overlay">
|
<div class="card-overlay">
|
||||||
<div class="spinner-border" role="status">
|
<div class="spinner-border" role="status">
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user