mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-16 12:12:53 -06:00
commit
b5d57f3418
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.3.2
|
placeholder: v3.3.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@ -14,7 +14,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: NetBox version
|
label: NetBox version
|
||||||
description: What version of NetBox are you currently running?
|
description: What version of NetBox are you currently running?
|
||||||
placeholder: v3.3.2
|
placeholder: v3.3.3
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
|
14
README.md
14
README.md
@ -2,8 +2,6 @@
|
|||||||
<img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo.svg" width="400" alt="NetBox logo" />
|
<img src="https://raw.githubusercontent.com/netbox-community/netbox/develop/docs/netbox_logo.svg" width="400" alt="NetBox logo" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||

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

|
||||||
|
|
||||||
|
[](https://github.com/netbox-community/netbox/commits)
|
||||||
|
[](https://github.com/netbox-community/netbox/issues)
|
||||||
|
[](https://github.com/netbox-community/netbox/pulls)
|
||||||
|
[](https://github.com/netbox-community/netbox/graphs/contributors)
|
||||||
|
<br />Stats via [Repography](https://repography.com)
|
||||||
|
|
||||||
|
## About NetBox
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Myriad infrastructure components can be modeled in NetBox, including:
|
Myriad infrastructure components can be modeled in NetBox, including:
|
||||||
@ -57,7 +65,7 @@ complete list of requirements, see `requirements.txt`. The code is available
|
|||||||
[on GitHub](https://github.com/netbox-community/netbox).
|
[on GitHub](https://github.com/netbox-community/netbox).
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<h4>Thank you to our sponsors!</h4>
|
<h3>Thank you to our sponsors!</h3>
|
||||||
|
|
||||||
[](https://try.digitalocean.com/developer-cloud)
|
[](https://try.digitalocean.com/developer-cloud)
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ This section of the documentation discusses installing and configuring the NetBo
|
|||||||
Begin by installing all system packages required by NetBox and its dependencies.
|
Begin by installing all system packages required by NetBox and its dependencies.
|
||||||
|
|
||||||
!!! warning "Python 3.8 or later required"
|
!!! warning "Python 3.8 or later required"
|
||||||
NetBox v3.2 requires Python 3.8, 3.9, or 3.10.
|
NetBox requires Python 3.8, 3.9, or 3.10.
|
||||||
|
|
||||||
=== "Ubuntu"
|
=== "Ubuntu"
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
The installation instructions provided here have been tested to work on Ubuntu 20.04 and CentOS 8.3. The particular commands needed to install dependencies on other distributions may vary significantly. Unfortunately, this is outside the control of the NetBox maintainers. Please consult your distribution's documentation for assistance with any errors.
|
The installation instructions provided here have been tested to work on Ubuntu 20.04 and CentOS 8.3. The particular commands needed to install dependencies on other distributions may vary significantly. Unfortunately, this is outside the control of the NetBox maintainers. Please consult your distribution's documentation for assistance with any errors.
|
||||||
|
|
||||||
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/_y5JRiW_PLM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
|
||||||
The following sections detail how to set up a new instance of NetBox:
|
The following sections detail how to set up a new instance of NetBox:
|
||||||
|
|
||||||
1. [PostgreSQL database](1-postgresql.md)
|
1. [PostgreSQL database](1-postgresql.md)
|
||||||
|
@ -1,10 +1,19 @@
|
|||||||
# Upgrading to a New NetBox Release
|
# Upgrading to a New NetBox Release
|
||||||
|
|
||||||
## Review the Release Notes
|
Upgrading NetBox to a new version is pretty simple, however users are cautioned to always review the release notes and save a backup of their current deployment prior to beginning an upgrade.
|
||||||
|
|
||||||
|
NetBox can generally be upgraded directly to any newer release with no interim steps, with the one exception being incrementing major versions. This can be done only from the most recent _minor_ release of the major version. For example, NetBox v2.11.8 can be upgraded to version 3.3.2 following the steps below. However, a deployment of NetBox v2.10.10 or earlier must first be upgraded to any v2.11 release, and then to any v3.x release. (This is to accommodate the consolidation of database schema migrations effected by a major version change).
|
||||||
|
|
||||||
|
[](../media/installation/upgrade_paths.png)
|
||||||
|
|
||||||
|
!!! warning "Perform a Backup"
|
||||||
|
Always be sure to save a backup of your current NetBox deployment prior to starting the upgrade process.
|
||||||
|
|
||||||
|
## 1. Review the Release Notes
|
||||||
|
|
||||||
Prior to upgrading your NetBox instance, be sure to carefully review all [release notes](../release-notes/index.md) that have been published since your current version was released. Although the upgrade process typically does not involve additional work, certain releases may introduce breaking or backward-incompatible changes. These are called out in the release notes under the release in which the change went into effect.
|
Prior to upgrading your NetBox instance, be sure to carefully review all [release notes](../release-notes/index.md) that have been published since your current version was released. Although the upgrade process typically does not involve additional work, certain releases may introduce breaking or backward-incompatible changes. These are called out in the release notes under the release in which the change went into effect.
|
||||||
|
|
||||||
## Update Dependencies to Required Versions
|
## 2. Update Dependencies to Required Versions
|
||||||
|
|
||||||
NetBox v3.0 and later require the following:
|
NetBox v3.0 and later require the following:
|
||||||
|
|
||||||
@ -14,7 +23,7 @@ NetBox v3.0 and later require the following:
|
|||||||
| PostgreSQL | 10 |
|
| PostgreSQL | 10 |
|
||||||
| Redis | 4.0 |
|
| Redis | 4.0 |
|
||||||
|
|
||||||
## Install the Latest Release
|
## 3. Install the Latest Release
|
||||||
|
|
||||||
As with the initial installation, you can upgrade NetBox by either downloading the latest release package or by cloning the `master` branch of the git repository.
|
As with the initial installation, you can upgrade NetBox by either downloading the latest release package or by cloning the `master` branch of the git repository.
|
||||||
|
|
||||||
@ -87,7 +96,7 @@ sudo git pull origin master
|
|||||||
|
|
||||||
sudo git checkout v2.11.11
|
sudo git checkout v2.11.11
|
||||||
|
|
||||||
## Run the Upgrade Script
|
## 4. Run the Upgrade Script
|
||||||
|
|
||||||
Once the new code is in place, verify that any optional Python packages required by your deployment (e.g. `napalm` or `django-auth-ldap`) are listed in `local_requirements.txt`. Then, run the upgrade script:
|
Once the new code is in place, verify that any optional Python packages required by your deployment (e.g. `napalm` or `django-auth-ldap`) are listed in `local_requirements.txt`. Then, run the upgrade script:
|
||||||
|
|
||||||
@ -118,7 +127,7 @@ This script performs the following actions:
|
|||||||
been made to your local codebase and should be investigated. Never attempt to create new migrations unless you are
|
been made to your local codebase and should be investigated. Never attempt to create new migrations unless you are
|
||||||
intentionally modifying the database schema.
|
intentionally modifying the database schema.
|
||||||
|
|
||||||
## Restart the NetBox Services
|
## 5. Restart the NetBox Services
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
If you are upgrading from an installation that does not use a Python virtual environment (any release prior to v2.7.9), you'll need to update the systemd service files to reference the new Python and gunicorn executables before restarting the services. These are located in `/opt/netbox/venv/bin/`. See the example service files in `/opt/netbox/contrib/` for reference.
|
If you are upgrading from an installation that does not use a Python virtual environment (any release prior to v2.7.9), you'll need to update the systemd service files to reference the new Python and gunicorn executables before restarting the services. These are located in `/opt/netbox/venv/bin/`. See the example service files in `/opt/netbox/contrib/` for reference.
|
||||||
@ -129,7 +138,7 @@ Finally, restart the gunicorn and RQ services:
|
|||||||
sudo systemctl restart netbox netbox-rq
|
sudo systemctl restart netbox netbox-rq
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verify Housekeeping Scheduling
|
## 6. Verify Housekeeping Scheduling
|
||||||
|
|
||||||
If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be linked from your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.)
|
If upgrading from a release prior to NetBox v3.0, check that a cron task (or similar scheduled process) has been configured to run NetBox's nightly housekeeping command. A shell script which invokes this command is included at `contrib/netbox-housekeeping.sh`. It can be linked from your system's daily cron task directory, or included within the crontab directly. (If NetBox has been installed in a nonstandard path, be sure to update the system paths within this script first.)
|
||||||
|
|
||||||
|
BIN
docs/media/installation/upgrade_paths.png
Normal file
BIN
docs/media/installation/upgrade_paths.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
@ -1,5 +1,34 @@
|
|||||||
# NetBox v3.3
|
# NetBox v3.3
|
||||||
|
|
||||||
|
## v3.3.3 (2022-09-15)
|
||||||
|
|
||||||
|
### Enhancements
|
||||||
|
|
||||||
|
* [#8580](https://github.com/netbox-community/netbox/issues/8580) - Add `occupied` filter for cabled objects to filter by cable or `mark_connected`
|
||||||
|
* [#9577](https://github.com/netbox-community/netbox/issues/9577) - Add `has_front_image` and `has_rear_image` filters for device types
|
||||||
|
* [#10268](https://github.com/netbox-community/netbox/issues/10268) - Omit trailing ".0" in device positions within UI
|
||||||
|
* [#10359](https://github.com/netbox-community/netbox/issues/10359) - Add region and site group columns to the devices table
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* [#9231](https://github.com/netbox-community/netbox/issues/9231) - Fix `empty` lookup expression for string filters
|
||||||
|
* [#10247](https://github.com/netbox-community/netbox/issues/10247) - Allow changing the pre-populated device/VM when creating new components
|
||||||
|
* [#10250](https://github.com/netbox-community/netbox/issues/10250) - Fix exception when CableTermination validation fails during bulk import of cables
|
||||||
|
* [#10258](https://github.com/netbox-community/netbox/issues/10258) - Enable the use of reports & scripts packaged in submodules
|
||||||
|
* [#10259](https://github.com/netbox-community/netbox/issues/10259) - Fix `NoReverseMatch` exception when listing available prefixes with "flat" column displayed
|
||||||
|
* [#10270](https://github.com/netbox-community/netbox/issues/10270) - Fix custom field validation when creating new services
|
||||||
|
* [#10278](https://github.com/netbox-community/netbox/issues/10278) - Fix "create & add another" for image attachments
|
||||||
|
* [#10294](https://github.com/netbox-community/netbox/issues/10294) - Fix spurious changelog diff for interface WWN field
|
||||||
|
* [#10304](https://github.com/netbox-community/netbox/issues/10304) - Enable cloning for custom fields & custom links
|
||||||
|
* [#10305](https://github.com/netbox-community/netbox/issues/10305) - Fix Virtual Chassis master field cannot be null according to the API
|
||||||
|
* [#10307](https://github.com/netbox-community/netbox/issues/10307) - Correct value for "Passive 48V (4-pair)" PoE type selection
|
||||||
|
* [#10333](https://github.com/netbox-community/netbox/issues/10333) - Show available values for `ui_visibility` field of CustomField for CSV import
|
||||||
|
* [#10337](https://github.com/netbox-community/netbox/issues/10337) - Display SSO links when local authentication fails
|
||||||
|
* [#10353](https://github.com/netbox-community/netbox/issues/10353) - Table action buttons should reserve return URL parameters
|
||||||
|
* [#10362](https://github.com/netbox-community/netbox/issues/10362) - Correct display of custom fields when editing an L2VPN termination
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v3.3.2 (2022-09-02)
|
## v3.3.2 (2022-09-02)
|
||||||
|
|
||||||
### Enhancements
|
### Enhancements
|
||||||
|
@ -344,6 +344,7 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 4'),
|
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 4'),
|
||||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 5'),
|
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 5'),
|
||||||
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 6'),
|
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 6'),
|
||||||
|
Circuit(provider=providers[0], type=circuit_types[0], cid='Circuit 7'),
|
||||||
)
|
)
|
||||||
Circuit.objects.bulk_create(circuits)
|
Circuit.objects.bulk_create(circuits)
|
||||||
|
|
||||||
@ -357,6 +358,7 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
CircuitTermination(circuit=circuits[3], provider_network=provider_networks[0], term_side='A'),
|
CircuitTermination(circuit=circuits[3], provider_network=provider_networks[0], term_side='A'),
|
||||||
CircuitTermination(circuit=circuits[4], provider_network=provider_networks[1], term_side='A'),
|
CircuitTermination(circuit=circuits[4], provider_network=provider_networks[1], term_side='A'),
|
||||||
CircuitTermination(circuit=circuits[5], provider_network=provider_networks[2], term_side='A'),
|
CircuitTermination(circuit=circuits[5], provider_network=provider_networks[2], term_side='A'),
|
||||||
|
CircuitTermination(circuit=circuits[6], provider_network=provider_networks[0], term_side='A', mark_connected=True),
|
||||||
))
|
))
|
||||||
CircuitTermination.objects.bulk_create(circuit_terminations)
|
CircuitTermination.objects.bulk_create(circuit_terminations)
|
||||||
|
|
||||||
@ -364,7 +366,7 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
|
|
||||||
def test_term_side(self):
|
def test_term_side(self):
|
||||||
params = {'term_side': 'A'}
|
params = {'term_side': 'A'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 7)
|
||||||
|
|
||||||
def test_port_speed(self):
|
def test_port_speed(self):
|
||||||
params = {'port_speed': ['1000', '2000']}
|
params = {'port_speed': ['1000', '2000']}
|
||||||
@ -397,11 +399,19 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
def test_provider_network(self):
|
def test_provider_network(self):
|
||||||
provider_networks = ProviderNetwork.objects.all()[:2]
|
provider_networks = ProviderNetwork.objects.all()[:2]
|
||||||
params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]}
|
params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': True}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
|
||||||
|
params = {'occupied': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 7)
|
||||||
|
|
||||||
|
|
||||||
class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
class ProviderNetworkTestCase(TestCase, ChangeLoggedFilterSetTests):
|
||||||
|
@ -1076,7 +1076,7 @@ class CablePathSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class VirtualChassisSerializer(NetBoxModelSerializer):
|
class VirtualChassisSerializer(NetBoxModelSerializer):
|
||||||
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
url = serializers.HyperlinkedIdentityField(view_name='dcim-api:virtualchassis-detail')
|
||||||
master = NestedDeviceSerializer(required=False)
|
master = NestedDeviceSerializer(required=False, allow_null=True, default=None)
|
||||||
member_count = serializers.IntegerField(read_only=True)
|
member_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -1096,7 +1096,7 @@ class InterfacePoETypeChoices(ChoiceSet):
|
|||||||
(PASSIVE_24V_2PAIR, 'Passive 24V (2-pair)'),
|
(PASSIVE_24V_2PAIR, 'Passive 24V (2-pair)'),
|
||||||
(PASSIVE_24V_4PAIR, 'Passive 24V (4-pair)'),
|
(PASSIVE_24V_4PAIR, 'Passive 24V (4-pair)'),
|
||||||
(PASSIVE_48V_2PAIR, 'Passive 48V (2-pair)'),
|
(PASSIVE_48V_2PAIR, 'Passive 48V (2-pair)'),
|
||||||
(PASSIVE_48V_2PAIR, 'Passive 48V (4-pair)'),
|
(PASSIVE_48V_4PAIR, 'Passive 48V (4-pair)'),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -434,6 +434,14 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
|||||||
to_field_name='slug',
|
to_field_name='slug',
|
||||||
label='Manufacturer (slug)',
|
label='Manufacturer (slug)',
|
||||||
)
|
)
|
||||||
|
has_front_image = django_filters.BooleanFilter(
|
||||||
|
label='Has a front image',
|
||||||
|
method='_has_front_image'
|
||||||
|
)
|
||||||
|
has_rear_image = django_filters.BooleanFilter(
|
||||||
|
label='Has a rear image',
|
||||||
|
method='_has_rear_image'
|
||||||
|
)
|
||||||
console_ports = django_filters.BooleanFilter(
|
console_ports = django_filters.BooleanFilter(
|
||||||
method='_console_ports',
|
method='_console_ports',
|
||||||
label='Has console ports',
|
label='Has console ports',
|
||||||
@ -487,6 +495,18 @@ class DeviceTypeFilterSet(NetBoxModelFilterSet):
|
|||||||
Q(comments__icontains=value)
|
Q(comments__icontains=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _has_front_image(self, queryset, name, value):
|
||||||
|
if value:
|
||||||
|
return queryset.exclude(front_image='')
|
||||||
|
else:
|
||||||
|
return queryset.filter(front_image='')
|
||||||
|
|
||||||
|
def _has_rear_image(self, queryset, name, value):
|
||||||
|
if value:
|
||||||
|
return queryset.exclude(rear_image='')
|
||||||
|
else:
|
||||||
|
return queryset.filter(rear_image='')
|
||||||
|
|
||||||
def _console_ports(self, queryset, name, value):
|
def _console_ports(self, queryset, name, value):
|
||||||
return queryset.exclude(consoleporttemplates__isnull=value)
|
return queryset.exclude(consoleporttemplates__isnull=value)
|
||||||
|
|
||||||
@ -1144,6 +1164,15 @@ class CabledObjectFilterSet(django_filters.FilterSet):
|
|||||||
lookup_expr='isnull',
|
lookup_expr='isnull',
|
||||||
exclude=True
|
exclude=True
|
||||||
)
|
)
|
||||||
|
occupied = django_filters.BooleanFilter(
|
||||||
|
method='filter_occupied'
|
||||||
|
)
|
||||||
|
|
||||||
|
def filter_occupied(self, queryset, name, value):
|
||||||
|
if value:
|
||||||
|
return queryset.filter(Q(cable__isnull=False) | Q(mark_connected=True))
|
||||||
|
else:
|
||||||
|
return queryset.filter(cable__isnull=True, mark_connected=False)
|
||||||
|
|
||||||
|
|
||||||
class PathEndpointFilterSet(django_filters.FilterSet):
|
class PathEndpointFilterSet(django_filters.FilterSet):
|
||||||
|
@ -3,7 +3,7 @@ from django import forms
|
|||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from extras.forms import CustomFieldsMixin
|
from extras.forms import CustomFieldsMixin
|
||||||
from extras.models import Tag
|
from extras.models import Tag
|
||||||
from utilities.forms import DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
|
from utilities.forms import BootstrapMixin, DynamicModelMultipleChoiceField, ExpandableNameField, form_from_model
|
||||||
from .object_create import ComponentCreateForm
|
from .object_create import ComponentCreateForm
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
@ -24,7 +24,7 @@ __all__ = (
|
|||||||
# Device components
|
# Device components
|
||||||
#
|
#
|
||||||
|
|
||||||
class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
|
class DeviceBulkAddComponentForm(BootstrapMixin, CustomFieldsMixin, ComponentCreateForm):
|
||||||
pk = forms.ModelMultipleChoiceField(
|
pk = forms.ModelMultipleChoiceField(
|
||||||
queryset=Device.objects.all(),
|
queryset=Device.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
@ -37,6 +37,7 @@ class DeviceBulkAddComponentForm(CustomFieldsMixin, ComponentCreateForm):
|
|||||||
queryset=Tag.objects.all(),
|
queryset=Tag.objects.all(),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
replication_fields = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortBulkCreateForm(
|
class ConsolePortBulkCreateForm(
|
||||||
@ -44,7 +45,7 @@ class ConsolePortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'mark_connected', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'mark_connected', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortBulkCreateForm(
|
class ConsoleServerPortBulkCreateForm(
|
||||||
@ -52,7 +53,7 @@ class ConsoleServerPortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'speed', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'speed', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class PowerPortBulkCreateForm(
|
class PowerPortBulkCreateForm(
|
||||||
@ -60,7 +61,7 @@ class PowerPortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletBulkCreateForm(
|
class PowerOutletBulkCreateForm(
|
||||||
@ -68,7 +69,7 @@ class PowerOutletBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'feed_leg', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'feed_leg', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class InterfaceBulkCreateForm(
|
class InterfaceBulkCreateForm(
|
||||||
@ -79,7 +80,7 @@ class InterfaceBulkCreateForm(
|
|||||||
):
|
):
|
||||||
model = Interface
|
model = Interface
|
||||||
field_order = (
|
field_order = (
|
||||||
'name_pattern', 'label_pattern', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
'name', 'label', 'type', 'enabled', 'speed', 'duplex', 'mtu', 'mgmt_only', 'poe_mode',
|
||||||
'poe_type', 'mark_connected', 'description', 'tags',
|
'poe_type', 'mark_connected', 'description', 'tags',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -96,13 +97,13 @@ class RearPortBulkCreateForm(
|
|||||||
DeviceBulkAddComponentForm
|
DeviceBulkAddComponentForm
|
||||||
):
|
):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
field_order = ('name_pattern', 'label_pattern', 'type', 'positions', 'mark_connected', 'description', 'tags')
|
field_order = ('name', 'label', 'type', 'positions', 'mark_connected', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
field_order = ('name_pattern', 'label_pattern', 'position_pattern', 'description', 'tags')
|
field_order = ('name', 'label', 'position_pattern', 'description', 'tags')
|
||||||
|
replication_fields = ('name', 'label', 'position')
|
||||||
position_pattern = ExpandableNameField(
|
position_pattern = ExpandableNameField(
|
||||||
label='Position',
|
label='Position',
|
||||||
required=False,
|
required=False,
|
||||||
@ -112,7 +113,7 @@ class ModuleBayBulkCreateForm(DeviceBulkAddComponentForm):
|
|||||||
|
|
||||||
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
|
class DeviceBayBulkCreateForm(DeviceBulkAddComponentForm):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
field_order = ('name_pattern', 'label_pattern', 'description', 'tags')
|
field_order = ('name', 'label', 'description', 'tags')
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemBulkCreateForm(
|
class InventoryItemBulkCreateForm(
|
||||||
@ -121,6 +122,6 @@ class InventoryItemBulkCreateForm(
|
|||||||
):
|
):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
field_order = (
|
field_order = (
|
||||||
'name_pattern', 'label_pattern', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered',
|
||||||
'description', 'tags',
|
'description', 'tags',
|
||||||
)
|
)
|
||||||
|
@ -365,6 +365,7 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')),
|
('Hardware', ('manufacturer_id', 'part_number', 'subdevice_role', 'airflow')),
|
||||||
|
('Images', ('has_front_image', 'has_rear_image')),
|
||||||
('Components', (
|
('Components', (
|
||||||
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
'console_ports', 'console_server_ports', 'power_ports', 'power_outlets', 'interfaces',
|
||||||
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
|
'pass_through_ports', 'device_bays', 'module_bays', 'inventory_items',
|
||||||
@ -386,6 +387,20 @@ class DeviceTypeFilterForm(NetBoxModelFilterSetForm):
|
|||||||
choices=add_blank_choice(DeviceAirflowChoices),
|
choices=add_blank_choice(DeviceAirflowChoices),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
|
has_front_image = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
label='Has a front image',
|
||||||
|
widget=StaticSelect(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
has_rear_image = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
label='Has a rear image',
|
||||||
|
widget=StaticSelect(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
console_ports = forms.NullBooleanField(
|
console_ports = forms.NullBooleanField(
|
||||||
required=False,
|
required=False,
|
||||||
label='Has console ports',
|
label='Has console ports',
|
||||||
@ -936,12 +951,37 @@ class PowerFeedFilterForm(NetBoxModelFilterSetForm):
|
|||||||
# Device components
|
# Device components
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortFilterForm(DeviceComponentFilterForm):
|
class CabledFilterForm(forms.Form):
|
||||||
|
cabled = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
widget=StaticSelect(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
occupied = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
widget=StaticSelect(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PathEndpointFilterForm(CabledFilterForm):
|
||||||
|
connected = forms.NullBooleanField(
|
||||||
|
required=False,
|
||||||
|
widget=StaticSelect(
|
||||||
|
choices=BOOLEAN_WITH_BLANK_CHOICES
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Attributes', ('name', 'label', 'type', 'speed')),
|
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||||
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = MultipleChoiceField(
|
type = MultipleChoiceField(
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
@ -954,12 +994,13 @@ class ConsolePortFilterForm(DeviceComponentFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
|
class ConsoleServerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Attributes', ('name', 'label', 'type', 'speed')),
|
('Attributes', ('name', 'label', 'type', 'speed')),
|
||||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||||
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = MultipleChoiceField(
|
type = MultipleChoiceField(
|
||||||
choices=ConsolePortTypeChoices,
|
choices=ConsolePortTypeChoices,
|
||||||
@ -972,12 +1013,13 @@ class ConsoleServerPortFilterForm(DeviceComponentFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class PowerPortFilterForm(DeviceComponentFilterForm):
|
class PowerPortFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Attributes', ('name', 'label', 'type')),
|
('Attributes', ('name', 'label', 'type')),
|
||||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||||
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = MultipleChoiceField(
|
type = MultipleChoiceField(
|
||||||
choices=PowerPortTypeChoices,
|
choices=PowerPortTypeChoices,
|
||||||
@ -986,12 +1028,13 @@ class PowerPortFilterForm(DeviceComponentFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletFilterForm(DeviceComponentFilterForm):
|
class PowerOutletFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Attributes', ('name', 'label', 'type')),
|
('Attributes', ('name', 'label', 'type')),
|
||||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||||
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
type = MultipleChoiceField(
|
type = MultipleChoiceField(
|
||||||
choices=PowerOutletTypeChoices,
|
choices=PowerOutletTypeChoices,
|
||||||
@ -1000,7 +1043,7 @@ class PowerOutletFilterForm(DeviceComponentFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class InterfaceFilterForm(DeviceComponentFilterForm):
|
class InterfaceFilterForm(PathEndpointFilterForm, DeviceComponentFilterForm):
|
||||||
model = Interface
|
model = Interface
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
@ -1009,6 +1052,7 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
|
|||||||
('PoE', ('poe_mode', 'poe_type')),
|
('PoE', ('poe_mode', 'poe_type')),
|
||||||
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
|
('Wireless', ('rf_role', 'rf_channel', 'rf_channel_width', 'tx_power')),
|
||||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||||
|
('Connection', ('cabled', 'connected', 'occupied')),
|
||||||
)
|
)
|
||||||
kind = MultipleChoiceField(
|
kind = MultipleChoiceField(
|
||||||
choices=InterfaceKindChoices,
|
choices=InterfaceKindChoices,
|
||||||
@ -1089,11 +1133,12 @@ class InterfaceFilterForm(DeviceComponentFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class FrontPortFilterForm(DeviceComponentFilterForm):
|
class FrontPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Attributes', ('name', 'label', 'type', 'color')),
|
('Attributes', ('name', 'label', 'type', 'color')),
|
||||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||||
|
('Cable', ('cabled', 'occupied')),
|
||||||
)
|
)
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
type = MultipleChoiceField(
|
type = MultipleChoiceField(
|
||||||
@ -1106,12 +1151,13 @@ class FrontPortFilterForm(DeviceComponentFilterForm):
|
|||||||
tag = TagFilterField(model)
|
tag = TagFilterField(model)
|
||||||
|
|
||||||
|
|
||||||
class RearPortFilterForm(DeviceComponentFilterForm):
|
class RearPortFilterForm(CabledFilterForm, DeviceComponentFilterForm):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, ('q', 'tag')),
|
(None, ('q', 'tag')),
|
||||||
('Attributes', ('name', 'label', 'type', 'color')),
|
('Attributes', ('name', 'label', 'type', 'color')),
|
||||||
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
('Device', ('region_id', 'site_group_id', 'site_id', 'location_id', 'rack_id', 'virtual_chassis_id', 'device_id')),
|
||||||
|
('Cable', ('cabled', 'occupied')),
|
||||||
)
|
)
|
||||||
type = MultipleChoiceField(
|
type = MultipleChoiceField(
|
||||||
choices=PortTypeChoices,
|
choices=PortTypeChoices,
|
||||||
|
@ -986,47 +986,74 @@ class VCMemberSelectForm(BootstrapMixin, forms.Form):
|
|||||||
# Device component templates
|
# Device component templates
|
||||||
#
|
#
|
||||||
|
|
||||||
|
class ComponentTemplateForm(BootstrapMixin, forms.ModelForm):
|
||||||
|
device_type = DynamicModelChoiceField(
|
||||||
|
queryset=DeviceType.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable reassignment of DeviceType when editing an existing instance
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['device_type'].disabled = True
|
||||||
|
|
||||||
|
|
||||||
|
class ModularComponentTemplateForm(ComponentTemplateForm):
|
||||||
|
module_type = DynamicModelChoiceField(
|
||||||
|
queryset=ModuleType.objects.all(),
|
||||||
|
required=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class ConsolePortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect,
|
'type': StaticSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class ConsoleServerPortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect,
|
'type': StaticSelect,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class PowerPortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
class PowerOutletTemplateForm(ModularComponentTemplateForm):
|
||||||
power_port = DynamicModelChoiceField(
|
power_port = DynamicModelChoiceField(
|
||||||
queryset=PowerPortTemplate.objects.all(),
|
queryset=PowerPortTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1035,35 +1062,40 @@ class PowerOutletTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'power_port', 'feed_leg', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'feed_leg': StaticSelect(),
|
'feed_leg': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateForm(BootstrapMixin, forms.ModelForm):
|
class InterfaceTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description')),
|
||||||
|
('PoE', ('poe_mode', 'poe_type'))
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
|
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'description', 'poe_mode', 'poe_type',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'poe_mode': StaticSelect(),
|
'poe_mode': StaticSelect(),
|
||||||
'poe_type': StaticSelect(),
|
'poe_type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class FrontPortTemplateForm(ModularComponentTemplateForm):
|
||||||
rear_port = DynamicModelChoiceField(
|
rear_port = DynamicModelChoiceField(
|
||||||
queryset=RearPortTemplate.objects.all(),
|
queryset=RearPortTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1073,6 +1105,13 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position',
|
||||||
|
'description',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
@ -1080,48 +1119,50 @@ class FrontPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
'description',
|
'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateForm(BootstrapMixin, forms.ModelForm):
|
class RearPortTemplateForm(ModularComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
|
'device_type', 'module_type', 'name', 'label', 'type', 'color', 'positions', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
'module_type': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
class ModuleBayTemplateForm(ComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'name', 'label', 'position', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBayTemplate
|
model = ModuleBayTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'label', 'position', 'description',
|
'device_type', 'name', 'label', 'position', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateForm(BootstrapMixin, forms.ModelForm):
|
class DeviceBayTemplateForm(ComponentTemplateForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'name', 'label', 'description')),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'name', 'label', 'description',
|
'device_type', 'name', 'label', 'description',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
|
class InventoryItemTemplateForm(ComponentTemplateForm):
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=InventoryItemTemplate.objects.all(),
|
queryset=InventoryItemTemplate.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1148,22 +1189,39 @@ class InventoryItemTemplateForm(BootstrapMixin, forms.ModelForm):
|
|||||||
widget=forms.HiddenInput
|
widget=forms.HiddenInput
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||||
|
'component_type', 'component_id',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InventoryItemTemplate
|
model = InventoryItemTemplate
|
||||||
fields = [
|
fields = [
|
||||||
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
'device_type', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'description',
|
||||||
'component_type', 'component_id',
|
'component_type', 'component_id',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device_type': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Device components
|
# Device components
|
||||||
#
|
#
|
||||||
|
|
||||||
class ConsolePortForm(NetBoxModelForm):
|
class DeviceComponentForm(NetBoxModelForm):
|
||||||
|
device = DynamicModelChoiceField(
|
||||||
|
queryset=Device.objects.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable reassignment of Device when editing an existing instance
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['device'].disabled = True
|
||||||
|
|
||||||
|
|
||||||
|
class ModularDeviceComponentForm(DeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
module = DynamicModelChoiceField(
|
||||||
queryset=Module.objects.all(),
|
queryset=Module.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1172,25 +1230,31 @@ class ConsolePortForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConsolePortForm(ModularDeviceComponentForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'speed': StaticSelect(),
|
'speed': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortForm(NetBoxModelForm):
|
class ConsoleServerPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
fieldsets = (
|
||||||
required=False,
|
(None, (
|
||||||
query_params={
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
'device_id': '$device',
|
)),
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1199,42 +1263,32 @@ class ConsoleServerPortForm(NetBoxModelForm):
|
|||||||
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'speed', 'mark_connected', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'speed': StaticSelect(),
|
'speed': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerPortForm(NetBoxModelForm):
|
class PowerPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
fieldsets = (
|
||||||
required=False,
|
(None, (
|
||||||
query_params={
|
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
||||||
'device_id': '$device',
|
'description', 'tags',
|
||||||
}
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
'device', 'module', 'name', 'label', 'type', 'maximum_draw', 'allocated_draw', 'mark_connected',
|
||||||
'description',
|
'description', 'tags',
|
||||||
'tags',
|
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletForm(NetBoxModelForm):
|
class PowerOutletForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
|
||||||
required=False,
|
|
||||||
query_params={
|
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
power_port = DynamicModelChoiceField(
|
power_port = DynamicModelChoiceField(
|
||||||
queryset=PowerPort.objects.all(),
|
queryset=PowerPort.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1243,6 +1297,13 @@ class PowerOutletForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'power_port', 'feed_leg', 'mark_connected', 'description',
|
||||||
|
'tags',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
fields = [
|
fields = [
|
||||||
@ -1250,20 +1311,12 @@ class PowerOutletForm(NetBoxModelForm):
|
|||||||
'tags',
|
'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'feed_leg': StaticSelect(),
|
'feed_leg': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
|
||||||
required=False,
|
|
||||||
query_params={
|
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=Interface.objects.all(),
|
queryset=Interface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -1331,8 +1384,14 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
label='VRF'
|
label='VRF'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
wwn = forms.CharField(
|
||||||
|
empty_value=None,
|
||||||
|
required=False,
|
||||||
|
label='WWN'
|
||||||
|
)
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
('Interface', ('device', 'module', 'name', 'type', 'speed', 'duplex', 'label', 'description', 'tags')),
|
('Interface', ('device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags')),
|
||||||
('Addressing', ('vrf', 'mac_address', 'wwn')),
|
('Addressing', ('vrf', 'mac_address', 'wwn')),
|
||||||
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
('Operation', ('mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected')),
|
||||||
('Related Interfaces', ('parent', 'bridge', 'lag')),
|
('Related Interfaces', ('parent', 'bridge', 'lag')),
|
||||||
@ -1352,7 +1411,6 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
'speed': SelectSpeedWidget(),
|
'speed': SelectSpeedWidget(),
|
||||||
'poe_mode': StaticSelect(),
|
'poe_mode': StaticSelect(),
|
||||||
@ -1382,14 +1440,7 @@ class InterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
self.fields['bridge'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
|
self.fields['bridge'].widget.add_query_param('device_id', device.virtual_chassis.master.pk)
|
||||||
|
|
||||||
|
|
||||||
class FrontPortForm(NetBoxModelForm):
|
class FrontPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
|
||||||
queryset=Module.objects.all(),
|
|
||||||
required=False,
|
|
||||||
query_params={
|
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
rear_port = DynamicModelChoiceField(
|
rear_port = DynamicModelChoiceField(
|
||||||
queryset=RearPort.objects.all(),
|
queryset=RearPort.objects.all(),
|
||||||
query_params={
|
query_params={
|
||||||
@ -1397,6 +1448,13 @@ class FrontPortForm(NetBoxModelForm):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'mark_connected',
|
||||||
|
'description', 'tags',
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
fields = [
|
fields = [
|
||||||
@ -1404,18 +1462,15 @@ class FrontPortForm(NetBoxModelForm):
|
|||||||
'description', 'tags',
|
'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class RearPortForm(NetBoxModelForm):
|
class RearPortForm(ModularDeviceComponentForm):
|
||||||
module = DynamicModelChoiceField(
|
fieldsets = (
|
||||||
queryset=Module.objects.all(),
|
(None, (
|
||||||
required=False,
|
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
||||||
query_params={
|
)),
|
||||||
'device_id': '$device',
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -1424,33 +1479,32 @@ class RearPortForm(NetBoxModelForm):
|
|||||||
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
'device', 'module', 'name', 'label', 'type', 'color', 'positions', 'mark_connected', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
'type': StaticSelect(),
|
'type': StaticSelect(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayForm(NetBoxModelForm):
|
class ModuleBayForm(DeviceComponentForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device', 'name', 'label', 'position', 'description', 'tags',)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'name', 'label', 'position', 'description', 'tags',
|
'device', 'name', 'label', 'position', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayForm(NetBoxModelForm):
|
class DeviceBayForm(DeviceComponentForm):
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device', 'name', 'label', 'description', 'tags',)),
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
fields = [
|
fields = [
|
||||||
'device', 'name', 'label', 'description', 'tags',
|
'device', 'name', 'label', 'description', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
|
||||||
'device': forms.HiddenInput(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
||||||
@ -1473,10 +1527,7 @@ class PopulateDeviceBayForm(BootstrapMixin, forms.Form):
|
|||||||
).exclude(pk=device_bay.device.pk)
|
).exclude(pk=device_bay.device.pk)
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemForm(NetBoxModelForm):
|
class InventoryItemForm(DeviceComponentForm):
|
||||||
device = DynamicModelChoiceField(
|
|
||||||
queryset=Device.objects.all()
|
|
||||||
)
|
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=InventoryItem.objects.all(),
|
queryset=InventoryItem.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
|
@ -2,46 +2,56 @@ from django import forms
|
|||||||
|
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from utilities.forms import (
|
from utilities.forms import DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField
|
||||||
BootstrapMixin, DynamicModelChoiceField, DynamicModelMultipleChoiceField, ExpandableNameField,
|
from . import models as model_forms
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ComponentTemplateCreateForm',
|
'ComponentCreateForm',
|
||||||
'DeviceComponentCreateForm',
|
'ConsolePortCreateForm',
|
||||||
|
'ConsolePortTemplateCreateForm',
|
||||||
|
'ConsoleServerPortCreateForm',
|
||||||
|
'ConsoleServerPortTemplateCreateForm',
|
||||||
|
'DeviceBayCreateForm',
|
||||||
|
'DeviceBayTemplateCreateForm',
|
||||||
'FrontPortCreateForm',
|
'FrontPortCreateForm',
|
||||||
'FrontPortTemplateCreateForm',
|
'FrontPortTemplateCreateForm',
|
||||||
|
'InterfaceCreateForm',
|
||||||
|
'InterfaceTemplateCreateForm',
|
||||||
'InventoryItemCreateForm',
|
'InventoryItemCreateForm',
|
||||||
'ModularComponentTemplateCreateForm',
|
'InventoryItemTemplateCreateForm',
|
||||||
'ModuleBayCreateForm',
|
'ModuleBayCreateForm',
|
||||||
'ModuleBayTemplateCreateForm',
|
'ModuleBayTemplateCreateForm',
|
||||||
|
'PowerOutletCreateForm',
|
||||||
|
'PowerOutletTemplateCreateForm',
|
||||||
|
'PowerPortCreateForm',
|
||||||
|
'PowerPortTemplateCreateForm',
|
||||||
|
'RearPortCreateForm',
|
||||||
|
'RearPortTemplateCreateForm',
|
||||||
'VirtualChassisCreateForm',
|
'VirtualChassisCreateForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComponentCreateForm(BootstrapMixin, forms.Form):
|
class ComponentCreateForm(forms.Form):
|
||||||
"""
|
"""
|
||||||
Subclass this form when facilitating the creation of one or more device component or component templates based on
|
Subclass this form when facilitating the creation of one or more component or component template objects based on
|
||||||
a name pattern.
|
a name pattern.
|
||||||
"""
|
"""
|
||||||
name_pattern = ExpandableNameField(
|
name = ExpandableNameField()
|
||||||
label='Name'
|
label = ExpandableNameField(
|
||||||
)
|
|
||||||
label_pattern = ExpandableNameField(
|
|
||||||
label='Label',
|
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Identify the fields which support replication (i.e. ExpandableNameFields). This is referenced by
|
||||||
|
# ComponentCreateView when creating objects.
|
||||||
|
replication_fields = ('name', 'label')
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
# Validate that all patterned fields generate an equal number of values
|
# Validate that all replication fields generate an equal number of values
|
||||||
patterned_fields = [
|
pattern_count = len(self.cleaned_data[self.replication_fields[0]])
|
||||||
field_name for field_name in self.fields if field_name.endswith('_pattern')
|
for field_name in self.replication_fields:
|
||||||
]
|
|
||||||
pattern_count = len(self.cleaned_data['name_pattern'])
|
|
||||||
for field_name in patterned_fields:
|
|
||||||
value_count = len(self.cleaned_data[field_name])
|
value_count = len(self.cleaned_data[field_name])
|
||||||
if self.cleaned_data[field_name] and value_count != pattern_count:
|
if self.cleaned_data[field_name] and value_count != pattern_count:
|
||||||
raise forms.ValidationError({
|
raise forms.ValidationError({
|
||||||
@ -50,56 +60,55 @@ class ComponentCreateForm(BootstrapMixin, forms.Form):
|
|||||||
}, code='label_pattern_mismatch')
|
}, code='label_pattern_mismatch')
|
||||||
|
|
||||||
|
|
||||||
class ComponentTemplateCreateForm(ComponentCreateForm):
|
#
|
||||||
"""
|
# Device component templates
|
||||||
Creation form for component templates that can be assigned only to a DeviceType.
|
#
|
||||||
"""
|
|
||||||
device_type = DynamicModelChoiceField(
|
class ConsolePortTemplateCreateForm(ComponentCreateForm, model_forms.ConsolePortTemplateForm):
|
||||||
queryset=DeviceType.objects.all(),
|
|
||||||
)
|
class Meta(model_forms.ConsolePortTemplateForm.Meta):
|
||||||
field_order = ('device_type', 'name_pattern', 'label_pattern')
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
class ModularComponentTemplateCreateForm(ComponentCreateForm):
|
class ConsoleServerPortTemplateCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortTemplateForm):
|
||||||
"""
|
|
||||||
Creation form for component templates that can be assigned to either a DeviceType *or* a ModuleType.
|
class Meta(model_forms.ConsoleServerPortTemplateForm.Meta):
|
||||||
"""
|
exclude = ('name', 'label')
|
||||||
name_pattern = ExpandableNameField(
|
|
||||||
label='Name',
|
|
||||||
help_text="""
|
|
||||||
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
|
||||||
are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>. {module} is accepted as a substitution for
|
|
||||||
the module bay position.
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
device_type = DynamicModelChoiceField(
|
|
||||||
queryset=DeviceType.objects.all(),
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
module_type = DynamicModelChoiceField(
|
|
||||||
queryset=ModuleType.objects.all(),
|
|
||||||
required=False
|
|
||||||
)
|
|
||||||
field_order = ('device_type', 'module_type', 'name_pattern', 'label_pattern')
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceComponentCreateForm(ComponentCreateForm):
|
class PowerPortTemplateCreateForm(ComponentCreateForm, model_forms.PowerPortTemplateForm):
|
||||||
device = DynamicModelChoiceField(
|
|
||||||
queryset=Device.objects.all()
|
class Meta(model_forms.PowerPortTemplateForm.Meta):
|
||||||
)
|
exclude = ('name', 'label')
|
||||||
field_order = ('device', 'name_pattern', 'label_pattern')
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
class PowerOutletTemplateCreateForm(ComponentCreateForm, model_forms.PowerOutletTemplateForm):
|
||||||
rear_port_set = forms.MultipleChoiceField(
|
|
||||||
|
class Meta(model_forms.PowerOutletTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceTemplateCreateForm(ComponentCreateForm, model_forms.InterfaceTemplateForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.InterfaceTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class FrontPortTemplateCreateForm(ComponentCreateForm, model_forms.FrontPortTemplateForm):
|
||||||
|
rear_port = forms.MultipleChoiceField(
|
||||||
choices=[],
|
choices=[],
|
||||||
label='Rear ports',
|
label='Rear ports',
|
||||||
help_text='Select one rear port assignment for each front port being created.',
|
help_text='Select one rear port assignment for each front port being created.',
|
||||||
)
|
)
|
||||||
field_order = (
|
|
||||||
'device_type', 'name_pattern', 'label_pattern', 'rear_port_set',
|
# Override fieldsets from FrontPortTemplateForm to omit rear_port_position
|
||||||
|
fieldsets = (
|
||||||
|
(None, ('device_type', 'module_type', 'name', 'label', 'type', 'color', 'rear_port', 'description')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta(model_forms.FrontPortTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'rear_port', 'rear_port_position')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@ -130,12 +139,12 @@ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
|||||||
choices.append(
|
choices.append(
|
||||||
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
||||||
)
|
)
|
||||||
self.fields['rear_port_set'].choices = choices
|
self.fields['rear_port'].choices = choices
|
||||||
|
|
||||||
def get_iterative_data(self, iteration):
|
def get_iterative_data(self, iteration):
|
||||||
|
|
||||||
# Assign rear port and position from selected set
|
# Assign rear port and position from selected set
|
||||||
rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
|
rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'rear_port': int(rear_port),
|
'rear_port': int(rear_port),
|
||||||
@ -143,16 +152,94 @@ class FrontPortTemplateCreateForm(ModularComponentTemplateCreateForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class FrontPortCreateForm(DeviceComponentCreateForm):
|
class RearPortTemplateCreateForm(ComponentCreateForm, model_forms.RearPortTemplateForm):
|
||||||
rear_port_set = forms.MultipleChoiceField(
|
|
||||||
|
class Meta(model_forms.RearPortTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayTemplateCreateForm(ComponentCreateForm, model_forms.DeviceBayTemplateForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.DeviceBayTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleBayTemplateCreateForm(ComponentCreateForm, model_forms.ModuleBayTemplateForm):
|
||||||
|
position = ExpandableNameField(
|
||||||
|
label='Position',
|
||||||
|
required=False,
|
||||||
|
help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
|
||||||
|
)
|
||||||
|
replication_fields = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
class Meta(model_forms.ModuleBayTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
|
||||||
|
class InventoryItemTemplateCreateForm(ComponentCreateForm, model_forms.InventoryItemTemplateForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.InventoryItemTemplateForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Device components
|
||||||
|
#
|
||||||
|
|
||||||
|
class ConsolePortCreateForm(ComponentCreateForm, model_forms.ConsolePortForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.ConsolePortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleServerPortCreateForm(ComponentCreateForm, model_forms.ConsoleServerPortForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.ConsoleServerPortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class PowerPortCreateForm(ComponentCreateForm, model_forms.PowerPortForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.PowerPortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class PowerOutletCreateForm(ComponentCreateForm, model_forms.PowerOutletForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.PowerOutletForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class InterfaceCreateForm(ComponentCreateForm, model_forms.InterfaceForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.InterfaceForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if 'module' in self.fields:
|
||||||
|
self.fields['name'].help_text += ' The string <code>{module}</code> will be replaced with the position ' \
|
||||||
|
'of the assigned module, if any'
|
||||||
|
|
||||||
|
|
||||||
|
class FrontPortCreateForm(ComponentCreateForm, model_forms.FrontPortForm):
|
||||||
|
rear_port = forms.MultipleChoiceField(
|
||||||
choices=[],
|
choices=[],
|
||||||
label='Rear ports',
|
label='Rear ports',
|
||||||
help_text='Select one rear port assignment for each front port being created.',
|
help_text='Select one rear port assignment for each front port being created.',
|
||||||
)
|
)
|
||||||
field_order = (
|
|
||||||
'device', 'name_pattern', 'label_pattern', 'rear_port_set',
|
# Override fieldsets from FrontPortForm to omit rear_port_position
|
||||||
|
fieldsets = (
|
||||||
|
(None, (
|
||||||
|
'device', 'module', 'name', 'label', 'type', 'color', 'rear_port', 'mark_connected', 'description', 'tags',
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class Meta(model_forms.FrontPortForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'rear_port', 'rear_port_position')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@ -176,12 +263,12 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
|
|||||||
choices.append(
|
choices.append(
|
||||||
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
('{}:{}'.format(rear_port.pk, i), '{}:{}'.format(rear_port.name, i))
|
||||||
)
|
)
|
||||||
self.fields['rear_port_set'].choices = choices
|
self.fields['rear_port'].choices = choices
|
||||||
|
|
||||||
def get_iterative_data(self, iteration):
|
def get_iterative_data(self, iteration):
|
||||||
|
|
||||||
# Assign rear port and position from selected set
|
# Assign rear port and position from selected set
|
||||||
rear_port, position = self.cleaned_data['rear_port_set'][iteration].split(':')
|
rear_port, position = self.cleaned_data['rear_port'][iteration].split(':')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'rear_port': int(rear_port),
|
'rear_port': int(rear_port),
|
||||||
@ -189,28 +276,39 @@ class FrontPortCreateForm(DeviceComponentCreateForm):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateCreateForm(ComponentTemplateCreateForm):
|
class RearPortCreateForm(ComponentCreateForm, model_forms.RearPortForm):
|
||||||
position_pattern = ExpandableNameField(
|
|
||||||
|
class Meta(model_forms.RearPortForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceBayCreateForm(ComponentCreateForm, model_forms.DeviceBayForm):
|
||||||
|
|
||||||
|
class Meta(model_forms.DeviceBayForm.Meta):
|
||||||
|
exclude = ('name', 'label')
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleBayCreateForm(ComponentCreateForm, model_forms.ModuleBayForm):
|
||||||
|
position = ExpandableNameField(
|
||||||
label='Position',
|
label='Position',
|
||||||
required=False,
|
required=False,
|
||||||
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
help_text='Alphanumeric ranges are supported. (Must match the number of objects being created.)'
|
||||||
)
|
)
|
||||||
field_order = ('device_type', 'name_pattern', 'label_pattern', 'position_pattern')
|
replication_fields = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
class Meta(model_forms.ModuleBayForm.Meta):
|
||||||
|
exclude = ('name', 'label', 'position')
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayCreateForm(DeviceComponentCreateForm):
|
class InventoryItemCreateForm(ComponentCreateForm, model_forms.InventoryItemForm):
|
||||||
position_pattern = ExpandableNameField(
|
|
||||||
label='Position',
|
class Meta(model_forms.InventoryItemForm.Meta):
|
||||||
required=False,
|
exclude = ('name', 'label')
|
||||||
help_text='Alphanumeric ranges are supported. (Must match the number of names being created.)'
|
|
||||||
)
|
|
||||||
field_order = ('device', 'name_pattern', 'label_pattern', 'position_pattern')
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryItemCreateForm(ComponentCreateForm):
|
#
|
||||||
# Device is assigned by the model form
|
# Virtual chassis
|
||||||
field_order = ('name_pattern', 'label_pattern')
|
#
|
||||||
|
|
||||||
|
|
||||||
class VirtualChassisCreateForm(NetBoxModelForm):
|
class VirtualChassisCreateForm(NetBoxModelForm):
|
||||||
region = DynamicModelChoiceField(
|
region = DynamicModelChoiceField(
|
||||||
|
@ -281,15 +281,11 @@ class CableTermination(models.Model):
|
|||||||
|
|
||||||
# Validate interface type (if applicable)
|
# Validate interface type (if applicable)
|
||||||
if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES:
|
if self.termination_type.model == 'interface' and self.termination.type in NONCONNECTABLE_IFACE_TYPES:
|
||||||
raise ValidationError({
|
raise ValidationError(f"Cables cannot be terminated to {self.termination.get_type_display()} interfaces")
|
||||||
'termination': f'Cables cannot be terminated to {self.termination.get_type_display()} interfaces'
|
|
||||||
})
|
|
||||||
|
|
||||||
# A CircuitTermination attached to a ProviderNetwork cannot have a Cable
|
# A CircuitTermination attached to a ProviderNetwork cannot have a Cable
|
||||||
if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None:
|
if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None:
|
||||||
raise ValidationError({
|
raise ValidationError("Circuit terminations attached to a provider network may not be cabled.")
|
||||||
'termination': "Circuit terminations attached to a provider network may not be cabled."
|
|
||||||
})
|
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
|
|
||||||
|
@ -908,6 +908,8 @@ class FrontPort(ModularComponentModel, CabledObjectModel):
|
|||||||
def clean(self):
|
def clean(self):
|
||||||
super().clean()
|
super().clean()
|
||||||
|
|
||||||
|
if hasattr(self, 'rear_port'):
|
||||||
|
|
||||||
# Validate rear port assignment
|
# Validate rear port assignment
|
||||||
if self.rear_port.device != self.device:
|
if self.rear_port.device != self.device:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
|
@ -143,6 +143,15 @@ class DeviceTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
template_code=DEVICE_LINK
|
template_code=DEVICE_LINK
|
||||||
)
|
)
|
||||||
status = columns.ChoiceFieldColumn()
|
status = columns.ChoiceFieldColumn()
|
||||||
|
region = tables.Column(
|
||||||
|
accessor=Accessor('site__region'),
|
||||||
|
linkify=True
|
||||||
|
)
|
||||||
|
site_group = tables.Column(
|
||||||
|
accessor=Accessor('site__group'),
|
||||||
|
linkify=True,
|
||||||
|
verbose_name='Site Group'
|
||||||
|
)
|
||||||
site = tables.Column(
|
site = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
@ -152,6 +161,9 @@ class DeviceTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
rack = tables.Column(
|
rack = tables.Column(
|
||||||
linkify=True
|
linkify=True
|
||||||
)
|
)
|
||||||
|
position = columns.TemplateColumn(
|
||||||
|
template_code='{{ value|floatformat }}'
|
||||||
|
)
|
||||||
device_role = columns.ColoredLabelColumn(
|
device_role = columns.ColoredLabelColumn(
|
||||||
verbose_name='Role'
|
verbose_name='Role'
|
||||||
)
|
)
|
||||||
@ -199,10 +211,10 @@ class DeviceTable(TenancyColumnsMixin, NetBoxTable):
|
|||||||
class Meta(NetBoxTable.Meta):
|
class Meta(NetBoxTable.Meta):
|
||||||
model = Device
|
model = Device
|
||||||
fields = (
|
fields = (
|
||||||
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial',
|
'pk', 'id', 'name', 'status', 'tenant', 'tenant_group', 'device_role', 'manufacturer', 'device_type',
|
||||||
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'primary_ip', 'airflow', 'primary_ip4',
|
'platform', 'serial', 'asset_tag', 'region', 'site_group', 'site', 'location', 'rack', 'position', 'face',
|
||||||
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'contacts', 'tags',
|
'airflow', 'primary_ip', 'primary_ip4', 'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position',
|
||||||
'created', 'last_updated',
|
'vc_priority', 'comments', 'contacts', 'tags', 'created', 'last_updated',
|
||||||
)
|
)
|
||||||
default_columns = (
|
default_columns = (
|
||||||
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
|
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
|
||||||
|
@ -239,7 +239,7 @@ INTERFACE_BUTTONS = """
|
|||||||
<li><a class="dropdown-item" href="{% url 'dcim:inventoryitem_add' %}?device={{ record.device_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Inventory Item</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:inventoryitem_add' %}?device={{ record.device_id }}&component_type={{ record|content_type_id }}&component_id={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Inventory Item</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.dcim.add_interface %}
|
{% if perms.dcim.add_interface %}
|
||||||
<li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ record.device_id }}&parent={{ record.pk }}&name_pattern={{ record.name }}.&type=virtual&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Child Interface</a></li>
|
<li><a class="dropdown-item" href="{% url 'dcim:interface_add' %}?device={{ record.device_id }}&parent={{ record.pk }}&name={{ record.name }}.&type=virtual&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">Child Interface</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if perms.ipam.add_l2vpntermination %}
|
{% if perms.ipam.add_l2vpntermination %}
|
||||||
<li><a class="dropdown-item" href="{% url 'ipam:l2vpntermination_add' %}?device={{ object.pk }}&interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
<li><a class="dropdown-item" href="{% url 'ipam:l2vpntermination_add' %}?device={{ object.pk }}&interface={{ record.pk }}&return_url={% url 'dcim:device_interfaces' pk=object.pk %}">L2VPN Termination</a></li>
|
||||||
|
@ -2057,6 +2057,7 @@ class VirtualChassisTest(APIViewTestCases.APIViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_update_data = {
|
cls.bulk_update_data = {
|
||||||
'domain': 'newdomain',
|
'domain': 'newdomain',
|
||||||
|
'master': None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -688,7 +688,7 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
Manufacturer.objects.bulk_create(manufacturers)
|
Manufacturer.objects.bulk_create(manufacturers)
|
||||||
|
|
||||||
device_types = (
|
device_types = (
|
||||||
DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True),
|
DeviceType(manufacturer=manufacturers[0], model='Model 1', slug='model-1', part_number='Part Number 1', u_height=1, is_full_depth=True, front_image='front.png', rear_image='rear.png'),
|
||||||
DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR),
|
DeviceType(manufacturer=manufacturers[1], model='Model 2', slug='model-2', part_number='Part Number 2', u_height=2, is_full_depth=True, subdevice_role=SubdeviceRoleChoices.ROLE_PARENT, airflow=DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR),
|
||||||
DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT),
|
DeviceType(manufacturer=manufacturers[2], model='Model 3', slug='model-3', part_number='Part Number 3', u_height=3, is_full_depth=False, subdevice_role=SubdeviceRoleChoices.ROLE_CHILD, airflow=DeviceAirflowChoices.AIRFLOW_REAR_TO_FRONT),
|
||||||
)
|
)
|
||||||
@ -753,9 +753,9 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_is_full_depth(self):
|
def test_is_full_depth(self):
|
||||||
params = {'is_full_depth': 'true'}
|
params = {'is_full_depth': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'is_full_depth': 'false'}
|
params = {'is_full_depth': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_subdevice_role(self):
|
def test_subdevice_role(self):
|
||||||
@ -773,6 +773,18 @@ class DeviceTypeTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
|
params = {'manufacturer': [manufacturers[0].slug, manufacturers[1].slug]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_has_front_image(self):
|
||||||
|
params = {'has_front_image': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
params = {'has_front_image': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_has_rear_image(self):
|
||||||
|
params = {'has_rear_image': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
params = {'has_rear_image': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_console_ports(self):
|
def test_console_ports(self):
|
||||||
params = {'console_ports': 'true'}
|
params = {'console_ports': 'true'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
@ -1983,12 +1995,6 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'description': ['First', 'Second']}
|
params = {'description': ['First', 'Second']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_connected(self):
|
|
||||||
params = {'connected': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
params = {'connected': False}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_region(self):
|
def test_region(self):
|
||||||
regions = Region.objects.all()[:2]
|
regions = Region.objects.all()[:2]
|
||||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||||
@ -2037,9 +2043,21 @@ class ConsolePortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'occupied': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_connected(self):
|
||||||
|
params = {'connected': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'connected': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
@ -2144,12 +2162,6 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'description': ['First', 'Second']}
|
params = {'description': ['First', 'Second']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_connected(self):
|
|
||||||
params = {'connected': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
params = {'connected': False}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_region(self):
|
def test_region(self):
|
||||||
regions = Region.objects.all()[:2]
|
regions = Region.objects.all()[:2]
|
||||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||||
@ -2198,9 +2210,21 @@ class ConsoleServerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'occupied': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_connected(self):
|
||||||
|
params = {'connected': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'connected': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
@ -2313,12 +2337,6 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'allocated_draw': [50, 100]}
|
params = {'allocated_draw': [50, 100]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_connected(self):
|
|
||||||
params = {'connected': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
params = {'connected': False}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_region(self):
|
def test_region(self):
|
||||||
regions = Region.objects.all()[:2]
|
regions = Region.objects.all()[:2]
|
||||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||||
@ -2367,9 +2385,21 @@ class PowerPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'occupied': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_connected(self):
|
||||||
|
params = {'connected': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'connected': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
@ -2478,12 +2508,6 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'feed_leg': [PowerOutletFeedLegChoices.FEED_LEG_A, PowerOutletFeedLegChoices.FEED_LEG_B]}
|
params = {'feed_leg': [PowerOutletFeedLegChoices.FEED_LEG_A, PowerOutletFeedLegChoices.FEED_LEG_B]}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_connected(self):
|
|
||||||
params = {'connected': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
|
||||||
params = {'connected': False}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
|
||||||
|
|
||||||
def test_region(self):
|
def test_region(self):
|
||||||
regions = Region.objects.all()[:2]
|
regions = Region.objects.all()[:2]
|
||||||
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
params = {'region_id': [regions[0].pk, regions[1].pk]}
|
||||||
@ -2532,9 +2556,21 @@ class PowerOutletTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'occupied': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
def test_connected(self):
|
||||||
|
params = {'connected': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
params = {'connected': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
|
|
||||||
@ -2741,12 +2777,6 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
params = {'label': ['A', 'B']}
|
params = {'label': ['A', 'B']}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_connected(self):
|
|
||||||
params = {'connected': True}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
|
||||||
params = {'connected': False}
|
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
|
||||||
|
|
||||||
def test_enabled(self):
|
def test_enabled(self):
|
||||||
params = {'enabled': 'true'}
|
params = {'enabled': 'true'}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
|
||||||
@ -2880,9 +2910,21 @@ class InterfaceTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
params = {'occupied': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
|
def test_connected(self):
|
||||||
|
params = {'connected': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
params = {'connected': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
|
||||||
def test_kind(self):
|
def test_kind(self):
|
||||||
@ -3091,9 +3133,15 @@ class FrontPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
params = {'occupied': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
@ -3255,9 +3303,15 @@ class RearPortTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
def test_occupied(self):
|
||||||
|
params = {'occupied': True}
|
||||||
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
|
||||||
|
params = {'occupied': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
|
|
||||||
@ -4159,9 +4213,9 @@ class PowerFeedTestCase(TestCase, ChangeLoggedFilterSetTests):
|
|||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
|
|
||||||
def test_cabled(self):
|
def test_cabled(self):
|
||||||
params = {'cabled': 'true'}
|
params = {'cabled': True}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
|
||||||
params = {'cabled': 'false'}
|
params = {'cabled': False}
|
||||||
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
|
||||||
|
|
||||||
def test_connected(self):
|
def test_connected(self):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from dcim.choices import DeviceFaceChoices, DeviceStatusChoices
|
from dcim.choices import DeviceFaceChoices, DeviceStatusChoices, InterfaceTypeChoices
|
||||||
from dcim.forms import *
|
from dcim.forms import *
|
||||||
from dcim.models import *
|
from dcim.models import *
|
||||||
from utilities.testing import create_test_device
|
from utilities.testing import create_test_device
|
||||||
@ -129,10 +129,11 @@ class LabelTestCase(TestCase):
|
|||||||
"""
|
"""
|
||||||
interface_data = {
|
interface_data = {
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name_pattern': 'eth[0-9]',
|
'name': 'eth[0-9]',
|
||||||
'label_pattern': 'Interface[0-9]',
|
'label': 'Interface[0-9]',
|
||||||
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
}
|
}
|
||||||
form = DeviceComponentCreateForm(interface_data)
|
form = InterfaceCreateForm(interface_data)
|
||||||
|
|
||||||
self.assertTrue(form.is_valid())
|
self.assertTrue(form.is_valid())
|
||||||
|
|
||||||
@ -142,10 +143,11 @@ class LabelTestCase(TestCase):
|
|||||||
"""
|
"""
|
||||||
bad_interface_data = {
|
bad_interface_data = {
|
||||||
'device': self.device.pk,
|
'device': self.device.pk,
|
||||||
'name_pattern': 'eth[0-9]',
|
'name': 'eth[0-9]',
|
||||||
'label_pattern': 'Interface[0-1]',
|
'label': 'Interface[0-1]',
|
||||||
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
}
|
}
|
||||||
form = DeviceComponentCreateForm(bad_interface_data)
|
form = InterfaceCreateForm(bad_interface_data)
|
||||||
|
|
||||||
self.assertFalse(form.is_valid())
|
self.assertFalse(form.is_valid())
|
||||||
self.assertIn('label_pattern', form.errors)
|
self.assertIn('label', form.errors)
|
||||||
|
@ -1082,31 +1082,28 @@ front-ports:
|
|||||||
|
|
||||||
class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = ConsolePortTemplate
|
model = ConsolePortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
ConsolePortTemplate.objects.bulk_create((
|
ConsolePortTemplate.objects.bulk_create((
|
||||||
ConsolePortTemplate(device_type=devicetypes[0], name='Console Port Template 1'),
|
ConsolePortTemplate(device_type=devicetype, name='Console Port Template 1'),
|
||||||
ConsolePortTemplate(device_type=devicetypes[0], name='Console Port Template 2'),
|
ConsolePortTemplate(device_type=devicetype, name='Console Port Template 2'),
|
||||||
ConsolePortTemplate(device_type=devicetypes[0], name='Console Port Template 3'),
|
ConsolePortTemplate(device_type=devicetype, name='Console Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Console Port Template X',
|
'name': 'Console Port Template X',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Console Port Template [4-6]',
|
'name': 'Console Port Template [4-6]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1117,31 +1114,28 @@ class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|||||||
|
|
||||||
class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = ConsoleServerPortTemplate
|
model = ConsoleServerPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
ConsoleServerPortTemplate.objects.bulk_create((
|
ConsoleServerPortTemplate.objects.bulk_create((
|
||||||
ConsoleServerPortTemplate(device_type=devicetypes[0], name='Console Server Port Template 1'),
|
ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 1'),
|
||||||
ConsoleServerPortTemplate(device_type=devicetypes[0], name='Console Server Port Template 2'),
|
ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 2'),
|
||||||
ConsoleServerPortTemplate(device_type=devicetypes[0], name='Console Server Port Template 3'),
|
ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Console Server Port Template X',
|
'name': 'Console Server Port Template X',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Console Server Port Template [4-6]',
|
'name': 'Console Server Port Template [4-6]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1152,24 +1146,21 @@ class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateVie
|
|||||||
|
|
||||||
class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = PowerPortTemplate
|
model = PowerPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
PowerPortTemplate.objects.bulk_create((
|
PowerPortTemplate.objects.bulk_create((
|
||||||
PowerPortTemplate(device_type=devicetypes[0], name='Power Port Template 1'),
|
PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'),
|
||||||
PowerPortTemplate(device_type=devicetypes[0], name='Power Port Template 2'),
|
PowerPortTemplate(device_type=devicetype, name='Power Port Template 2'),
|
||||||
PowerPortTemplate(device_type=devicetypes[0], name='Power Port Template 3'),
|
PowerPortTemplate(device_type=devicetype, name='Power Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Power Port Template X',
|
'name': 'Power Port Template X',
|
||||||
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
||||||
'maximum_draw': 100,
|
'maximum_draw': 100,
|
||||||
@ -1177,8 +1168,8 @@ class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Power Port Template [4-6]',
|
'name': 'Power Port Template [4-6]',
|
||||||
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
||||||
'maximum_draw': 100,
|
'maximum_draw': 100,
|
||||||
'allocated_draw': 50,
|
'allocated_draw': 50,
|
||||||
@ -1193,6 +1184,7 @@ class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = PowerOutletTemplate
|
model = PowerOutletTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1220,7 +1212,7 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetype.pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Power Outlet Template [4-6]',
|
'name': 'Power Outlet Template [4-6]',
|
||||||
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
||||||
'power_port': powerports[0].pk,
|
'power_port': powerports[0].pk,
|
||||||
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
||||||
@ -1234,34 +1226,31 @@ class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestC
|
|||||||
|
|
||||||
class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = InterfaceTemplate
|
model = InterfaceTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
InterfaceTemplate.objects.bulk_create((
|
InterfaceTemplate.objects.bulk_create((
|
||||||
InterfaceTemplate(device_type=devicetypes[0], name='Interface Template 1'),
|
InterfaceTemplate(device_type=devicetype, name='Interface Template 1'),
|
||||||
InterfaceTemplate(device_type=devicetypes[0], name='Interface Template 2'),
|
InterfaceTemplate(device_type=devicetype, name='Interface Template 2'),
|
||||||
InterfaceTemplate(device_type=devicetypes[0], name='Interface Template 3'),
|
InterfaceTemplate(device_type=devicetype, name='Interface Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Interface Template X',
|
'name': 'Interface Template X',
|
||||||
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
'mgmt_only': True,
|
'mgmt_only': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Interface Template [4-6]',
|
'name': 'Interface Template [4-6]',
|
||||||
# Test that a label can be applied to each generated interface templates
|
# Test that a label can be applied to each generated interface templates
|
||||||
'label_pattern': 'Interface Template Label [3-5]',
|
'label': 'Interface Template Label [3-5]',
|
||||||
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
'mgmt_only': True,
|
'mgmt_only': True,
|
||||||
}
|
}
|
||||||
@ -1274,6 +1263,7 @@ class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = FrontPortTemplate
|
model = FrontPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label', 'rear_port')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1306,11 +1296,9 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetype.pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Front Port [4-6]',
|
'name': 'Front Port [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'rear_port_set': [
|
'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
|
||||||
'{}:1'.format(rp.pk) for rp in rearports[3:6]
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -1320,32 +1308,29 @@ class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = RearPortTemplate
|
model = RearPortTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
RearPortTemplate.objects.bulk_create((
|
RearPortTemplate.objects.bulk_create((
|
||||||
RearPortTemplate(device_type=devicetypes[0], name='Rear Port Template 1'),
|
RearPortTemplate(device_type=devicetype, name='Rear Port Template 1'),
|
||||||
RearPortTemplate(device_type=devicetypes[0], name='Rear Port Template 2'),
|
RearPortTemplate(device_type=devicetype, name='Rear Port Template 2'),
|
||||||
RearPortTemplate(device_type=devicetypes[0], name='Rear Port Template 3'),
|
RearPortTemplate(device_type=devicetype, name='Rear Port Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Rear Port Template X',
|
'name': 'Rear Port Template X',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'positions': 2,
|
'positions': 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Rear Port Template [4-6]',
|
'name': 'Rear Port Template [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'positions': 2,
|
'positions': 2,
|
||||||
}
|
}
|
||||||
@ -1357,30 +1342,27 @@ class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase
|
|||||||
|
|
||||||
class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = ModuleBayTemplate
|
model = ModuleBayTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
ModuleBayTemplate.objects.bulk_create((
|
ModuleBayTemplate.objects.bulk_create((
|
||||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 1'),
|
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 1'),
|
||||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 2'),
|
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 2'),
|
||||||
ModuleBayTemplate(device_type=devicetypes[0], name='Module Bay Template 3'),
|
ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Module Bay Template X',
|
'name': 'Module Bay Template X',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Module Bay Template [4-6]',
|
'name': 'Module Bay Template [4-6]',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -1390,30 +1372,27 @@ class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = DeviceBayTemplate
|
model = DeviceBayTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
|
||||||
devicetypes = (
|
devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1', subdevice_role=SubdeviceRoleChoices.ROLE_PARENT),
|
|
||||||
DeviceType(manufacturer=manufacturer, model='Device Type 2', slug='device-type-2', subdevice_role=SubdeviceRoleChoices.ROLE_PARENT),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
DeviceBayTemplate.objects.bulk_create((
|
DeviceBayTemplate.objects.bulk_create((
|
||||||
DeviceBayTemplate(device_type=devicetypes[0], name='Device Bay Template 1'),
|
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 1'),
|
||||||
DeviceBayTemplate(device_type=devicetypes[0], name='Device Bay Template 2'),
|
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 2'),
|
||||||
DeviceBayTemplate(device_type=devicetypes[0], name='Device Bay Template 3'),
|
DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 3'),
|
||||||
))
|
))
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Device Bay Template X',
|
'name': 'Device Bay Template X',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Device Bay Template [4-6]',
|
'name': 'Device Bay Template [4-6]',
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_edit_data = {
|
cls.bulk_edit_data = {
|
||||||
@ -1423,6 +1402,7 @@ class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCas
|
|||||||
|
|
||||||
class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
|
||||||
model = InventoryItemTemplate
|
model = InventoryItemTemplate
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1431,30 +1411,25 @@ class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTes
|
|||||||
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
|
||||||
)
|
)
|
||||||
Manufacturer.objects.bulk_create(manufacturers)
|
Manufacturer.objects.bulk_create(manufacturers)
|
||||||
|
devicetype = DeviceType.objects.create(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1')
|
||||||
devicetypes = (
|
|
||||||
DeviceType(manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'),
|
|
||||||
DeviceType(manufacturer=manufacturers[0], model='Device Type 2', slug='device-type-2'),
|
|
||||||
)
|
|
||||||
DeviceType.objects.bulk_create(devicetypes)
|
|
||||||
|
|
||||||
inventory_item_templates = (
|
inventory_item_templates = (
|
||||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 1', manufacturer=manufacturers[0]),
|
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 1', manufacturer=manufacturers[0]),
|
||||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 2', manufacturer=manufacturers[0]),
|
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 2', manufacturer=manufacturers[0]),
|
||||||
InventoryItemTemplate(device_type=devicetypes[0], name='Inventory Item Template 3', manufacturer=manufacturers[0]),
|
InventoryItemTemplate(device_type=devicetype, name='Inventory Item Template 3', manufacturer=manufacturers[0]),
|
||||||
)
|
)
|
||||||
for item in inventory_item_templates:
|
for item in inventory_item_templates:
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name': 'Inventory Item Template X',
|
'name': 'Inventory Item Template X',
|
||||||
'manufacturer': manufacturers[1].pk,
|
'manufacturer': manufacturers[1].pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device_type': devicetypes[1].pk,
|
'device_type': devicetype.pk,
|
||||||
'name_pattern': 'Inventory Item Template [4-6]',
|
'name': 'Inventory Item Template [4-6]',
|
||||||
'manufacturer': manufacturers[1].pk,
|
'manufacturer': manufacturers[1].pk,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1912,6 +1887,7 @@ class ModuleTestCase(
|
|||||||
|
|
||||||
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = ConsolePort
|
model = ConsolePort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1935,9 +1911,9 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Console Port [4-6]',
|
'name': 'Console Port [4-6]',
|
||||||
# Test that a label can be applied to each generated console ports
|
# Test that a label can be applied to each generated console ports
|
||||||
'label_pattern': 'Serial[3-5]',
|
'label': 'Serial[3-5]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
'description': 'A console port',
|
'description': 'A console port',
|
||||||
'tags': sorted([t.pk for t in tags]),
|
'tags': sorted([t.pk for t in tags]),
|
||||||
@ -1970,6 +1946,7 @@ class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = ConsoleServerPort
|
model = ConsoleServerPort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -1993,7 +1970,7 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Console Server Port [4-6]',
|
'name': 'Console Server Port [4-6]',
|
||||||
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
'type': ConsolePortTypeChoices.TYPE_RJ45,
|
||||||
'description': 'A console server port',
|
'description': 'A console server port',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
@ -2026,6 +2003,7 @@ class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = PowerPort
|
model = PowerPort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2051,7 +2029,7 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Power Port [4-6]]',
|
'name': 'Power Port [4-6]]',
|
||||||
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
'type': PowerPortTypeChoices.TYPE_IEC_C14,
|
||||||
'maximum_draw': 100,
|
'maximum_draw': 100,
|
||||||
'allocated_draw': 50,
|
'allocated_draw': 50,
|
||||||
@ -2088,6 +2066,7 @@ class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = PowerOutlet
|
model = PowerOutlet
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2119,7 +2098,7 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Power Outlet [4-6]',
|
'name': 'Power Outlet [4-6]',
|
||||||
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
'type': PowerOutletTypeChoices.TYPE_IEC_C13,
|
||||||
'power_port': powerports[1].pk,
|
'power_port': powerports[1].pk,
|
||||||
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
|
||||||
@ -2153,6 +2132,7 @@ class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = Interface
|
model = Interface
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2217,7 +2197,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Interface [4-6]',
|
'name': 'Interface [4-6]',
|
||||||
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'bridge': interfaces[4].pk,
|
'bridge': interfaces[4].pk,
|
||||||
@ -2277,6 +2257,7 @@ class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = FrontPort
|
model = FrontPort
|
||||||
|
validation_excluded_fields = ('name', 'label', 'rear_port')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2312,11 +2293,9 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Front Port [4-6]',
|
'name': 'Front Port [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'rear_port_set': [
|
'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
|
||||||
'{}:1'.format(rp.pk) for rp in rearports[3:6]
|
|
||||||
],
|
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2348,6 +2327,7 @@ class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = RearPort
|
model = RearPort
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2372,7 +2352,7 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Rear Port [4-6]',
|
'name': 'Rear Port [4-6]',
|
||||||
'type': PortTypeChoices.TYPE_8P8C,
|
'type': PortTypeChoices.TYPE_8P8C,
|
||||||
'positions': 3,
|
'positions': 3,
|
||||||
'description': 'A rear port',
|
'description': 'A rear port',
|
||||||
@ -2406,6 +2386,7 @@ class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = ModuleBay
|
model = ModuleBay
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2428,7 +2409,7 @@ class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Module Bay [4-6]',
|
'name': 'Module Bay [4-6]',
|
||||||
'description': 'A module bay',
|
'description': 'A module bay',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2447,6 +2428,7 @@ class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = DeviceBay
|
model = DeviceBay
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2472,7 +2454,7 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Device Bay [4-6]',
|
'name': 'Device Bay [4-6]',
|
||||||
'description': 'A device bay',
|
'description': 'A device bay',
|
||||||
'tags': [t.pk for t in tags],
|
'tags': [t.pk for t in tags],
|
||||||
}
|
}
|
||||||
@ -2491,6 +2473,7 @@ class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = InventoryItem
|
model = InventoryItem
|
||||||
|
validation_excluded_fields = ('name', 'label')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -2525,7 +2508,7 @@ class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'device': device.pk,
|
'device': device.pk,
|
||||||
'name_pattern': 'Inventory Item [4-6]',
|
'name': 'Inventory Item [4-6]',
|
||||||
'role': roles[1].pk,
|
'role': roles[1].pk,
|
||||||
'manufacturer': manufacturer.pk,
|
'manufacturer': manufacturer.pk,
|
||||||
'parent': None,
|
'parent': None,
|
||||||
|
@ -1120,9 +1120,8 @@ class ModuleTypeBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConsolePortTemplateCreateView(generic.ComponentCreateView):
|
class ConsolePortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsolePortTemplate.objects.all()
|
queryset = ConsolePortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.ConsolePortTemplateCreateForm
|
||||||
model_form = forms.ConsolePortTemplateForm
|
model_form = forms.ConsolePortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortTemplateEditView(generic.ObjectEditView):
|
class ConsolePortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1155,9 +1154,8 @@ class ConsolePortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
|
class ConsoleServerPortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsoleServerPortTemplate.objects.all()
|
queryset = ConsoleServerPortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.ConsoleServerPortTemplateCreateForm
|
||||||
model_form = forms.ConsoleServerPortTemplateForm
|
model_form = forms.ConsoleServerPortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortTemplateEditView(generic.ObjectEditView):
|
class ConsoleServerPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1190,9 +1188,8 @@ class ConsoleServerPortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerPortTemplateCreateView(generic.ComponentCreateView):
|
class PowerPortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerPortTemplate.objects.all()
|
queryset = PowerPortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.PowerPortTemplateCreateForm
|
||||||
model_form = forms.PowerPortTemplateForm
|
model_form = forms.PowerPortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerPortTemplateEditView(generic.ObjectEditView):
|
class PowerPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1225,9 +1222,8 @@ class PowerPortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class PowerOutletTemplateCreateView(generic.ComponentCreateView):
|
class PowerOutletTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerOutletTemplate.objects.all()
|
queryset = PowerOutletTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.PowerOutletTemplateCreateForm
|
||||||
model_form = forms.PowerOutletTemplateForm
|
model_form = forms.PowerOutletTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletTemplateEditView(generic.ObjectEditView):
|
class PowerOutletTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1260,9 +1256,8 @@ class PowerOutletTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class InterfaceTemplateCreateView(generic.ComponentCreateView):
|
class InterfaceTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = InterfaceTemplate.objects.all()
|
queryset = InterfaceTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.InterfaceTemplateCreateForm
|
||||||
model_form = forms.InterfaceTemplateForm
|
model_form = forms.InterfaceTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceTemplateEditView(generic.ObjectEditView):
|
class InterfaceTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1297,15 +1292,6 @@ class FrontPortTemplateCreateView(generic.ComponentCreateView):
|
|||||||
queryset = FrontPortTemplate.objects.all()
|
queryset = FrontPortTemplate.objects.all()
|
||||||
form = forms.FrontPortTemplateCreateForm
|
form = forms.FrontPortTemplateCreateForm
|
||||||
model_form = forms.FrontPortTemplateForm
|
model_form = forms.FrontPortTemplateForm
|
||||||
template_name = 'dcim/frontporttemplate_create.html'
|
|
||||||
|
|
||||||
def initialize_forms(self, request):
|
|
||||||
form, model_form = super().initialize_forms(request)
|
|
||||||
|
|
||||||
model_form.fields.pop('rear_port')
|
|
||||||
model_form.fields.pop('rear_port_position')
|
|
||||||
|
|
||||||
return form, model_form
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortTemplateEditView(generic.ObjectEditView):
|
class FrontPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1338,9 +1324,8 @@ class FrontPortTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class RearPortTemplateCreateView(generic.ComponentCreateView):
|
class RearPortTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = RearPortTemplate.objects.all()
|
queryset = RearPortTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.RearPortTemplateCreateForm
|
||||||
model_form = forms.RearPortTemplateForm
|
model_form = forms.RearPortTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortTemplateEditView(generic.ObjectEditView):
|
class RearPortTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1375,8 +1360,6 @@ class ModuleBayTemplateCreateView(generic.ComponentCreateView):
|
|||||||
queryset = ModuleBayTemplate.objects.all()
|
queryset = ModuleBayTemplate.objects.all()
|
||||||
form = forms.ModuleBayTemplateCreateForm
|
form = forms.ModuleBayTemplateCreateForm
|
||||||
model_form = forms.ModuleBayTemplateForm
|
model_form = forms.ModuleBayTemplateForm
|
||||||
template_name = 'dcim/modulebaytemplate_create.html'
|
|
||||||
patterned_fields = ('name', 'label', 'position')
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayTemplateEditView(generic.ObjectEditView):
|
class ModuleBayTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1409,9 +1392,8 @@ class ModuleBayTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class DeviceBayTemplateCreateView(generic.ComponentCreateView):
|
class DeviceBayTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = DeviceBayTemplate.objects.all()
|
queryset = DeviceBayTemplate.objects.all()
|
||||||
form = forms.ComponentTemplateCreateForm
|
form = forms.DeviceBayTemplateCreateForm
|
||||||
model_form = forms.DeviceBayTemplateForm
|
model_form = forms.DeviceBayTemplateForm
|
||||||
template_name = 'dcim/component_template_create.html'
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayTemplateEditView(generic.ObjectEditView):
|
class DeviceBayTemplateEditView(generic.ObjectEditView):
|
||||||
@ -1444,9 +1426,8 @@ class DeviceBayTemplateBulkDeleteView(generic.BulkDeleteView):
|
|||||||
|
|
||||||
class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
class InventoryItemTemplateCreateView(generic.ComponentCreateView):
|
||||||
queryset = InventoryItemTemplate.objects.all()
|
queryset = InventoryItemTemplate.objects.all()
|
||||||
form = forms.ModularComponentTemplateCreateForm
|
form = forms.InventoryItemTemplateCreateForm
|
||||||
model_form = forms.InventoryItemTemplateForm
|
model_form = forms.InventoryItemTemplateForm
|
||||||
template_name = 'dcim/inventoryitemtemplate_create.html'
|
|
||||||
|
|
||||||
def alter_object(self, instance, request):
|
def alter_object(self, instance, request):
|
||||||
# Set component (if any)
|
# Set component (if any)
|
||||||
@ -1874,14 +1855,13 @@ class ConsolePortView(generic.ObjectView):
|
|||||||
|
|
||||||
class ConsolePortCreateView(generic.ComponentCreateView):
|
class ConsolePortCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.ConsolePortCreateForm
|
||||||
model_form = forms.ConsolePortForm
|
model_form = forms.ConsolePortForm
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortEditView(generic.ObjectEditView):
|
class ConsolePortEditView(generic.ObjectEditView):
|
||||||
queryset = ConsolePort.objects.all()
|
queryset = ConsolePort.objects.all()
|
||||||
form = forms.ConsolePortForm
|
form = forms.ConsolePortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsolePortDeleteView(generic.ObjectDeleteView):
|
class ConsolePortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -1933,14 +1913,13 @@ class ConsoleServerPortView(generic.ObjectView):
|
|||||||
|
|
||||||
class ConsoleServerPortCreateView(generic.ComponentCreateView):
|
class ConsoleServerPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.ConsoleServerPortCreateForm
|
||||||
model_form = forms.ConsoleServerPortForm
|
model_form = forms.ConsoleServerPortForm
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortEditView(generic.ObjectEditView):
|
class ConsoleServerPortEditView(generic.ObjectEditView):
|
||||||
queryset = ConsoleServerPort.objects.all()
|
queryset = ConsoleServerPort.objects.all()
|
||||||
form = forms.ConsoleServerPortForm
|
form = forms.ConsoleServerPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleServerPortDeleteView(generic.ObjectDeleteView):
|
class ConsoleServerPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -1992,14 +1971,13 @@ class PowerPortView(generic.ObjectView):
|
|||||||
|
|
||||||
class PowerPortCreateView(generic.ComponentCreateView):
|
class PowerPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.PowerPortCreateForm
|
||||||
model_form = forms.PowerPortForm
|
model_form = forms.PowerPortForm
|
||||||
|
|
||||||
|
|
||||||
class PowerPortEditView(generic.ObjectEditView):
|
class PowerPortEditView(generic.ObjectEditView):
|
||||||
queryset = PowerPort.objects.all()
|
queryset = PowerPort.objects.all()
|
||||||
form = forms.PowerPortForm
|
form = forms.PowerPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerPortDeleteView(generic.ObjectDeleteView):
|
class PowerPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2051,14 +2029,13 @@ class PowerOutletView(generic.ObjectView):
|
|||||||
|
|
||||||
class PowerOutletCreateView(generic.ComponentCreateView):
|
class PowerOutletCreateView(generic.ComponentCreateView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.PowerOutletCreateForm
|
||||||
model_form = forms.PowerOutletForm
|
model_form = forms.PowerOutletForm
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletEditView(generic.ObjectEditView):
|
class PowerOutletEditView(generic.ObjectEditView):
|
||||||
queryset = PowerOutlet.objects.all()
|
queryset = PowerOutlet.objects.all()
|
||||||
form = forms.PowerOutletForm
|
form = forms.PowerOutletForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class PowerOutletDeleteView(generic.ObjectDeleteView):
|
class PowerOutletDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2154,42 +2131,13 @@ class InterfaceView(generic.ObjectView):
|
|||||||
|
|
||||||
class InterfaceCreateView(generic.ComponentCreateView):
|
class InterfaceCreateView(generic.ComponentCreateView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.InterfaceCreateForm
|
||||||
model_form = forms.InterfaceForm
|
model_form = forms.InterfaceForm
|
||||||
# template_name = 'dcim/interface_create.html'
|
|
||||||
|
|
||||||
# TODO: Figure out what to do with this
|
|
||||||
# def post(self, request):
|
|
||||||
# """
|
|
||||||
# Override inherited post() method to handle request to assign newly created
|
|
||||||
# interface objects (first object) to an IP Address object.
|
|
||||||
# """
|
|
||||||
# form = self.form(request.POST, initial=request.GET)
|
|
||||||
# new_objs = self.validate_form(request, form)
|
|
||||||
#
|
|
||||||
# if form.is_valid() and not form.errors:
|
|
||||||
# if '_addanother' in request.POST:
|
|
||||||
# return redirect(request.get_full_path())
|
|
||||||
# elif new_objs is not None and '_assignip' in request.POST and len(new_objs) >= 1 and \
|
|
||||||
# request.user.has_perm('ipam.add_ipaddress'):
|
|
||||||
# first_obj = new_objs[0].pk
|
|
||||||
# return redirect(
|
|
||||||
# f'/ipam/ip-addresses/add/?interface={first_obj}&return_url={self.get_return_url(request)}'
|
|
||||||
# )
|
|
||||||
# else:
|
|
||||||
# return redirect(self.get_return_url(request))
|
|
||||||
#
|
|
||||||
# return render(request, self.template_name, {
|
|
||||||
# 'obj_type': self.queryset.model._meta.verbose_name,
|
|
||||||
# 'form': form,
|
|
||||||
# 'return_url': self.get_return_url(request),
|
|
||||||
# })
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceEditView(generic.ObjectEditView):
|
class InterfaceEditView(generic.ObjectEditView):
|
||||||
queryset = Interface.objects.all()
|
queryset = Interface.objects.all()
|
||||||
form = forms.InterfaceForm
|
form = forms.InterfaceForm
|
||||||
template_name = 'dcim/interface_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class InterfaceDeleteView(generic.ObjectDeleteView):
|
class InterfaceDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2244,19 +2192,10 @@ class FrontPortCreateView(generic.ComponentCreateView):
|
|||||||
form = forms.FrontPortCreateForm
|
form = forms.FrontPortCreateForm
|
||||||
model_form = forms.FrontPortForm
|
model_form = forms.FrontPortForm
|
||||||
|
|
||||||
def initialize_forms(self, request):
|
|
||||||
form, model_form = super().initialize_forms(request)
|
|
||||||
|
|
||||||
model_form.fields.pop('rear_port')
|
|
||||||
model_form.fields.pop('rear_port_position')
|
|
||||||
|
|
||||||
return form, model_form
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortEditView(generic.ObjectEditView):
|
class FrontPortEditView(generic.ObjectEditView):
|
||||||
queryset = FrontPort.objects.all()
|
queryset = FrontPort.objects.all()
|
||||||
form = forms.FrontPortForm
|
form = forms.FrontPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class FrontPortDeleteView(generic.ObjectDeleteView):
|
class FrontPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2308,14 +2247,13 @@ class RearPortView(generic.ObjectView):
|
|||||||
|
|
||||||
class RearPortCreateView(generic.ComponentCreateView):
|
class RearPortCreateView(generic.ComponentCreateView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.RearPortCreateForm
|
||||||
model_form = forms.RearPortForm
|
model_form = forms.RearPortForm
|
||||||
|
|
||||||
|
|
||||||
class RearPortEditView(generic.ObjectEditView):
|
class RearPortEditView(generic.ObjectEditView):
|
||||||
queryset = RearPort.objects.all()
|
queryset = RearPort.objects.all()
|
||||||
form = forms.RearPortForm
|
form = forms.RearPortForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class RearPortDeleteView(generic.ObjectDeleteView):
|
class RearPortDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2369,13 +2307,11 @@ class ModuleBayCreateView(generic.ComponentCreateView):
|
|||||||
queryset = ModuleBay.objects.all()
|
queryset = ModuleBay.objects.all()
|
||||||
form = forms.ModuleBayCreateForm
|
form = forms.ModuleBayCreateForm
|
||||||
model_form = forms.ModuleBayForm
|
model_form = forms.ModuleBayForm
|
||||||
patterned_fields = ('name', 'label', 'position')
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayEditView(generic.ObjectEditView):
|
class ModuleBayEditView(generic.ObjectEditView):
|
||||||
queryset = ModuleBay.objects.all()
|
queryset = ModuleBay.objects.all()
|
||||||
form = forms.ModuleBayForm
|
form = forms.ModuleBayForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleBayDeleteView(generic.ObjectDeleteView):
|
class ModuleBayDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2423,14 +2359,13 @@ class DeviceBayView(generic.ObjectView):
|
|||||||
|
|
||||||
class DeviceBayCreateView(generic.ComponentCreateView):
|
class DeviceBayCreateView(generic.ComponentCreateView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
form = forms.DeviceComponentCreateForm
|
form = forms.DeviceBayCreateForm
|
||||||
model_form = forms.DeviceBayForm
|
model_form = forms.DeviceBayForm
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayEditView(generic.ObjectEditView):
|
class DeviceBayEditView(generic.ObjectEditView):
|
||||||
queryset = DeviceBay.objects.all()
|
queryset = DeviceBay.objects.all()
|
||||||
form = forms.DeviceBayForm
|
form = forms.DeviceBayForm
|
||||||
template_name = 'dcim/device_component_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBayDeleteView(generic.ObjectDeleteView):
|
class DeviceBayDeleteView(generic.ObjectDeleteView):
|
||||||
@ -2552,7 +2487,6 @@ class InventoryItemCreateView(generic.ComponentCreateView):
|
|||||||
queryset = InventoryItem.objects.all()
|
queryset = InventoryItem.objects.all()
|
||||||
form = forms.InventoryItemCreateForm
|
form = forms.InventoryItemCreateForm
|
||||||
model_form = forms.InventoryItemForm
|
model_form = forms.InventoryItemForm
|
||||||
template_name = 'dcim/inventoryitem_create.html'
|
|
||||||
|
|
||||||
def alter_object(self, instance, request):
|
def alter_object(self, instance, request):
|
||||||
# Set component (if any)
|
# Set component (if any)
|
||||||
@ -2736,7 +2670,6 @@ class DeviceBulkAddModuleBayView(generic.BulkComponentCreateView):
|
|||||||
filterset = filtersets.DeviceFilterSet
|
filterset = filtersets.DeviceFilterSet
|
||||||
table = tables.DeviceTable
|
table = tables.DeviceTable
|
||||||
default_return_url = 'dcim:device_list'
|
default_return_url = 'dcim:device_list'
|
||||||
patterned_fields = ('name', 'label', 'position')
|
|
||||||
|
|
||||||
|
|
||||||
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
class DeviceBulkAddDeviceBayView(generic.BulkComponentCreateView):
|
||||||
|
@ -159,7 +159,7 @@ class ReportViewSet(ViewSet):
|
|||||||
# Read the PK as "<module>.<report>"
|
# Read the PK as "<module>.<report>"
|
||||||
if '.' not in pk:
|
if '.' not in pk:
|
||||||
raise Http404
|
raise Http404
|
||||||
module_name, report_name = pk.split('.', 1)
|
module_name, report_name = pk.split('.', maxsplit=1)
|
||||||
|
|
||||||
# Raise a 404 on an invalid Report module/name
|
# Raise a 404 on an invalid Report module/name
|
||||||
report = get_report(module_name, report_name)
|
report = get_report(module_name, report_name)
|
||||||
@ -183,8 +183,8 @@ class ReportViewSet(ViewSet):
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Iterate through all available Reports.
|
# Iterate through all available Reports.
|
||||||
for module_name, reports in get_reports():
|
for module_name, reports in get_reports().items():
|
||||||
for report in reports:
|
for report in reports.values():
|
||||||
|
|
||||||
# Attach the relevant JobResult (if any) to each Report.
|
# Attach the relevant JobResult (if any) to each Report.
|
||||||
report.result = results.get(report.full_name, None)
|
report.result = results.get(report.full_name, None)
|
||||||
@ -257,7 +257,7 @@ class ScriptViewSet(ViewSet):
|
|||||||
lookup_value_regex = '[^/]+' # Allow dots
|
lookup_value_regex = '[^/]+' # Allow dots
|
||||||
|
|
||||||
def _get_script(self, pk):
|
def _get_script(self, pk):
|
||||||
module_name, script_name = pk.split('.')
|
module_name, script_name = pk.split('.', maxsplit=1)
|
||||||
script = get_script(module_name, script_name)
|
script = get_script(module_name, script_name)
|
||||||
if script is None:
|
if script is None:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
@ -3,7 +3,7 @@ from django.contrib.contenttypes.models import ContentType
|
|||||||
from django.contrib.postgres.forms import SimpleArrayField
|
from django.contrib.postgres.forms import SimpleArrayField
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from extras.choices import CustomFieldTypeChoices
|
from extras.choices import CustomFieldVisibilityChoices, CustomFieldTypeChoices
|
||||||
from extras.models import *
|
from extras.models import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelForm, CSVMultipleContentTypeField, SlugField
|
from utilities.forms import CSVChoiceField, CSVContentTypeField, CSVModelForm, CSVMultipleContentTypeField, SlugField
|
||||||
@ -38,6 +38,10 @@ class CustomFieldCSVForm(CSVModelForm):
|
|||||||
required=False,
|
required=False,
|
||||||
help_text='Comma-separated list of field choices'
|
help_text='Comma-separated list of field choices'
|
||||||
)
|
)
|
||||||
|
ui_visibility = CSVChoiceField(
|
||||||
|
choices=CustomFieldVisibilityChoices,
|
||||||
|
help_text='How the custom field is displayed in the user interface'
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CustomField
|
model = CustomField
|
||||||
|
@ -21,8 +21,8 @@ class Command(BaseCommand):
|
|||||||
reports = get_reports()
|
reports = get_reports()
|
||||||
|
|
||||||
# Run reports
|
# Run reports
|
||||||
for module_name, report_list in reports:
|
for module_name, report_list in reports.items():
|
||||||
for report in report_list:
|
for report in report_list.values():
|
||||||
if module_name in options['reports'] or report.full_name in options['reports']:
|
if module_name in options['reports'] or report.full_name in options['reports']:
|
||||||
|
|
||||||
# Run the report and create a new JobResult
|
# Run the report and create a new JobResult
|
||||||
|
@ -14,7 +14,7 @@ from django.utils.safestring import mark_safe
|
|||||||
from extras.choices import *
|
from extras.choices import *
|
||||||
from extras.utils import FeatureQuery
|
from extras.utils import FeatureQuery
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
from netbox.models.features import ExportTemplatesMixin, WebhooksMixin
|
from netbox.models.features import CloningMixin, ExportTemplatesMixin, WebhooksMixin
|
||||||
from utilities import filters
|
from utilities import filters
|
||||||
from utilities.forms import (
|
from utilities.forms import (
|
||||||
CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
CSVChoiceField, CSVMultipleChoiceField, DatePicker, DynamicModelChoiceField, DynamicModelMultipleChoiceField,
|
||||||
@ -41,7 +41,7 @@ class CustomFieldManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|||||||
return self.get_queryset().filter(content_types=content_type)
|
return self.get_queryset().filter(content_types=content_type)
|
||||||
|
|
||||||
|
|
||||||
class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
class CustomField(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||||
content_types = models.ManyToManyField(
|
content_types = models.ManyToManyField(
|
||||||
to=ContentType,
|
to=ContentType,
|
||||||
related_name='custom_fields',
|
related_name='custom_fields',
|
||||||
@ -143,8 +143,14 @@ class CustomField(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
|||||||
verbose_name='UI visibility',
|
verbose_name='UI visibility',
|
||||||
help_text='Specifies the visibility of custom field in the UI'
|
help_text='Specifies the visibility of custom field in the UI'
|
||||||
)
|
)
|
||||||
|
|
||||||
objects = CustomFieldManager()
|
objects = CustomFieldManager()
|
||||||
|
|
||||||
|
clone_fields = (
|
||||||
|
'content_types', 'type', 'object_type', 'group_name', 'description', 'required', 'filter_logic', 'default',
|
||||||
|
'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices', 'ui_visibility',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['group_name', 'weight', 'name']
|
ordering = ['group_name', 'weight', 'name']
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ from extras.conditions import ConditionSet
|
|||||||
from extras.utils import FeatureQuery, image_upload
|
from extras.utils import FeatureQuery, image_upload
|
||||||
from netbox.models import ChangeLoggedModel
|
from netbox.models import ChangeLoggedModel
|
||||||
from netbox.models.features import (
|
from netbox.models.features import (
|
||||||
CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin,
|
CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, TagsMixin, WebhooksMixin,
|
||||||
)
|
)
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from utilities.utils import render_jinja2
|
from utilities.utils import render_jinja2
|
||||||
@ -187,7 +187,7 @@ class Webhook(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
|||||||
return render_jinja2(self.payload_url, context)
|
return render_jinja2(self.payload_url, context)
|
||||||
|
|
||||||
|
|
||||||
class CustomLink(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
class CustomLink(CloningMixin, ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
||||||
"""
|
"""
|
||||||
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
A custom link to an external representation of a NetBox object. The link text and URL fields accept Jinja2 template
|
||||||
code to be rendered with an object as context.
|
code to be rendered with an object as context.
|
||||||
@ -230,6 +230,10 @@ class CustomLink(ExportTemplatesMixin, WebhooksMixin, ChangeLoggedModel):
|
|||||||
help_text="Force link to open in a new window"
|
help_text="Force link to open in a new window"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
clone_fields = (
|
||||||
|
'content_type', 'enabled', 'weight', 'group_name', 'button_class', 'new_window',
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['group_name', 'weight', 'name']
|
ordering = ['group_name', 'weight', 'name']
|
||||||
|
|
||||||
|
@ -26,20 +26,18 @@ def get_report(module_name, report_name):
|
|||||||
"""
|
"""
|
||||||
Return a specific report from within a module.
|
Return a specific report from within a module.
|
||||||
"""
|
"""
|
||||||
file_path = '{}/{}.py'.format(settings.REPORTS_ROOT, module_name)
|
reports = get_reports()
|
||||||
|
module = reports.get(module_name)
|
||||||
|
|
||||||
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
if module is None:
|
||||||
module = importlib.util.module_from_spec(spec)
|
|
||||||
try:
|
|
||||||
spec.loader.exec_module(module)
|
|
||||||
except FileNotFoundError:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
report = getattr(module, report_name, None)
|
report = module.get(report_name)
|
||||||
|
|
||||||
if report is None:
|
if report is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return report()
|
return report
|
||||||
|
|
||||||
|
|
||||||
def get_reports():
|
def get_reports():
|
||||||
@ -52,7 +50,7 @@ def get_reports():
|
|||||||
...
|
...
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
module_list = []
|
module_list = {}
|
||||||
|
|
||||||
# Iterate through all modules within the reports path. These are the user-created files in which reports are
|
# Iterate through all modules within the reports path. These are the user-created files in which reports are
|
||||||
# defined.
|
# defined.
|
||||||
@ -61,7 +59,16 @@ def get_reports():
|
|||||||
report_order = getattr(module, "report_order", ())
|
report_order = getattr(module, "report_order", ())
|
||||||
ordered_reports = [cls() for cls in report_order if is_report(cls)]
|
ordered_reports = [cls() for cls in report_order if is_report(cls)]
|
||||||
unordered_reports = [cls() for _, cls in inspect.getmembers(module, is_report) if cls not in report_order]
|
unordered_reports = [cls() for _, cls in inspect.getmembers(module, is_report) if cls not in report_order]
|
||||||
module_list.append((module_name, [*ordered_reports, *unordered_reports]))
|
|
||||||
|
module_reports = {}
|
||||||
|
|
||||||
|
for cls in [*ordered_reports, *unordered_reports]:
|
||||||
|
# For reports in submodules use the full import path w/o the root module as the name
|
||||||
|
report_name = cls.full_name.split(".", maxsplit=1)[1]
|
||||||
|
module_reports[report_name] = cls
|
||||||
|
|
||||||
|
if module_reports:
|
||||||
|
module_list[module_name] = module_reports
|
||||||
|
|
||||||
return module_list
|
return module_list
|
||||||
|
|
||||||
|
@ -299,6 +299,10 @@ class BaseScript:
|
|||||||
def module(cls):
|
def module(cls):
|
||||||
return cls.__module__
|
return cls.__module__
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def root_module(cls):
|
||||||
|
return cls.__module__.split(".")[0]
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def job_timeout(self):
|
def job_timeout(self):
|
||||||
return getattr(self.Meta, 'job_timeout', None)
|
return getattr(self.Meta, 'job_timeout', None)
|
||||||
@ -514,7 +518,9 @@ def get_scripts(use_names=False):
|
|||||||
ordered_scripts = [cls for cls in script_order if is_script(cls)]
|
ordered_scripts = [cls for cls in script_order if is_script(cls)]
|
||||||
unordered_scripts = [cls for _, cls in inspect.getmembers(module, is_script) if cls not in script_order]
|
unordered_scripts = [cls for _, cls in inspect.getmembers(module, is_script) if cls not in script_order]
|
||||||
for cls in [*ordered_scripts, *unordered_scripts]:
|
for cls in [*ordered_scripts, *unordered_scripts]:
|
||||||
module_scripts[cls.__name__] = cls
|
# For scripts in submodules use the full import path w/o the root module as the name
|
||||||
|
script_name = cls.full_name.split(".", maxsplit=1)[1]
|
||||||
|
module_scripts[script_name] = cls
|
||||||
if module_scripts:
|
if module_scripts:
|
||||||
scripts[module_name] = module_scripts
|
scripts[module_name] = module_scripts
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from django.urls import path
|
from django.urls import path, re_path
|
||||||
|
|
||||||
from extras import models, views
|
from extras import models, views
|
||||||
from netbox.views.generic import ObjectChangeLogView
|
from netbox.views.generic import ObjectChangeLogView
|
||||||
@ -100,12 +100,12 @@ urlpatterns = [
|
|||||||
|
|
||||||
# Reports
|
# Reports
|
||||||
path('reports/', views.ReportListView.as_view(), name='report_list'),
|
path('reports/', views.ReportListView.as_view(), name='report_list'),
|
||||||
path('reports/<str:module>.<str:name>/', views.ReportView.as_view(), name='report'),
|
|
||||||
path('reports/results/<int:job_result_pk>/', views.ReportResultView.as_view(), name='report_result'),
|
path('reports/results/<int:job_result_pk>/', views.ReportResultView.as_view(), name='report_result'),
|
||||||
|
re_path(r'^reports/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ReportView.as_view(), name='report'),
|
||||||
|
|
||||||
# Scripts
|
# Scripts
|
||||||
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
|
path('scripts/', views.ScriptListView.as_view(), name='script_list'),
|
||||||
path('scripts/<str:module>.<str:name>/', views.ScriptView.as_view(), name='script'),
|
|
||||||
path('scripts/results/<int:job_result_pk>/', views.ScriptResultView.as_view(), name='script_result'),
|
path('scripts/results/<int:job_result_pk>/', views.ScriptResultView.as_view(), name='script_result'),
|
||||||
|
re_path(r'^scripts/(?P<module>.([^.]+)).(?P<name>.(.+))/', views.ScriptView.as_view(), name='script'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -441,6 +441,12 @@ class ImageAttachmentEditView(generic.ObjectEditView):
|
|||||||
def get_return_url(self, request, obj=None):
|
def get_return_url(self, request, obj=None):
|
||||||
return obj.parent.get_absolute_url() if obj else super().get_return_url(request)
|
return obj.parent.get_absolute_url() if obj else super().get_return_url(request)
|
||||||
|
|
||||||
|
def get_extra_addanother_params(self, request):
|
||||||
|
return {
|
||||||
|
'content_type': request.GET.get('content_type'),
|
||||||
|
'object_id': request.GET.get('object_id'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ImageAttachmentDeleteView(generic.ObjectDeleteView):
|
class ImageAttachmentDeleteView(generic.ObjectDeleteView):
|
||||||
queryset = ImageAttachment.objects.all()
|
queryset = ImageAttachment.objects.all()
|
||||||
@ -528,9 +534,10 @@ class ReportListView(ContentTypePermissionRequiredMixin, View):
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret = []
|
ret = []
|
||||||
for module, report_list in reports:
|
|
||||||
|
for module, report_list in reports.items():
|
||||||
module_reports = []
|
module_reports = []
|
||||||
for report in report_list:
|
for report in report_list.values():
|
||||||
report.result = results.get(report.full_name, None)
|
report.result = results.get(report.full_name, None)
|
||||||
module_reports.append(report)
|
module_reports.append(report)
|
||||||
ret.append((module, module_reports))
|
ret.append((module, module_reports))
|
||||||
@ -607,7 +614,7 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View):
|
|||||||
result = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type)
|
result = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type)
|
||||||
|
|
||||||
# Retrieve the Report and attach the JobResult to it
|
# Retrieve the Report and attach the JobResult to it
|
||||||
module, report_name = result.name.split('.')
|
module, report_name = result.name.split('.', maxsplit=1)
|
||||||
report = get_report(module, report_name)
|
report = get_report(module, report_name)
|
||||||
report.result = result
|
report.result = result
|
||||||
|
|
||||||
|
@ -854,6 +854,7 @@ class ServiceCreateForm(ServiceForm):
|
|||||||
del self.fields[field].widget.attrs['required']
|
del self.fields[field].widget.attrs['required']
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
if self.cleaned_data['service_template']:
|
if self.cleaned_data['service_template']:
|
||||||
# Create a new Service from the specified template
|
# Create a new Service from the specified template
|
||||||
service_template = self.cleaned_data['service_template']
|
service_template = self.cleaned_data['service_template']
|
||||||
|
@ -21,6 +21,14 @@ __all__ = (
|
|||||||
AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
|
AVAILABLE_LABEL = mark_safe('<span class="badge bg-success">Available</span>')
|
||||||
|
|
||||||
PREFIX_LINK = """
|
PREFIX_LINK = """
|
||||||
|
{% if record.pk %}
|
||||||
|
<a href="{{ record.get_absolute_url }}">{{ record.prefix }}</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.site %}&site={{ object.site.pk }}{% endif %}{% if object.tenant %}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}{% endif %}">{{ record.prefix }}</a>
|
||||||
|
{% endif %}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PREFIX_LINK_WITH_DEPTH = """
|
||||||
{% load helpers %}
|
{% load helpers %}
|
||||||
{% if record.depth %}
|
{% if record.depth %}
|
||||||
<div class="record-depth">
|
<div class="record-depth">
|
||||||
@ -29,8 +37,7 @@ PREFIX_LINK = """
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% if record.pk %}{% url 'ipam:prefix' pk=record.pk %}{% else %}{% url 'ipam:prefix_add' %}?prefix={{ record }}{% if object.vrf %}&vrf={{ object.vrf.pk }}{% endif %}{% if object.site %}&site={{ object.site.pk }}{% endif %}{% if object.tenant %}&tenant_group={{ object.tenant.group.pk }}&tenant={{ object.tenant.pk }}{% endif %}{% endif %}">{{ record.prefix }}</a>
|
""" + PREFIX_LINK
|
||||||
"""
|
|
||||||
|
|
||||||
IPADDRESS_LINK = """
|
IPADDRESS_LINK = """
|
||||||
{% if record.pk %}
|
{% if record.pk %}
|
||||||
@ -216,14 +223,15 @@ class PrefixUtilizationColumn(columns.UtilizationColumn):
|
|||||||
|
|
||||||
class PrefixTable(TenancyColumnsMixin, NetBoxTable):
|
class PrefixTable(TenancyColumnsMixin, NetBoxTable):
|
||||||
prefix = columns.TemplateColumn(
|
prefix = columns.TemplateColumn(
|
||||||
template_code=PREFIX_LINK,
|
template_code=PREFIX_LINK_WITH_DEPTH,
|
||||||
export_raw=True,
|
export_raw=True,
|
||||||
attrs={'td': {'class': 'text-nowrap'}}
|
attrs={'td': {'class': 'text-nowrap'}}
|
||||||
)
|
)
|
||||||
prefix_flat = tables.Column(
|
prefix_flat = columns.TemplateColumn(
|
||||||
accessor=Accessor('prefix'),
|
accessor=Accessor('prefix'),
|
||||||
linkify=True,
|
template_code=PREFIX_LINK,
|
||||||
verbose_name='Prefix (Flat)',
|
export_raw=True,
|
||||||
|
verbose_name='Prefix (Flat)'
|
||||||
)
|
)
|
||||||
depth = tables.Column(
|
depth = tables.Column(
|
||||||
accessor=Accessor('_depth'),
|
accessor=Accessor('_depth'),
|
||||||
|
@ -80,6 +80,13 @@ class BaseFilterSet(django_filters.FilterSet):
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# bit of a hack for #9231 - extras.lookup.Empty is registered in apps.ready
|
||||||
|
# however FilterSet Factory is setup before this which creates the
|
||||||
|
# initial filters. This recreates the filters so Empty is picked up correctly.
|
||||||
|
self.base_filters = self.__class__.get_filters()
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_filter_lookup_dict(existing_filter):
|
def _get_filter_lookup_dict(existing_filter):
|
||||||
# Choose the lookup expression map based on the filter type
|
# Choose the lookup expression map based on the filter type
|
||||||
|
@ -2,7 +2,6 @@ from django.core.validators import ValidationError
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from mptt.models import MPTTModel, TreeForeignKey
|
from mptt.models import MPTTModel, TreeForeignKey
|
||||||
|
|
||||||
from extras.utils import is_taggable
|
|
||||||
from utilities.mptt import TreeManager
|
from utilities.mptt import TreeManager
|
||||||
from utilities.querysets import RestrictedQuerySet
|
from utilities.querysets import RestrictedQuerySet
|
||||||
from netbox.models.features import *
|
from netbox.models.features import *
|
||||||
@ -32,7 +31,7 @@ class NetBoxFeatureSet(
|
|||||||
def get_prerequisite_models(cls):
|
def get_prerequisite_models(cls):
|
||||||
"""
|
"""
|
||||||
Return a list of model types that are required to create this model or empty list if none. This is used for
|
Return a list of model types that are required to create this model or empty list if none. This is used for
|
||||||
showing prequisite warnings in the UI on the list and detail views.
|
showing prerequisite warnings in the UI on the list and detail views.
|
||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@ -52,7 +51,7 @@ class ChangeLoggedModel(ChangeLoggingMixin, CustomValidationMixin, models.Model)
|
|||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
|
|
||||||
class NetBoxModel(NetBoxFeatureSet, models.Model):
|
class NetBoxModel(CloningMixin, NetBoxFeatureSet, models.Model):
|
||||||
"""
|
"""
|
||||||
Primary models represent real objects within the infrastructure being modeled.
|
Primary models represent real objects within the infrastructure being modeled.
|
||||||
"""
|
"""
|
||||||
@ -61,25 +60,6 @@ class NetBoxModel(NetBoxFeatureSet, models.Model):
|
|||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
def clone(self):
|
|
||||||
"""
|
|
||||||
Return a dictionary of attributes suitable for creating a copy of the current instance. This is used for pre-
|
|
||||||
populating an object creation form in the UI.
|
|
||||||
"""
|
|
||||||
attrs = {}
|
|
||||||
|
|
||||||
for field_name in getattr(self, 'clone_fields', []):
|
|
||||||
field = self._meta.get_field(field_name)
|
|
||||||
field_value = field.value_from_object(self)
|
|
||||||
if field_value not in (None, ''):
|
|
||||||
attrs[field_name] = field_value
|
|
||||||
|
|
||||||
# Include tags (if applicable)
|
|
||||||
if is_taggable(self):
|
|
||||||
attrs['tags'] = [tag.pk for tag in self.tags.all()]
|
|
||||||
|
|
||||||
return attrs
|
|
||||||
|
|
||||||
|
|
||||||
class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
|
class NestedGroupModel(NetBoxFeatureSet, MPTTModel):
|
||||||
"""
|
"""
|
||||||
|
@ -10,12 +10,13 @@ from django.db import models
|
|||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices
|
from extras.choices import CustomFieldVisibilityChoices, ObjectChangeActionChoices
|
||||||
from extras.utils import register_features
|
from extras.utils import is_taggable, register_features
|
||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from utilities.utils import serialize_object
|
from utilities.utils import serialize_object
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ChangeLoggingMixin',
|
'ChangeLoggingMixin',
|
||||||
|
'CloningMixin',
|
||||||
'CustomFieldsMixin',
|
'CustomFieldsMixin',
|
||||||
'CustomLinksMixin',
|
'CustomLinksMixin',
|
||||||
'CustomValidationMixin',
|
'CustomValidationMixin',
|
||||||
@ -82,6 +83,33 @@ class ChangeLoggingMixin(models.Model):
|
|||||||
return objectchange
|
return objectchange
|
||||||
|
|
||||||
|
|
||||||
|
class CloningMixin(models.Model):
|
||||||
|
"""
|
||||||
|
Provides the clone() method used to prepare a copy of existing objects.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
def clone(self):
|
||||||
|
"""
|
||||||
|
Return a dictionary of attributes suitable for creating a copy of the current instance. This is used for pre-
|
||||||
|
populating an object creation form in the UI.
|
||||||
|
"""
|
||||||
|
attrs = {}
|
||||||
|
|
||||||
|
for field_name in getattr(self, 'clone_fields', []):
|
||||||
|
field = self._meta.get_field(field_name)
|
||||||
|
field_value = field.value_from_object(self)
|
||||||
|
if field_value not in (None, ''):
|
||||||
|
attrs[field_name] = field_value
|
||||||
|
|
||||||
|
# Include tags (if applicable)
|
||||||
|
if is_taggable(self):
|
||||||
|
attrs['tags'] = [tag.pk for tag in self.tags.all()]
|
||||||
|
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class CustomFieldsMixin(models.Model):
|
class CustomFieldsMixin(models.Model):
|
||||||
"""
|
"""
|
||||||
Enables support for custom fields.
|
Enables support for custom fields.
|
||||||
|
@ -29,7 +29,7 @@ django.utils.encoding.force_text = force_str
|
|||||||
# Environment setup
|
# Environment setup
|
||||||
#
|
#
|
||||||
|
|
||||||
VERSION = '3.3.2'
|
VERSION = '3.3.3'
|
||||||
|
|
||||||
# Hostname
|
# Hostname
|
||||||
HOSTNAME = platform.node()
|
HOSTNAME = platform.node()
|
||||||
|
@ -7,6 +7,7 @@ from django.contrib.auth.models import AnonymousUser
|
|||||||
from django.db.models import DateField, DateTimeField
|
from django.db.models import DateField, DateTimeField
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.encoding import escape_uri_path
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.formats import date_format
|
from django.utils.formats import date_format
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
@ -210,7 +211,7 @@ class ActionsColumn(tables.Column):
|
|||||||
|
|
||||||
model = table.Meta.model
|
model = table.Meta.model
|
||||||
request = getattr(table, 'context', {}).get('request')
|
request = getattr(table, 'context', {}).get('request')
|
||||||
url_appendix = f'?return_url={request.path}' if request else ''
|
url_appendix = f'?return_url={escape_uri_path(request.get_full_path())}' if request else ''
|
||||||
html = ''
|
html = ''
|
||||||
|
|
||||||
# Compile actions menu
|
# Compile actions menu
|
||||||
|
@ -774,7 +774,6 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
model_form = None
|
model_form = None
|
||||||
filterset = None
|
filterset = None
|
||||||
table = None
|
table = None
|
||||||
patterned_fields = ('name', 'label')
|
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return f'dcim.add_{self.queryset.model._meta.model_name}'
|
return f'dcim.add_{self.queryset.model._meta.model_name}'
|
||||||
@ -804,23 +803,25 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
|
|
||||||
new_components = []
|
new_components = []
|
||||||
data = deepcopy(form.cleaned_data)
|
data = deepcopy(form.cleaned_data)
|
||||||
|
replication_data = {
|
||||||
|
field: data.pop(field) for field in form.replication_fields
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
|
||||||
for obj in data['pk']:
|
for obj in data['pk']:
|
||||||
|
|
||||||
pattern_count = len(data[f'{self.patterned_fields[0]}_pattern'])
|
pattern_count = len(replication_data[form.replication_fields[0]])
|
||||||
for i in range(pattern_count):
|
for i in range(pattern_count):
|
||||||
component_data = {
|
component_data = {
|
||||||
self.parent_field: obj.pk
|
self.parent_field: obj.pk
|
||||||
}
|
}
|
||||||
|
|
||||||
for field_name in self.patterned_fields:
|
|
||||||
if data.get(f'{field_name}_pattern'):
|
|
||||||
component_data[field_name] = data[f'{field_name}_pattern'][i]
|
|
||||||
|
|
||||||
component_data.update(data)
|
component_data.update(data)
|
||||||
|
for field, values in replication_data.items():
|
||||||
|
if values:
|
||||||
|
component_data[field] = values[i]
|
||||||
|
|
||||||
component_form = self.model_form(component_data)
|
component_form = self.model_form(component_data)
|
||||||
if component_form.is_valid():
|
if component_form.is_valid():
|
||||||
instance = component_form.save()
|
instance = component_form.save()
|
||||||
@ -829,7 +830,7 @@ class BulkComponentCreateView(GetReturnURLMixin, BaseMultiObjectView):
|
|||||||
else:
|
else:
|
||||||
for field, errors in component_form.errors.as_data().items():
|
for field, errors in component_form.errors.as_data().items():
|
||||||
for e in errors:
|
for e in errors:
|
||||||
form.add_error(field, '{} {}: {}'.format(obj, name, ', '.join(e)))
|
form.add_error(field, '{}: {}'.format(obj, ', '.join(e)))
|
||||||
|
|
||||||
# Enforce object-level permissions
|
# Enforce object-level permissions
|
||||||
if self.queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(new_components):
|
if self.queryset.filter(pk__in=[obj.pk for obj in new_components]).count() != len(new_components):
|
||||||
|
@ -538,10 +538,9 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
"""
|
"""
|
||||||
Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
|
Add one or more components (e.g. interfaces, console ports, etc.) to a Device or VirtualMachine.
|
||||||
"""
|
"""
|
||||||
template_name = 'dcim/component_create.html'
|
template_name = 'generic/object_edit.html'
|
||||||
form = None
|
form = None
|
||||||
model_form = None
|
model_form = None
|
||||||
patterned_fields = ('name', 'label')
|
|
||||||
|
|
||||||
def get_required_permission(self):
|
def get_required_permission(self):
|
||||||
return get_permission_for_model(self.queryset.model, 'add')
|
return get_permission_for_model(self.queryset.model, 'add')
|
||||||
@ -549,44 +548,38 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
def alter_object(self, instance, request):
|
def alter_object(self, instance, request):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
def initialize_forms(self, request):
|
def initialize_form(self, request):
|
||||||
data = request.POST if request.method == 'POST' else None
|
data = request.POST if request.method == 'POST' else None
|
||||||
initial_data = normalize_querydict(request.GET)
|
initial_data = normalize_querydict(request.GET)
|
||||||
|
|
||||||
form = self.form(data=data, initial=request.GET)
|
form = self.form(data=data, initial=initial_data)
|
||||||
model_form = self.model_form(data=data, initial=initial_data)
|
|
||||||
|
|
||||||
# These fields will be set from the pattern values
|
return form
|
||||||
for field_name in self.patterned_fields:
|
|
||||||
model_form.fields[field_name].widget = HiddenInput()
|
|
||||||
|
|
||||||
return form, model_form
|
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
form, model_form = self.initialize_forms(request)
|
form = self.initialize_form(request)
|
||||||
instance = self.alter_object(self.queryset.model(), request)
|
instance = self.alter_object(self.queryset.model(), request)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
'replication_form': form,
|
'form': form,
|
||||||
'form': model_form,
|
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
logger = logging.getLogger('netbox.views.ComponentCreateView')
|
logger = logging.getLogger('netbox.views.ComponentCreateView')
|
||||||
form, model_form = self.initialize_forms(request)
|
form = self.initialize_form(request)
|
||||||
instance = self.alter_object(self.queryset.model(), request)
|
instance = self.alter_object(self.queryset.model(), request)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
new_components = []
|
new_components = []
|
||||||
data = deepcopy(request.POST)
|
data = deepcopy(request.POST)
|
||||||
pattern_count = len(form.cleaned_data[f'{self.patterned_fields[0]}_pattern'])
|
pattern_count = len(form.cleaned_data[self.form.replication_fields[0]])
|
||||||
|
|
||||||
for i in range(pattern_count):
|
for i in range(pattern_count):
|
||||||
for field_name in self.patterned_fields:
|
for field_name in self.form.replication_fields:
|
||||||
if form.cleaned_data.get(f'{field_name}_pattern'):
|
if form.cleaned_data.get(field_name):
|
||||||
data[field_name] = form.cleaned_data[f'{field_name}_pattern'][i]
|
data[field_name] = form.cleaned_data[field_name][i]
|
||||||
|
|
||||||
if hasattr(form, 'get_iterative_data'):
|
if hasattr(form, 'get_iterative_data'):
|
||||||
data.update(form.get_iterative_data(i))
|
data.update(form.get_iterative_data(i))
|
||||||
@ -626,7 +619,6 @@ class ComponentCreateView(GetReturnURLMixin, BaseObjectView):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'object': instance,
|
'object': instance,
|
||||||
'replication_form': form,
|
'form': form,
|
||||||
'form': model_form,
|
|
||||||
'return_url': self.get_return_url(request),
|
'return_url': self.get_return_url(request),
|
||||||
})
|
})
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
{% if form.module_type %}
|
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="offset-sm-3">
|
|
||||||
<ul class="nav nav-pills" role="tablist">
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="devicetype_tab" data-bs-toggle="tab" aria-controls="devicetype" data-bs-target="#devicetype" class="nav-link {% if not form.initial.module_type %}active{% endif %}">
|
|
||||||
Device Type
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
<li role="presentation" class="nav-item">
|
|
||||||
<button role="tab" type="button" id="moduletype_tab" data-bs-toggle="tab" aria-controls="moduletype" data-bs-target="#moduletype" class="nav-link {% if form.initial.module_type %}active{% endif %}">
|
|
||||||
Module Type
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tab-content p-0 border-0">
|
|
||||||
<div class="tab-pane {% if not form.initial.module_type %}active{% endif %}" id="devicetype" role="tabpanel">
|
|
||||||
{% render_field replication_form.device_type %}
|
|
||||||
</div>
|
|
||||||
<div class="tab-pane {% if form.initial.module_type %}active{% endif %}" id="moduletype" role="tabpanel">
|
|
||||||
{% render_field replication_form.module_type %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
{% render_field replication_form.device_type %}
|
|
||||||
{% endif %}
|
|
||||||
{% block replication_fields %}
|
|
||||||
{% render_field replication_form.name_pattern %}
|
|
||||||
{% render_field replication_form.label_pattern %}
|
|
||||||
{% endblock replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock form %}
|
|
@ -70,7 +70,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% elif object.rack and object.position %}
|
{% elif object.rack and object.position %}
|
||||||
<span>U{{ object.position }} / {{ object.get_face_display }}</span>
|
<span>U{{ object.position|floatformat }} / {{ object.get_face_display }}</span>
|
||||||
{% elif object.rack and object.device_type.u_height %}
|
{% elif object.rack and object.device_type.u_height %}
|
||||||
<span class="badge bg-warning">Not racked</span>
|
<span class="badge bg-warning">Not racked</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
<div class="field-group mb-5">
|
|
||||||
{% if form.instance.device %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">Device</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ form.instance.device }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% render_form form %}
|
|
||||||
</div>
|
|
||||||
{% endblock form %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'dcim/component_template_create.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% render_field replication_form.rear_port_set %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -1,17 +0,0 @@
|
|||||||
{% extends 'dcim/component_create.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if object.component %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">
|
|
||||||
{{ object.component|meta:"verbose_name"|bettertitle }}
|
|
||||||
</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ object.component }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -1,17 +0,0 @@
|
|||||||
{% extends 'dcim/component_template_create.html' %}
|
|
||||||
{% load helpers %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% if object.component %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end">
|
|
||||||
{{ object.component|meta:"verbose_name"|bettertitle }}
|
|
||||||
</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ object.component }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -1,7 +0,0 @@
|
|||||||
{% extends 'dcim/component_template_create.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block replication_fields %}
|
|
||||||
{{ block.super }}
|
|
||||||
{% render_field replication_form.position_pattern %}
|
|
||||||
{% endblock replication_fields %}
|
|
@ -55,7 +55,7 @@
|
|||||||
<td>{{ device.pk }}</td>
|
<td>{{ device.pk }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if device.rack %}
|
{% if device.rack %}
|
||||||
{{ device.rack }} / {{ device.position }}
|
{{ device.rack }} / {{ device.position|floatformat }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ ''|placeholder }}
|
{{ ''|placeholder }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
{% for class_name, script in module_scripts.items %}
|
{% for class_name, script in module_scripts.items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'extras:script' module=script.module name=class_name %}" name="script.{{ class_name }}">{{ script.name }}</a>
|
<a href="{% url 'extras:script' module=script.root_module name=class_name %}" name="script.{{ class_name }}">{{ script.name }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% include 'extras/inc/job_label.html' with result=script.result %}
|
{% include 'extras/inc/job_label.html' with result=script.result %}
|
||||||
|
@ -59,9 +59,11 @@ Context:
|
|||||||
{# Render grouped fields according to Form #}
|
{# Render grouped fields according to Form #}
|
||||||
{% for group, fields in form.fieldsets %}
|
{% for group, fields in form.fieldsets %}
|
||||||
<div class="field-group mb-5">
|
<div class="field-group mb-5">
|
||||||
|
{% if group %}
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<h5 class="offset-sm-3">{{ group }}</h5>
|
<h5 class="offset-sm-3">{{ group }}</h5>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% for name in fields %}
|
{% for name in fields %}
|
||||||
{% with field=form|getfield:name %}
|
{% with field=form|getfield:name %}
|
||||||
{% if not field.field.widget.is_hidden %}
|
{% if not field.field.widget.is_hidden %}
|
||||||
|
@ -46,4 +46,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if form.custom_fields %}
|
||||||
|
<div class="field-group my-5">
|
||||||
|
<div class="row mb-2">
|
||||||
|
<h5 class="offset-sm-3">Custom Fields</h5>
|
||||||
|
</div>
|
||||||
|
{% render_custom_fields form %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -13,6 +13,16 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Login form errors #}
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<h4 class="alert-heading">Errors</h4>
|
||||||
|
<p>
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Login form #}
|
{# Login form #}
|
||||||
<div class="form-login">
|
<div class="form-login">
|
||||||
<form action="{% url 'login' %}" method="post">
|
<form action="{% url 'login' %}" method="post">
|
||||||
@ -48,16 +58,6 @@
|
|||||||
</h5>
|
</h5>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Login form errors #}
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
<h4 class="alert-heading">Errors</h4>
|
|
||||||
<p>
|
|
||||||
{{ form.non_field_errors }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{# Page footer #}
|
{# Page footer #}
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
|
||||||
{% load form_helpers %}
|
|
||||||
|
|
||||||
{% block form %}
|
|
||||||
{# Render hidden fields #}
|
|
||||||
{% for field in form.hidden_fields %}
|
|
||||||
{{ field }}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Interface</h5>
|
|
||||||
</div>
|
|
||||||
{% if form.instance.virtual_machine %}
|
|
||||||
<div class="row mb-3">
|
|
||||||
<label class="col-sm-3 col-form-label text-lg-end required" for="id_device">Virtual Machine</label>
|
|
||||||
<div class="col">
|
|
||||||
<input class="form-control" value="{{ form.instance.virtual_machine }}" disabled />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% render_field form.name %}
|
|
||||||
{% render_field form.description %}
|
|
||||||
{% render_field form.tags %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Addressing</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.vrf %}
|
|
||||||
{% render_field form.mac_address %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Operation</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.mtu %}
|
|
||||||
{% render_field form.enabled %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Related Interfaces</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.parent %}
|
|
||||||
{% render_field form.bridge %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">802.1Q Switching</h5>
|
|
||||||
</div>
|
|
||||||
{% render_field form.mode %}
|
|
||||||
{% render_field form.vlan_group %}
|
|
||||||
{% render_field form.untagged_vlan %}
|
|
||||||
{% render_field form.tagged_vlans %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.custom_fields %}
|
|
||||||
<div class="field-group my-5">
|
|
||||||
<div class="row mb-2">
|
|
||||||
<h5 class="offset-sm-3">Custom Fields</h5>
|
|
||||||
</div>
|
|
||||||
{% render_custom_fields form %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
@ -47,20 +47,14 @@ class LoginView(View):
|
|||||||
'url': f'{url}?{urlencode(params)}',
|
'url': f'{url}?{urlencode(params)}',
|
||||||
}
|
}
|
||||||
|
|
||||||
def get(self, request):
|
def get_auth_backends(self, request):
|
||||||
form = LoginForm(request)
|
|
||||||
|
|
||||||
if request.user.is_authenticated:
|
|
||||||
logger = logging.getLogger('netbox.auth.login')
|
|
||||||
return self.redirect_to_next(request, logger)
|
|
||||||
|
|
||||||
auth_backends = []
|
auth_backends = []
|
||||||
saml_idps = get_saml_idps()
|
saml_idps = get_saml_idps()
|
||||||
|
|
||||||
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
|
for name in load_backends(settings.AUTHENTICATION_BACKENDS).keys():
|
||||||
url = reverse('social:begin', args=[name, ])
|
url = reverse('social:begin', args=[name])
|
||||||
params = {}
|
params = {}
|
||||||
next = request.GET.get('next')
|
if next := request.GET.get('next'):
|
||||||
if next:
|
|
||||||
params['next'] = next
|
params['next'] = next
|
||||||
if name.lower() == 'saml' and saml_idps:
|
if name.lower() == 'saml' and saml_idps:
|
||||||
for idp in saml_idps:
|
for idp in saml_idps:
|
||||||
@ -71,9 +65,18 @@ class LoginView(View):
|
|||||||
else:
|
else:
|
||||||
auth_backends.append(self.gen_auth_data(name, url, params))
|
auth_backends.append(self.gen_auth_data(name, url, params))
|
||||||
|
|
||||||
|
return auth_backends
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
form = LoginForm(request)
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
logger = logging.getLogger('netbox.auth.login')
|
||||||
|
return self.redirect_to_next(request, logger)
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
'auth_backends': auth_backends,
|
'auth_backends': self.get_auth_backends(request),
|
||||||
})
|
})
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
@ -107,7 +110,7 @@ class LoginView(View):
|
|||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
'form': form,
|
'form': form,
|
||||||
'auth_backends': load_backends(settings.AUTHENTICATION_BACKENDS),
|
'auth_backends': self.get_auth_backends(request),
|
||||||
})
|
})
|
||||||
|
|
||||||
def redirect_to_next(self, request, logger):
|
def redirect_to_next(self, request, logger):
|
||||||
|
@ -22,7 +22,7 @@ class ExpandableNameField(forms.CharField):
|
|||||||
if not self.help_text:
|
if not self.help_text:
|
||||||
self.help_text = """
|
self.help_text = """
|
||||||
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
Alphanumeric ranges are supported for bulk creation. Mixed cases and types within a single range
|
||||||
are not supported. Example: <code>[ge,xe]-0/0/[0-9]</code>
|
are not supported (example: <code>[ge,xe]-0/0/[0-9]</code>).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
@ -466,6 +466,7 @@ class ViewTestCases:
|
|||||||
"""
|
"""
|
||||||
bulk_create_count = 3
|
bulk_create_count = 3
|
||||||
bulk_create_data = {}
|
bulk_create_data = {}
|
||||||
|
validation_excluded_fields = []
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
def test_create_multiple_objects_without_permission(self):
|
def test_create_multiple_objects_without_permission(self):
|
||||||
@ -500,7 +501,7 @@ class ViewTestCases:
|
|||||||
self.assertHttpStatus(response, 302)
|
self.assertHttpStatus(response, 302)
|
||||||
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
||||||
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
||||||
self.assertInstanceEqual(instance, self.bulk_create_data)
|
self.assertInstanceEqual(instance, self.bulk_create_data, exclude=self.validation_excluded_fields)
|
||||||
|
|
||||||
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
@override_settings(EXEMPT_VIEW_PERMISSIONS=[])
|
||||||
def test_create_multiple_objects_with_constrained_permission(self):
|
def test_create_multiple_objects_with_constrained_permission(self):
|
||||||
@ -532,7 +533,7 @@ class ViewTestCases:
|
|||||||
self.assertHttpStatus(response, 302)
|
self.assertHttpStatus(response, 302)
|
||||||
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
|
||||||
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
|
||||||
self.assertInstanceEqual(instance, self.bulk_create_data)
|
self.assertInstanceEqual(instance, self.bulk_create_data, exclude=self.validation_excluded_fields)
|
||||||
|
|
||||||
class BulkImportObjectsViewTestCase(ModelViewTestCase):
|
class BulkImportObjectsViewTestCase(ModelViewTestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -13,7 +13,7 @@ class VirtualMachineBulkAddComponentForm(BootstrapMixin, forms.Form):
|
|||||||
queryset=VirtualMachine.objects.all(),
|
queryset=VirtualMachine.objects.all(),
|
||||||
widget=forms.MultipleHiddenInput()
|
widget=forms.MultipleHiddenInput()
|
||||||
)
|
)
|
||||||
name_pattern = ExpandableNameField(
|
name = ExpandableNameField(
|
||||||
label='Name'
|
label='Name'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,4 +27,4 @@ class VMInterfaceBulkCreateForm(
|
|||||||
form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
|
form_from_model(VMInterface, ['enabled', 'mtu', 'description', 'tags']),
|
||||||
VirtualMachineBulkAddComponentForm
|
VirtualMachineBulkAddComponentForm
|
||||||
):
|
):
|
||||||
pass
|
replication_fields = ('name',)
|
||||||
|
@ -5,7 +5,6 @@ from django.core.exceptions import ValidationError
|
|||||||
from dcim.forms.common import InterfaceCommonForm
|
from dcim.forms.common import InterfaceCommonForm
|
||||||
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
|
from dcim.forms.models import INTERFACE_MODE_HELP_TEXT
|
||||||
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
|
from dcim.models import Device, DeviceRole, Platform, Rack, Region, Site, SiteGroup
|
||||||
from extras.models import Tag
|
|
||||||
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
|
from ipam.models import IPAddress, VLAN, VLANGroup, VRF
|
||||||
from netbox.forms import NetBoxModelForm
|
from netbox.forms import NetBoxModelForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
@ -278,6 +277,9 @@ class VirtualMachineForm(TenancyForm, NetBoxModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
||||||
|
virtual_machine = DynamicModelChoiceField(
|
||||||
|
queryset=VirtualMachine.objects.all()
|
||||||
|
)
|
||||||
parent = DynamicModelChoiceField(
|
parent = DynamicModelChoiceField(
|
||||||
queryset=VMInterface.objects.all(),
|
queryset=VMInterface.objects.all(),
|
||||||
required=False,
|
required=False,
|
||||||
@ -338,7 +340,6 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
'vlan_group', 'untagged_vlan', 'tagged_vlans', 'vrf', 'tags',
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
'virtual_machine': forms.HiddenInput(),
|
|
||||||
'mode': StaticSelect()
|
'mode': StaticSelect()
|
||||||
}
|
}
|
||||||
labels = {
|
labels = {
|
||||||
@ -347,3 +348,10 @@ class VMInterfaceForm(InterfaceCommonForm, NetBoxModelForm):
|
|||||||
help_texts = {
|
help_texts = {
|
||||||
'mode': INTERFACE_MODE_HELP_TEXT,
|
'mode': INTERFACE_MODE_HELP_TEXT,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Disable reassignment of VirtualMachine when editing an existing instance
|
||||||
|
if self.instance.pk:
|
||||||
|
self.fields['virtual_machine'].disabled = True
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
from django import forms
|
from utilities.forms import ExpandableNameField
|
||||||
|
from .models import VMInterfaceForm
|
||||||
from utilities.forms import BootstrapMixin, DynamicModelChoiceField, ExpandableNameField
|
|
||||||
from .models import VirtualMachine
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'VMInterfaceCreateForm',
|
'VMInterfaceCreateForm',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VMInterfaceCreateForm(BootstrapMixin, forms.Form):
|
class VMInterfaceCreateForm(VMInterfaceForm):
|
||||||
virtual_machine = DynamicModelChoiceField(
|
name = ExpandableNameField()
|
||||||
queryset=VirtualMachine.objects.all()
|
replication_fields = ('name',)
|
||||||
)
|
|
||||||
name_pattern = ExpandableNameField(
|
class Meta(VMInterfaceForm.Meta):
|
||||||
label='Name'
|
exclude = ('name',)
|
||||||
)
|
|
||||||
|
@ -251,6 +251,7 @@ class VirtualMachineTestCase(ViewTestCases.PrimaryObjectViewTestCase):
|
|||||||
|
|
||||||
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
||||||
model = VMInterface
|
model = VMInterface
|
||||||
|
validation_excluded_fields = ('name',)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
@ -290,10 +291,10 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
tags = create_tags('Alpha', 'Bravo', 'Charlie')
|
||||||
|
|
||||||
cls.form_data = {
|
cls.form_data = {
|
||||||
'virtual_machine': virtualmachines[1].pk,
|
'virtual_machine': virtualmachines[0].pk,
|
||||||
'name': 'Interface X',
|
'name': 'Interface X',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'bridge': interfaces[3].pk,
|
'bridge': interfaces[1].pk,
|
||||||
'mac_address': EUI('01-02-03-04-05-06'),
|
'mac_address': EUI('01-02-03-04-05-06'),
|
||||||
'mtu': 65000,
|
'mtu': 65000,
|
||||||
'description': 'New description',
|
'description': 'New description',
|
||||||
@ -306,7 +307,7 @@ class VMInterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
|
|||||||
|
|
||||||
cls.bulk_create_data = {
|
cls.bulk_create_data = {
|
||||||
'virtual_machine': virtualmachines[1].pk,
|
'virtual_machine': virtualmachines[1].pk,
|
||||||
'name_pattern': 'Interface [4-6]',
|
'name': 'Interface [4-6]',
|
||||||
'enabled': False,
|
'enabled': False,
|
||||||
'bridge': interfaces[3].pk,
|
'bridge': interfaces[3].pk,
|
||||||
'mac_address': EUI('01-02-03-04-05-06'),
|
'mac_address': EUI('01-02-03-04-05-06'),
|
||||||
|
@ -451,13 +451,11 @@ class VMInterfaceCreateView(generic.ComponentCreateView):
|
|||||||
queryset = VMInterface.objects.all()
|
queryset = VMInterface.objects.all()
|
||||||
form = forms.VMInterfaceCreateForm
|
form = forms.VMInterfaceCreateForm
|
||||||
model_form = forms.VMInterfaceForm
|
model_form = forms.VMInterfaceForm
|
||||||
patterned_fields = ('name',)
|
|
||||||
|
|
||||||
|
|
||||||
class VMInterfaceEditView(generic.ObjectEditView):
|
class VMInterfaceEditView(generic.ObjectEditView):
|
||||||
queryset = VMInterface.objects.all()
|
queryset = VMInterface.objects.all()
|
||||||
form = forms.VMInterfaceForm
|
form = forms.VMInterfaceForm
|
||||||
template_name = 'virtualization/vminterface_edit.html'
|
|
||||||
|
|
||||||
|
|
||||||
class VMInterfaceDeleteView(generic.ObjectDeleteView):
|
class VMInterfaceDeleteView(generic.ObjectDeleteView):
|
||||||
|
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# See PEP 518 for the spec of this file
|
||||||
|
# https://www.python.org/dev/peps/pep-0518/
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 120
|
||||||
|
target_version = ['py38', 'py39', 'py310']
|
||||||
|
skip-string-normalization = true
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
profile = "black"
|
||||||
|
|
||||||
|
[tool.pylint]
|
||||||
|
max-line-length = 120
|
@ -19,13 +19,13 @@ graphene-django==2.15.0
|
|||||||
gunicorn==20.1.0
|
gunicorn==20.1.0
|
||||||
Jinja2==3.1.2
|
Jinja2==3.1.2
|
||||||
Markdown==3.4.1
|
Markdown==3.4.1
|
||||||
mkdocs-material==8.4.2
|
mkdocs-material==8.5.1
|
||||||
mkdocstrings[python-legacy]==0.19.0
|
mkdocstrings[python-legacy]==0.19.0
|
||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
Pillow==9.2.0
|
Pillow==9.2.0
|
||||||
psycopg2-binary==2.9.3
|
psycopg2-binary==2.9.3
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
sentry-sdk==1.9.7
|
sentry-sdk==1.9.8
|
||||||
social-auth-app-django==5.0.0
|
social-auth-app-django==5.0.0
|
||||||
social-auth-core==4.3.0
|
social-auth-core==4.3.0
|
||||||
svgwrite==1.4.3
|
svgwrite==1.4.3
|
||||||
|
@ -40,11 +40,14 @@ if [ $? != 0 ]; then
|
|||||||
EXIT=1
|
EXIT=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
git diff --cached --name-only | if grep --quiet 'netbox/project-static/'
|
||||||
|
then
|
||||||
echo "Checking UI ESLint, TypeScript, and Prettier compliance..."
|
echo "Checking UI ESLint, TypeScript, and Prettier compliance..."
|
||||||
yarn --cwd "$PWD/netbox/project-static" validate
|
yarn --cwd "$PWD/netbox/project-static" validate
|
||||||
if [ $? != 0 ]; then
|
if [ $? != 0 ]; then
|
||||||
EXIT=1
|
EXIT=1
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $EXIT != 0 ]; then
|
if [ $EXIT != 0 ]; then
|
||||||
printf "${RED}COMMIT FAILED${NOCOLOR}\n"
|
printf "${RED}COMMIT FAILED${NOCOLOR}\n"
|
||||||
|
Loading…
Reference in New Issue
Block a user