Merge branch 'feature' into 7848-rq-api

This commit is contained in:
Arthur Hanson 2024-11-22 08:03:15 -08:00
commit 8f08852737
337 changed files with 15632 additions and 7428 deletions

View File

@ -14,7 +14,7 @@ body:
attributes:
label: NetBox version
description: What version of NetBox are you currently running?
placeholder: v4.1.5
placeholder: v4.1.7
validations:
required: true
- type: dropdown

View File

@ -39,7 +39,7 @@ body:
attributes:
label: NetBox Version
description: What version of NetBox are you currently running?
placeholder: v4.1.5
placeholder: v4.1.7
validations:
required: true
- type: dropdown

View File

@ -7,6 +7,9 @@ contact_links:
- name: ❓ Discussion
url: https://github.com/netbox-community/netbox/discussions
about: "If you're just looking for help, try starting a discussion instead."
- name: 👔 Professional Support
url: https://netboxlabs.com/netbox-enterprise/
about: "Professional support is available for NetBox Enterprise or Cloud."
- name: 🌎 Correct a Translation
url: https://explore.transifex.com/netbox-community/netbox/
about: "Spot an incorrect translation? You can propose a fix on Transifex."

View File

@ -15,6 +15,11 @@ on:
permissions:
contents: read
# Add concurrency group to control job running
concurrency:
group: ${{ github.event_name }}-${{ github.ref }}-${{ github.actor }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest

12
.tx/config Executable file
View File

@ -0,0 +1,12 @@
[main]
host = https://app.transifex.com
[o:netbox-community:p:netbox:r:9cbf4fcf95b3d92e4ebbf1a5e5d1caee]
file_filter = netbox/translations/<lang>/LC_MESSAGES/django.po
source_file = netbox/translations/en/LC_MESSAGES/django.po
type = PO
minimum_perc = 0
resource_name = django.po
replace_edited_strings = false
keep_translations = false

View File

@ -42,7 +42,7 @@ django-rich
# Django integration for RQ (Reqis queuing)
# https://github.com/rq/django-rq/blob/master/CHANGELOG.md
django-rq<3.0
django-rq
# Abstraction models for rendering and paginating HTML tables
# https://github.com/jieter/django-tables2/blob/master/CHANGELOG.md
@ -118,7 +118,7 @@ requests
# rq
# https://github.com/rq/rq/blob/master/CHANGES.md
rq<2.0
rq
# Social authentication framework
# https://github.com/python-social-auth/social-core/blob/master/CHANGELOG.md

View File

@ -329,6 +329,7 @@
"100base-tx",
"100base-t1",
"1000base-t",
"1000base-lx",
"1000base-tx",
"2.5gbase-t",
"5gbase-t",

View File

@ -90,7 +90,20 @@ This will automatically update the schema file at `contrib/generated_schema.json
### Update & Compile Translations
Updated language translations should be pulled from [Transifex](https://app.transifex.com/netbox-community/netbox/dashboard/) and re-compiled for each new release. Follow the documented process for [updating translated strings](./translations.md#updating-translated-strings) to do this.
Updated language translations should be pulled from [Transifex](https://app.transifex.com/netbox-community/netbox/dashboard/) and re-compiled for each new release. First, retrieve any updated translation files using the Transifex CLI client:
```no-highlight
tx pull
```
Then, compile these portable (`.po`) files for use in the application:
```no-highlight
./manage.py compilemessages
```
!!! tip
Consult the translation documentation for more detail on [updating translated strings](./translations.md#updating-translated-strings) if you've not set up the Transifex client already.
### Update Version and Changelog

View File

@ -16,26 +16,31 @@ To update the English `.po` file from which all translations are derived, use th
Then, commit the change and push to the `develop` branch on GitHub. Any new strings will appear for translation on Transifex automatically.
!!! note
It is typically not necessary to update source strings manually, as this is done nightly by a [GitHub action](https://github.com/netbox-community/netbox/blob/develop/.github/workflows/update-translation-strings.yml).
## Updating Translated Strings
Typically, translated strings need to be updated only as part of the NetBox [release process](./release-checklist.md).
Check the Transifex dashboard for languages that are not marked _ready for use_, being sure to click _Show all languages_ if it appears at the bottom of the list. Use machine translation to round out any not-ready languages. It's not necessary to review the machine translation immediately as the translation teams will handle that aspect; the goal at this stage is to get translations included in the Transifex pull request.
To update translated strings, start by initiating a sync from Transifex. From the Transifex dashboard, navigate to Settings > Integrations > GitHub > Manage, and click the **Manual Sync** button at top right.
To download translated strings automatically, you'll need to:
![Transifex manual sync](../media/development/transifex_sync.png)
1. Install the [Transifex CLI client](https://github.com/transifex/cli)
2. Generate a [Transifex API token](https://app.transifex.com/user/settings/api/)
Enter a threshold percentage of 1 (to ensure all translations are captured) and select the `develop` branch, then click **Sync**. This will initiate a pull request to GitHub to update any newly modified translation (`.po`) files.
Once you have the client set up, run the following command:
!!! tip
The new PR should appear within a few minutes. If it does not, check that there are in fact new translations to be added.
```no-highlight
TX_TOKEN=$TOKEN tx pull
```
![Transifex pull request](../media/development/transifex_pull_request.png)
This will download all portable (`.po`) translation files from Transifex, updating them locally as needed.
Once the PR has been merged, the updated strings need to be compiled into new `.mo` files so they can be used by the application. Update the `develop` branch locally to pull in the changes from the Transifex PR, then run Django's [`compilemessages`](https://docs.djangoproject.com/en/stable/ref/django-admin/#django-admin-compilemessages) management command:
Once retrieved, the updated strings need to be compiled into new `.mo` files so they can be used by the application. Run Django's [`compilemessages`](https://docs.djangoproject.com/en/stable/ref/django-admin/#django-admin-compilemessages) management command to compile them:
```nohighlight
```no-highlight
./manage.py compilemessages
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@ -0,0 +1,33 @@
# Virtual Circuits
A virtual circuit can connect two or more interfaces atop a set of decoupled physical connections. For example, it's very common to form a virtual connection between two virtual interfaces, each of which is bound to a physical interface on its respective device and physically connected to a [provider network](./providernetwork.md) via an independent [physical circuit](./circuit.md).
## Fields
### Provider Network
The [provider network](./providernetwork.md) across which the virtual circuit is formed.
### Provider Account
The [provider account](./provideraccount.md) with which the virtual circuit is associated (if any).
### Circuit ID
The unique identifier assigned to the virtual circuit by its [provider](./provider.md).
### Status
The operational status of the virtual circuit. By default, the following statuses are available:
| Name |
|----------------|
| Planned |
| Provisioning |
| Active |
| Offline |
| Deprovisioning |
| Decommissioned |
!!! tip "Custom circuit statuses"
Additional circuit statuses may be defined by setting `Circuit.status` under the [`FIELD_CHOICES`](../../configuration/data-validation.md#field_choices) configuration parameter.

View File

@ -0,0 +1,21 @@
# Virtual Circuit Terminations
This model represents the connection of a virtual [interface](../dcim/interface.md) to a [virtual circuit](./virtualcircuit.md).
## Fields
### Virtual Circuit
The [virtual circuit](./virtualcircuit.md) to which the interface is connected.
### Interface
The [interface](../dcim/interface.md) connected to the virtual circuit.
### Role
The functional role of the termination. This depends on the virtual circuit's topology, which is typically either peer-to-peer or hub-and-spoke (multipoint). Valid choices include:
* Peer
* Hub
* Spoke

View File

@ -45,9 +45,12 @@ The operation duplex (full, half, or auto).
The [virtual routing and forwarding](../ipam/vrf.md) instance to which this interface is assigned.
### MAC Address
### Primary MAC Address
The 48-bit MAC address (for Ethernet interfaces).
The [MAC address](./macaddress.md) assigned to this interface which is designated as its primary.
!!! note "Changed in NetBox v4.2"
The MAC address of an interface (formerly a concrete database field) is available as a property, `mac_address`, which reflects the value of the primary linked [MAC address](./macaddress.md) object.
### WWN

View File

@ -0,0 +1,11 @@
# MAC Addresses
A MAC address object in NetBox comprises a single Ethernet link layer address, and represents a MAC address as reported by or assigned to a network interface. MAC addresses can be assigned to [device](../dcim/device.md) and [virtual machine](../virtualization/virtualmachine.md) interfaces. A MAC address can be specified as the primary MAC address for a given device or VM interface.
Most interfaces have only a single MAC address, hard-coded at the factory. However, on some devices (particularly virtual interfaces) it is possible to assign additional MAC addresses or change existing ones. For this reason NetBox allows multiple MACAddress objects to be assigned to a single interface.
## Fields
### MAC Address
The 48-bit MAC address, in colon-hexadecimal notation (e.g. `aa:bb:cc:11:22:33`).

View File

@ -27,9 +27,12 @@ An interface on the same VM with which this interface is bridged.
If not selected, this interface will be treated as disabled/inoperative.
### MAC Address
### Primary MAC Address
The 48-bit MAC address (for Ethernet interfaces).
The [MAC address](./macaddress.md) assigned to this interface which is designated as its primary.
!!! note "Changed in NetBox v4.2"
The MAC address of an interface (formerly a concrete database field) is available as a property, `mac_address`, which reflects the value of the primary linked [MAC address](./macaddress.md) object.
### MTU

View File

@ -1,6 +1,6 @@
# IKE Policies
An [Internet Key Exhcnage (IKE)](https://en.wikipedia.org/wiki/Internet_Key_Exchange) policy defines an IKE version, mode, and set of [proposals](./ikeproposal.md) to be used in IKE negotiation. These policies are referenced by [IPSec profiles](./ipsecprofile.md).
An [Internet Key Exchange (IKE)](https://en.wikipedia.org/wiki/Internet_Key_Exchange) policy defines an IKE version, mode, and set of [proposals](./ikeproposal.md) to be used in IKE negotiation. These policies are referenced by [IPSec profiles](./ipsecprofile.md).
## Fields

View File

@ -185,6 +185,9 @@ class MyView(generic.ObjectView):
)
```
!!! note "Changed in NetBox v4.2"
The `register_model_view()` function was extended in NetBox v4.2 to support registration of list views by passing `detail=False`.
::: utilities.views.register_model_view
::: utilities.views.ViewTab

View File

@ -1,5 +1,37 @@
# NetBox v4.1
## v4.1.7 (FUTURE)
### Enhancements
* [#15239](https://github.com/netbox-community/netbox/issues/15239) - Enable adding/removing individual VLANs while bulk editing device interfaces
* [#17871](https://github.com/netbox-community/netbox/issues/17871) - Enable the assignment/removal of virtualization cluster via device bulk edit
* [#17934](https://github.com/netbox-community/netbox/issues/17934) - Add 1000Base-LX interface type
* [#18007](https://github.com/netbox-community/netbox/issues/18007) - Hide sensitive parameters under data source view (even for privileged users)
### Bug Fixes
* [#17459](https://github.com/netbox-community/netbox/issues/17459) - Correct help text on `name` field of module type component templates
* [#17901](https://github.com/netbox-community/netbox/issues/17901) - Ensure GraphiQL UI resources are served locally
* [#17921](https://github.com/netbox-community/netbox/issues/17921) - Fix scheduling of recurring custom scripts
* [#17923](https://github.com/netbox-community/netbox/issues/17923) - Fix the execution of custom scripts via REST API & management command
* [#17963](https://github.com/netbox-community/netbox/issues/17963) - Fix selection of all listed objects during bulk edit
* [#17969](https://github.com/netbox-community/netbox/issues/17969) - Fix system info export when a config revision exists
* [#17972](https://github.com/netbox-community/netbox/issues/17972) - Force evaluation of `LOGIN_REQUIRED` when requesting static media
* [#17986](https://github.com/netbox-community/netbox/issues/17986) - Correct labels for virtual machine & virtual disk size properties
* [#18037](https://github.com/netbox-community/netbox/issues/18037) - Fix validation of maximum VLAN ID value when defining VLAN groups
* [#18038](https://github.com/netbox-community/netbox/issues/18038) - The `to_grams()` utility function should always return an integer value
---
## v4.1.6 (2024-10-31)
### Bug Fixes
* [#17700](https://github.com/netbox-community/netbox/issues/17700) - Fix warning when no scripts are found within a script module
* [#17884](https://github.com/netbox-community/netbox/issues/17884) - Fix translation support for certain tab headings
* [#17885](https://github.com/netbox-community/netbox/issues/17885) - Fix regression preventing custom scripts from executing
## v4.1.5 (2024-10-28)
### Enhancements

View File

@ -174,6 +174,8 @@ nav:
- Provider: 'models/circuits/provider.md'
- Provider Account: 'models/circuits/provideraccount.md'
- Provider Network: 'models/circuits/providernetwork.md'
- Virtual Circuit: 'models/circuits/virtualcircuit.md'
- Virtual Circuit Termination: 'models/circuits/virtualcircuittermination.md'
- Core:
- DataFile: 'models/core/datafile.md'
- DataSource: 'models/core/datasource.md'

View File

@ -2,9 +2,13 @@ from django.contrib.contenttypes.models import ContentType
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices
from circuits.choices import CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
from circuits.models import Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType
from circuits.models import (
Circuit, CircuitGroup, CircuitGroupAssignment, CircuitTermination, CircuitType, VirtualCircuit,
VirtualCircuitTermination,
)
from dcim.api.serializers_.device_components import InterfaceSerializer
from dcim.api.serializers_.cables import CabledObjectSerializer
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer, WritableNestedSerializer
@ -20,6 +24,8 @@ __all__ = (
'CircuitGroupSerializer',
'CircuitTerminationSerializer',
'CircuitTypeSerializer',
'VirtualCircuitSerializer',
'VirtualCircuitTerminationSerializer',
)
@ -53,8 +59,8 @@ class CircuitCircuitTerminationSerializer(WritableNestedSerializer):
class Meta:
model = CircuitTermination
fields = [
'id', 'url', 'display_url', 'display', 'termination_type', 'termination_id', 'termination', 'provider_network', 'port_speed', 'upstream_speed',
'xconnect_id', 'description',
'id', 'url', 'display_url', 'display', 'termination_type', 'termination_id', 'termination',
'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id', 'description',
]
@extend_schema_field(serializers.JSONField(allow_null=True))
@ -132,9 +138,10 @@ class CircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer
class Meta:
model = CircuitTermination
fields = [
'id', 'url', 'display_url', 'display', 'circuit', 'term_side', 'termination_type', 'termination_id', 'termination', 'provider_network', 'port_speed',
'upstream_speed', 'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'cable_end',
'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created', 'last_updated', '_occupied',
'id', 'url', 'display_url', 'display', 'circuit', 'term_side', 'termination_type', 'termination_id',
'termination', 'provider_network', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info', 'description',
'mark_connected', 'cable', 'cable_end', 'link_peers', 'link_peers_type', 'tags', 'custom_fields', 'created',
'last_updated', '_occupied',
]
brief_fields = ('id', 'url', 'display', 'circuit', 'term_side', 'description', 'cable', '_occupied')
@ -156,3 +163,32 @@ class CircuitGroupAssignmentSerializer(CircuitGroupAssignmentSerializer_):
'id', 'url', 'display_url', 'display', 'group', 'circuit', 'priority', 'tags', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'group', 'circuit', 'priority')
class VirtualCircuitSerializer(NetBoxModelSerializer):
provider_network = ProviderNetworkSerializer(nested=True)
provider_account = ProviderAccountSerializer(nested=True, required=False, allow_null=True, default=None)
status = ChoiceField(choices=CircuitStatusChoices, required=False)
tenant = TenantSerializer(nested=True, required=False, allow_null=True)
class Meta:
model = VirtualCircuit
fields = [
'id', 'url', 'display_url', 'display', 'cid', 'provider_network', 'provider_account', 'status', 'tenant',
'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'provider_network', 'cid', 'description')
class VirtualCircuitTerminationSerializer(NetBoxModelSerializer, CabledObjectSerializer):
virtual_circuit = VirtualCircuitSerializer(nested=True)
role = ChoiceField(choices=VirtualCircuitTerminationRoleChoices, required=False)
interface = InterfaceSerializer(nested=True)
class Meta:
model = VirtualCircuitTermination
fields = [
'id', 'url', 'display_url', 'display', 'virtual_circuit', 'role', 'interface', 'description', 'tags',
'custom_fields', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'virtual_circuit', 'role', 'interface', 'description')

View File

@ -17,5 +17,9 @@ router.register('circuit-terminations', views.CircuitTerminationViewSet)
router.register('circuit-groups', views.CircuitGroupViewSet)
router.register('circuit-group-assignments', views.CircuitGroupAssignmentViewSet)
# Virtual circuits
router.register('virtual-circuits', views.VirtualCircuitViewSet)
router.register('virtual-circuit-terminations', views.VirtualCircuitTerminationViewSet)
app_name = 'circuits-api'
urlpatterns = router.urls

View File

@ -93,3 +93,23 @@ class ProviderNetworkViewSet(NetBoxModelViewSet):
queryset = ProviderNetwork.objects.all()
serializer_class = serializers.ProviderNetworkSerializer
filterset_class = filtersets.ProviderNetworkFilterSet
#
# Virtual circuits
#
class VirtualCircuitViewSet(NetBoxModelViewSet):
queryset = VirtualCircuit.objects.all()
serializer_class = serializers.VirtualCircuitSerializer
filterset_class = filtersets.VirtualCircuitFilterSet
#
# Virtual circuit terminations
#
class VirtualCircuitTerminationViewSet(PassThroughPortMixin, NetBoxModelViewSet):
queryset = VirtualCircuitTermination.objects.all()
serializer_class = serializers.VirtualCircuitTerminationSerializer
filterset_class = filtersets.VirtualCircuitTerminationFilterSet

View File

@ -92,3 +92,19 @@ class CircuitPriorityChoices(ChoiceSet):
(PRIORITY_TERTIARY, _('Tertiary')),
(PRIORITY_INACTIVE, _('Inactive')),
]
#
# Virtual circuits
#
class VirtualCircuitTerminationRoleChoices(ChoiceSet):
ROLE_PEER = 'peer'
ROLE_HUB = 'hub'
ROLE_SPOKE = 'spoke'
CHOICES = [
(ROLE_PEER, _('Peer'), 'green'),
(ROLE_HUB, _('Hub'), 'blue'),
(ROLE_SPOKE, _('Spoke'), 'orange'),
]

View File

@ -3,7 +3,7 @@ from django.db.models import Q
from django.utils.translation import gettext as _
from dcim.filtersets import CabledObjectFilterSet
from dcim.models import Location, Region, Site, SiteGroup
from dcim.models import Interface, Location, Region, Site, SiteGroup
from ipam.models import ASN
from netbox.filtersets import NetBoxModelFilterSet, OrganizationalModelFilterSet
from tenancy.filtersets import ContactModelFilterSet, TenancyFilterSet
@ -20,6 +20,8 @@ __all__ = (
'ProviderNetworkFilterSet',
'ProviderAccountFilterSet',
'ProviderFilterSet',
'VirtualCircuitFilterSet',
'VirtualCircuitTerminationFilterSet',
)
@ -239,7 +241,9 @@ class CircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet, ContactModelFilte
class Meta:
model = Circuit
fields = ('id', 'cid', 'description', 'install_date', 'termination_date', 'commit_rate', 'distance', 'distance_unit')
fields = (
'id', 'cid', 'description', 'install_date', 'termination_date', 'commit_rate', 'distance', 'distance_unit',
)
def search(self, queryset, name, value):
if not value.strip():
@ -334,8 +338,8 @@ class CircuitTerminationFilterSet(NetBoxModelFilterSet, CabledObjectFilterSet):
class Meta:
model = CircuitTermination
fields = (
'id', 'termination_id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description', 'mark_connected',
'pp_info', 'cable_end',
'id', 'termination_id', 'term_side', 'port_speed', 'upstream_speed', 'xconnect_id', 'description',
'mark_connected', 'pp_info', 'cable_end',
)
def search(self, queryset, name, value):
@ -404,3 +408,108 @@ class CircuitGroupAssignmentFilterSet(NetBoxModelFilterSet):
Q(circuit__cid__icontains=value) |
Q(group__name__icontains=value)
)
class VirtualCircuitFilterSet(NetBoxModelFilterSet, TenancyFilterSet):
provider_id = django_filters.ModelMultipleChoiceFilter(
field_name='provider_network__provider',
queryset=Provider.objects.all(),
label=_('Provider (ID)'),
)
provider = django_filters.ModelMultipleChoiceFilter(
field_name='provider_network__provider__slug',
queryset=Provider.objects.all(),
to_field_name='slug',
label=_('Provider (slug)'),
)
provider_account_id = django_filters.ModelMultipleChoiceFilter(
field_name='provider_account',
queryset=ProviderAccount.objects.all(),
label=_('Provider account (ID)'),
)
provider_account = django_filters.ModelMultipleChoiceFilter(
field_name='provider_account__account',
queryset=Provider.objects.all(),
to_field_name='account',
label=_('Provider account (account)'),
)
provider_network_id = django_filters.ModelMultipleChoiceFilter(
queryset=ProviderNetwork.objects.all(),
label=_('Provider network (ID)'),
)
status = django_filters.MultipleChoiceFilter(
choices=CircuitStatusChoices,
null_value=None
)
class Meta:
model = VirtualCircuit
fields = ('id', 'cid', 'description')
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(cid__icontains=value) |
Q(description__icontains=value) |
Q(comments__icontains=value)
).distinct()
class VirtualCircuitTerminationFilterSet(NetBoxModelFilterSet):
q = django_filters.CharFilter(
method='search',
label=_('Search'),
)
virtual_circuit_id = django_filters.ModelMultipleChoiceFilter(
queryset=VirtualCircuit.objects.all(),
label=_('Virtual circuit'),
)
role = django_filters.MultipleChoiceFilter(
choices=VirtualCircuitTerminationRoleChoices,
null_value=None
)
provider_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_circuit__provider_network__provider',
queryset=Provider.objects.all(),
label=_('Provider (ID)'),
)
provider = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_circuit__provider_network__provider__slug',
queryset=Provider.objects.all(),
to_field_name='slug',
label=_('Provider (slug)'),
)
provider_account_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_circuit__provider_account',
queryset=ProviderAccount.objects.all(),
label=_('Provider account (ID)'),
)
provider_account = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_circuit__provider_account__account',
queryset=ProviderAccount.objects.all(),
to_field_name='account',
label=_('Provider account (account)'),
)
provider_network_id = django_filters.ModelMultipleChoiceFilter(
queryset=ProviderNetwork.objects.all(),
field_name='virtual_circuit__provider_network',
label=_('Provider network (ID)'),
)
interface_id = django_filters.ModelMultipleChoiceFilter(
queryset=Interface.objects.all(),
field_name='interface',
label=_('Interface (ID)'),
)
class Meta:
model = VirtualCircuitTermination
fields = ('id', 'interface_id', 'description')
def search(self, queryset, name, value):
if not value.strip():
return queryset
return queryset.filter(
Q(virtual_circuit__cid__icontains=value) |
Q(description__icontains=value)
).distinct()

View File

@ -3,7 +3,9 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices
from circuits.choices import (
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, VirtualCircuitTerminationRoleChoices,
)
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
from circuits.models import *
from dcim.models import Site
@ -28,6 +30,8 @@ __all__ = (
'ProviderBulkEditForm',
'ProviderAccountBulkEditForm',
'ProviderNetworkBulkEditForm',
'VirtualCircuitBulkEditForm',
'VirtualCircuitTerminationBulkEditForm',
)
@ -291,3 +295,62 @@ class CircuitGroupAssignmentBulkEditForm(NetBoxModelBulkEditForm):
FieldSet('circuit', 'priority'),
)
nullable_fields = ('priority',)
class VirtualCircuitBulkEditForm(NetBoxModelBulkEditForm):
provider_network = DynamicModelChoiceField(
label=_('Provider network'),
queryset=ProviderNetwork.objects.all(),
required=False
)
provider_account = DynamicModelChoiceField(
label=_('Provider account'),
queryset=ProviderAccount.objects.all(),
required=False
)
status = forms.ChoiceField(
label=_('Status'),
choices=add_blank_choice(CircuitStatusChoices),
required=False,
initial=''
)
tenant = DynamicModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False
)
description = forms.CharField(
label=_('Description'),
max_length=100,
required=False
)
comments = CommentField()
model = VirtualCircuit
fieldsets = (
FieldSet('provider_network', 'provider_account', 'status', 'description', name=_('Virtual circuit')),
FieldSet('tenant', name=_('Tenancy')),
)
nullable_fields = (
'provider_account', 'tenant', 'description', 'comments',
)
class VirtualCircuitTerminationBulkEditForm(NetBoxModelBulkEditForm):
role = forms.ChoiceField(
label=_('Role'),
choices=add_blank_choice(VirtualCircuitTerminationRoleChoices),
required=False,
initial=''
)
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
model = VirtualCircuitTermination
fieldsets = (
FieldSet('role', 'description'),
)
nullable_fields = ('description',)

View File

@ -5,6 +5,7 @@ from django.utils.translation import gettext_lazy as _
from circuits.choices import *
from circuits.constants import *
from circuits.models import *
from dcim.models import Interface
from netbox.choices import DistanceUnitChoices
from netbox.forms import NetBoxModelImportForm
from tenancy.models import Tenant
@ -20,6 +21,9 @@ __all__ = (
'ProviderImportForm',
'ProviderAccountImportForm',
'ProviderNetworkImportForm',
'VirtualCircuitImportForm',
'VirtualCircuitTerminationImportForm',
'VirtualCircuitTerminationImportRelatedForm',
)
@ -179,3 +183,73 @@ class CircuitGroupAssignmentImportForm(NetBoxModelImportForm):
class Meta:
model = CircuitGroupAssignment
fields = ('circuit', 'group', 'priority')
class VirtualCircuitImportForm(NetBoxModelImportForm):
provider_network = CSVModelChoiceField(
label=_('Provider network'),
queryset=ProviderNetwork.objects.all(),
to_field_name='name',
help_text=_('The network to which this virtual circuit belongs')
)
provider_account = CSVModelChoiceField(
label=_('Provider account'),
queryset=ProviderAccount.objects.all(),
to_field_name='account',
help_text=_('Assigned provider account (if any)'),
required=False
)
status = CSVChoiceField(
label=_('Status'),
choices=CircuitStatusChoices,
help_text=_('Operational status')
)
tenant = CSVModelChoiceField(
label=_('Tenant'),
queryset=Tenant.objects.all(),
required=False,
to_field_name='name',
help_text=_('Assigned tenant')
)
class Meta:
model = VirtualCircuit
fields = [
'cid', 'provider_network', 'provider_account', 'status', 'tenant', 'description', 'comments', 'tags',
]
class BaseVirtualCircuitTerminationImportForm(forms.ModelForm):
virtual_circuit = CSVModelChoiceField(
label=_('Virtual circuit'),
queryset=VirtualCircuit.objects.all(),
to_field_name='cid',
)
role = CSVChoiceField(
label=_('Role'),
choices=VirtualCircuitTerminationRoleChoices,
help_text=_('Operational role')
)
interface = CSVModelChoiceField(
label=_('Interface'),
queryset=Interface.objects.all(),
to_field_name='pk',
)
class VirtualCircuitTerminationImportRelatedForm(BaseVirtualCircuitTerminationImportForm):
class Meta:
model = VirtualCircuitTermination
fields = [
'virtual_circuit', 'role', 'interface', 'description',
]
class VirtualCircuitTerminationImportForm(NetBoxModelImportForm, BaseVirtualCircuitTerminationImportForm):
class Meta:
model = VirtualCircuitTermination
fields = [
'virtual_circuit', 'role', 'interface', 'description', 'tags',
]

View File

@ -1,7 +1,10 @@
from django import forms
from django.utils.translation import gettext as _
from circuits.choices import CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices
from circuits.choices import (
CircuitCommitRateChoices, CircuitPriorityChoices, CircuitStatusChoices, CircuitTerminationSideChoices,
VirtualCircuitTerminationRoleChoices,
)
from circuits.models import *
from dcim.models import Location, Region, Site, SiteGroup
from ipam.models import ASN
@ -22,6 +25,8 @@ __all__ = (
'ProviderFilterForm',
'ProviderAccountFilterForm',
'ProviderNetworkFilterForm',
'VirtualCircuitFilterForm',
'VirtualCircuitTerminationFilterForm',
)
@ -116,7 +121,10 @@ class CircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFi
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
FieldSet('type_id', 'status', 'install_date', 'termination_date', 'commit_rate', 'distance', 'distance_unit', name=_('Attributes')),
FieldSet(
'type_id', 'status', 'install_date', 'termination_date', 'commit_rate', 'distance', 'distance_unit',
name=_('Attributes')
),
FieldSet('region_id', 'site_group_id', 'site_id', name=_('Location')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
FieldSet('contact', 'contact_role', 'contact_group', name=_('Contacts')),
@ -292,3 +300,74 @@ class CircuitGroupAssignmentFilterForm(NetBoxModelFilterSetForm):
required=False
)
tag = TagFilterField(model)
class VirtualCircuitFilterForm(TenancyFilterForm, ContactModelFilterForm, NetBoxModelFilterSetForm):
model = VirtualCircuit
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
FieldSet('status', name=_('Attributes')),
FieldSet('tenant_group_id', 'tenant_id', name=_('Tenant')),
)
selector_fields = ('filter_id', 'q', 'provider_id', 'provider_network_id')
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
required=False,
label=_('Provider')
)
provider_account_id = DynamicModelMultipleChoiceField(
queryset=ProviderAccount.objects.all(),
required=False,
query_params={
'provider_id': '$provider_id'
},
label=_('Provider account')
)
provider_network_id = DynamicModelMultipleChoiceField(
queryset=ProviderNetwork.objects.all(),
required=False,
query_params={
'provider_id': '$provider_id'
},
label=_('Provider network')
)
status = forms.MultipleChoiceField(
label=_('Status'),
choices=CircuitStatusChoices,
required=False
)
tag = TagFilterField(model)
class VirtualCircuitTerminationFilterForm(NetBoxModelFilterSetForm):
model = VirtualCircuitTermination
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('virtual_circuit_id', 'role', name=_('Virtual circuit')),
FieldSet('provider_id', 'provider_account_id', 'provider_network_id', name=_('Provider')),
)
virtual_circuit_id = DynamicModelMultipleChoiceField(
queryset=VirtualCircuit.objects.all(),
required=False,
label=_('Virtual circuit')
)
role = forms.MultipleChoiceField(
label=_('Role'),
choices=VirtualCircuitTerminationRoleChoices,
required=False
)
provider_network_id = DynamicModelMultipleChoiceField(
queryset=ProviderNetwork.objects.all(),
required=False,
query_params={
'provider_id': '$provider_id'
},
label=_('Provider network')
)
provider_id = DynamicModelMultipleChoiceField(
queryset=Provider.objects.all(),
required=False,
label=_('Provider')
)
tag = TagFilterField(model)

View File

@ -1,16 +1,21 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from circuits.choices import CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices
from circuits.choices import (
CircuitCommitRateChoices, CircuitTerminationPortSpeedChoices, VirtualCircuitTerminationRoleChoices,
)
from circuits.constants import *
from circuits.models import *
from dcim.models import Site
from dcim.models import Interface, Site
from ipam.models import ASN
from netbox.forms import NetBoxModelForm
from tenancy.forms import TenancyForm
from utilities.forms import get_field_value
from utilities.forms.fields import CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField
from utilities.forms.fields import (
CommentField, ContentTypeChoiceField, DynamicModelChoiceField, DynamicModelMultipleChoiceField, SlugField,
)
from utilities.forms.rendering import FieldSet, InlineFields
from utilities.forms.widgets import DatePicker, HTMXSelect, NumberWithOptions
from utilities.templatetags.builtins.filters import bettertitle
@ -24,6 +29,8 @@ __all__ = (
'ProviderForm',
'ProviderAccountForm',
'ProviderNetworkForm',
'VirtualCircuitForm',
'VirtualCircuitTerminationForm',
)
@ -50,7 +57,9 @@ class ProviderForm(NetBoxModelForm):
class ProviderAccountForm(NetBoxModelForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all()
queryset=Provider.objects.all(),
selector=True,
quick_add=True
)
comments = CommentField()
@ -64,7 +73,9 @@ class ProviderAccountForm(NetBoxModelForm):
class ProviderNetworkForm(NetBoxModelForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all()
queryset=Provider.objects.all(),
selector=True,
quick_add=True
)
comments = CommentField()
@ -97,7 +108,8 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
provider = DynamicModelChoiceField(
label=_('Provider'),
queryset=Provider.objects.all(),
selector=True
selector=True,
quick_add=True
)
provider_account = DynamicModelChoiceField(
label=_('Provider account'),
@ -108,7 +120,8 @@ class CircuitForm(TenancyForm, NetBoxModelForm):
}
)
type = DynamicModelChoiceField(
queryset=CircuitType.objects.all()
queryset=CircuitType.objects.all(),
quick_add=True
)
comments = CommentField()
@ -249,3 +262,66 @@ class CircuitGroupAssignmentForm(NetBoxModelForm):
fields = [
'group', 'circuit', 'priority', 'tags',
]
class VirtualCircuitForm(TenancyForm, NetBoxModelForm):
provider_network = DynamicModelChoiceField(
label=_('Provider network'),
queryset=ProviderNetwork.objects.all(),
selector=True
)
provider_account = DynamicModelChoiceField(
label=_('Provider account'),
queryset=ProviderAccount.objects.all(),
required=False
)
comments = CommentField()
fieldsets = (
FieldSet(
'provider_network', 'provider_account', 'cid', 'status', 'description', 'tags', name=_('Virtual circuit'),
),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
)
class Meta:
model = VirtualCircuit
fields = [
'cid', 'provider_network', 'provider_account', 'status', 'description', 'tenant_group', 'tenant',
'comments', 'tags',
]
class VirtualCircuitTerminationForm(NetBoxModelForm):
virtual_circuit = DynamicModelChoiceField(
label=_('Virtual circuit'),
queryset=VirtualCircuit.objects.all(),
selector=True
)
role = forms.ChoiceField(
choices=VirtualCircuitTerminationRoleChoices,
widget=HTMXSelect(),
label=_('Role')
)
interface = DynamicModelChoiceField(
label=_('Interface'),
queryset=Interface.objects.all(),
selector=True,
query_params={
'kind': 'virtual',
'virtual_circuit_termination_id': 'null',
},
context={
'parent': 'device',
}
)
fieldsets = (
FieldSet('virtual_circuit', 'role', 'interface', 'description', 'tags'),
)
class Meta:
model = VirtualCircuitTermination
fields = [
'virtual_circuit', 'role', 'interface', 'description', 'tags',
]

View File

@ -4,14 +4,16 @@ from circuits import filtersets, models
from netbox.graphql.filter_mixins import autotype_decorator, BaseFilterMixin
__all__ = (
'CircuitTerminationFilter',
'CircuitFilter',
'CircuitGroupAssignmentFilter',
'CircuitGroupFilter',
'CircuitTerminationFilter',
'CircuitTypeFilter',
'ProviderFilter',
'ProviderAccountFilter',
'ProviderNetworkFilter',
'VirtualCircuitFilter',
'VirtualCircuitTerminationFilter',
)
@ -61,3 +63,15 @@ class ProviderAccountFilter(BaseFilterMixin):
@autotype_decorator(filtersets.ProviderNetworkFilterSet)
class ProviderNetworkFilter(BaseFilterMixin):
pass
@strawberry_django.filter(models.VirtualCircuit, lookups=True)
@autotype_decorator(filtersets.VirtualCircuitFilterSet)
class VirtualCircuitFilter(BaseFilterMixin):
pass
@strawberry_django.filter(models.VirtualCircuitTermination, lookups=True)
@autotype_decorator(filtersets.VirtualCircuitTerminationFilterSet)
class VirtualCircuitTerminationFilter(BaseFilterMixin):
pass

View File

@ -31,3 +31,9 @@ class CircuitsQuery:
provider_network: ProviderNetworkType = strawberry_django.field()
provider_network_list: List[ProviderNetworkType] = strawberry_django.field()
virtual_circuit: VirtualCircuitType = strawberry_django.field()
virtual_circuit_list: List[VirtualCircuitType] = strawberry_django.field()
virtual_circuit_termination: VirtualCircuitTerminationType = strawberry_django.field()
virtual_circuit_termination_list: List[VirtualCircuitTerminationType] = strawberry_django.field()

View File

@ -19,6 +19,8 @@ __all__ = (
'ProviderType',
'ProviderAccountType',
'ProviderNetworkType',
'VirtualCircuitTerminationType',
'VirtualCircuitType',
)
@ -120,3 +122,32 @@ class CircuitGroupType(OrganizationalObjectType):
class CircuitGroupAssignmentType(TagsMixin, BaseObjectType):
group: Annotated["CircuitGroupType", strawberry.lazy('circuits.graphql.types')]
circuit: Annotated["CircuitType", strawberry.lazy('circuits.graphql.types')]
@strawberry_django.type(
models.VirtualCircuitTermination,
fields='__all__',
filters=VirtualCircuitTerminationFilter
)
class VirtualCircuitTerminationType(CustomFieldsMixin, TagsMixin, ObjectType):
virtual_circuit: Annotated[
"VirtualCircuitType",
strawberry.lazy('circuits.graphql.types')
] = strawberry_django.field(select_related=["virtual_circuit"])
interface: Annotated[
"InterfaceType",
strawberry.lazy('dcim.graphql.types')
] = strawberry_django.field(select_related=["interface"])
@strawberry_django.type(
models.VirtualCircuit,
fields='__all__',
filters=VirtualCircuitFilter
)
class VirtualCircuitType(NetBoxObjectType):
provider_network: ProviderNetworkType = strawberry_django.field(select_related=["provider_network"])
provider_account: ProviderAccountType | None
tenant: TenantType | None
terminations: List[VirtualCircuitTerminationType]

View File

@ -5,11 +5,9 @@ import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
replaces = [
('circuits', '0001_initial'),
@ -98,7 +96,12 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=100)),
('description', models.CharField(blank=True, max_length=200)),
('comments', models.TextField(blank=True)),
('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='networks', to='circuits.provider')),
(
'provider',
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='networks', to='circuits.provider'
),
),
],
options={
'ordering': ('provider', 'name'),

View File

@ -4,7 +4,6 @@ import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('dcim', '0001_initial'),
('contenttypes', '0002_remove_content_type_name'),
@ -58,32 +57,56 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='circuittermination',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='circuittermination',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='circuittermination',
name='circuit',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='circuits.circuit'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='circuits.circuit'
),
),
migrations.AddField(
model_name='circuittermination',
name='provider_network',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='circuits.providernetwork'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='circuit_terminations',
to='circuits.providernetwork',
),
),
migrations.AddField(
model_name='circuittermination',
name='site',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuit_terminations', to='dcim.site'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='circuit_terminations',
to='dcim.site',
),
),
migrations.AddField(
model_name='circuit',
name='provider',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.provider'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.provider'
),
),
migrations.AddField(
model_name='circuit',
@ -93,26 +116,50 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='circuit',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='circuits',
to='tenancy.tenant',
),
),
migrations.AddField(
model_name='circuit',
name='termination_a',
field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'),
field=models.ForeignKey(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='circuits.circuittermination',
),
),
migrations.AddField(
model_name='circuit',
name='termination_z',
field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.circuittermination'),
field=models.ForeignKey(
blank=True,
editable=False,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='circuits.circuittermination',
),
),
migrations.AddField(
model_name='circuit',
name='type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.circuittype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.circuittype'
),
),
migrations.AddConstraint(
model_name='providernetwork',
constraint=models.UniqueConstraint(fields=('provider', 'name'), name='circuits_providernetwork_provider_name'),
constraint=models.UniqueConstraint(
fields=('provider', 'name'), name='circuits_providernetwork_provider_name'
),
),
migrations.AlterUniqueTogether(
name='providernetwork',

View File

@ -5,7 +5,6 @@ import utilities.json
class Migration(migrations.Migration):
replaces = [
('circuits', '0003_extend_tag_support'),
('circuits', '0004_rename_cable_peer'),
@ -14,7 +13,7 @@ class Migration(migrations.Migration):
('circuits', '0034_created_datetimefield'),
('circuits', '0035_provider_asns'),
('circuits', '0036_circuit_termination_date_tags_custom_fields'),
('circuits', '0037_new_cabling_models')
('circuits', '0037_new_cabling_models'),
]
dependencies = [

View File

@ -6,13 +6,12 @@ import utilities.json
class Migration(migrations.Migration):
replaces = [
('circuits', '0038_cabling_cleanup'),
('circuits', '0039_unique_constraints'),
('circuits', '0040_provider_remove_deprecated_fields'),
('circuits', '0041_standardize_description_comments'),
('circuits', '0042_provideraccount')
('circuits', '0042_provideraccount'),
]
dependencies = [
@ -51,11 +50,15 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='circuittermination',
constraint=models.UniqueConstraint(fields=('circuit', 'term_side'), name='circuits_circuittermination_unique_circuit_term_side'),
constraint=models.UniqueConstraint(
fields=('circuit', 'term_side'), name='circuits_circuittermination_unique_circuit_term_side'
),
),
migrations.AddConstraint(
model_name='providernetwork',
constraint=models.UniqueConstraint(fields=('provider', 'name'), name='circuits_providernetwork_unique_provider_name'),
constraint=models.UniqueConstraint(
fields=('provider', 'name'), name='circuits_providernetwork_unique_provider_name'
),
),
migrations.RemoveField(
model_name='provider',
@ -84,12 +87,20 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('description', models.CharField(blank=True, max_length=200)),
('comments', models.TextField(blank=True)),
('account', models.CharField(max_length=100)),
('name', models.CharField(blank=True, max_length=100)),
('provider', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='accounts', to='circuits.provider')),
(
'provider',
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='accounts', to='circuits.provider'
),
),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
@ -98,11 +109,17 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='provideraccount',
constraint=models.UniqueConstraint(condition=models.Q(('name', ''), _negated=True), fields=('provider', 'name'), name='circuits_provideraccount_unique_provider_name'),
constraint=models.UniqueConstraint(
condition=models.Q(('name', ''), _negated=True),
fields=('provider', 'name'),
name='circuits_provideraccount_unique_provider_name',
),
),
migrations.AddConstraint(
model_name='provideraccount',
constraint=models.UniqueConstraint(fields=('provider', 'account'), name='circuits_provideraccount_unique_provider_account'),
constraint=models.UniqueConstraint(
fields=('provider', 'account'), name='circuits_provideraccount_unique_provider_account'
),
),
migrations.RemoveField(
model_name='provider',
@ -111,7 +128,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='circuit',
name='provider_account',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='circuits.provideraccount'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='circuits',
to='circuits.provideraccount',
),
preserve_default=False,
),
migrations.AlterModelOptions(
@ -120,6 +143,8 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='circuit',
constraint=models.UniqueConstraint(fields=('provider_account', 'cid'), name='circuits_circuit_unique_provideraccount_cid'),
constraint=models.UniqueConstraint(
fields=('provider_account', 'cid'), name='circuits_circuit_unique_provideraccount_cid'
),
),
]

View File

@ -5,7 +5,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0043_circuittype_color'),
('extras', '0119_notifications'),

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0044_circuit_groups'),
]

View File

@ -15,7 +15,6 @@ def set_null_values(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('circuits', '0045_circuit_distance'),
]
@ -36,8 +35,5 @@ class Migration(migrations.Migration):
name='cable_end',
field=models.CharField(blank=True, max_length=1, null=True),
),
migrations.RunPython(
code=set_null_values,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(code=set_null_values, reverse_code=migrations.RunPython.noop),
]

View File

@ -11,19 +11,17 @@ def copy_site_assignments(apps, schema_editor):
Site = apps.get_model('dcim', 'Site')
CircuitTermination.objects.filter(site__isnull=False).update(
termination_type=ContentType.objects.get_for_model(Site),
termination_id=models.F('site_id')
termination_type=ContentType.objects.get_for_model(Site), termination_id=models.F('site_id')
)
ProviderNetwork = apps.get_model('circuits', 'ProviderNetwork')
CircuitTermination.objects.filter(provider_network__isnull=False).update(
termination_type=ContentType.objects.get_for_model(ProviderNetwork),
termination_id=models.F('provider_network_id')
termination_id=models.F('provider_network_id'),
)
class Migration(migrations.Migration):
dependencies = [
('circuits', '0046_charfield_null_choices'),
('contenttypes', '0002_remove_content_type_name'),
@ -41,17 +39,15 @@ class Migration(migrations.Migration):
name='termination_type',
field=models.ForeignKey(
blank=True,
limit_choices_to=models.Q(('model__in', ('region', 'sitegroup', 'site', 'location', 'providernetwork'))),
limit_choices_to=models.Q(
('model__in', ('region', 'sitegroup', 'site', 'location', 'providernetwork'))
),
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
# Copy over existing site assignments
migrations.RunPython(
code=copy_site_assignments,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(code=copy_site_assignments, reverse_code=migrations.RunPython.noop),
]

View File

@ -20,7 +20,6 @@ def populate_denormalized_fields(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('circuits', '0047_circuittermination__termination'),
]
@ -70,13 +69,8 @@ class Migration(migrations.Migration):
to='dcim.sitegroup',
),
),
# Populate denormalized FK values
migrations.RunPython(
code=populate_denormalized_fields,
reverse_code=migrations.RunPython.noop
),
migrations.RunPython(code=populate_denormalized_fields, reverse_code=migrations.RunPython.noop),
# Delete the site ForeignKey
migrations.RemoveField(
model_name='circuittermination',

View File

@ -2,7 +2,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('circuits', '0048_circuitterminations_cached_relations'),
('dcim', '0197_natural_sort_collation'),

View File

@ -0,0 +1,115 @@
import django.db.models.deletion
import taggit.managers
from django.db import migrations, models
import utilities.json
class Migration(migrations.Migration):
dependencies = [
('circuits', '0049_natural_ordering'),
('dcim', '0196_qinq_svlan'),
('extras', '0122_charfield_null_choices'),
('tenancy', '0016_charfield_null_choices'),
]
operations = [
migrations.CreateModel(
name='VirtualCircuit',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('description', models.CharField(blank=True, max_length=200)),
('comments', models.TextField(blank=True)),
('cid', models.CharField(max_length=100)),
('status', models.CharField(default='active', max_length=50)),
(
'provider_account',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='virtual_circuits',
to='circuits.provideraccount',
),
),
(
'provider_network',
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name='virtual_circuits',
to='circuits.providernetwork',
),
),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
(
'tenant',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='virtual_circuits',
to='tenancy.tenant',
),
),
],
options={
'verbose_name': 'circuit',
'verbose_name_plural': 'circuits',
'ordering': ['provider_network', 'provider_account', 'cid'],
},
),
migrations.CreateModel(
name='VirtualCircuitTermination',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('role', models.CharField(default='peer', max_length=50)),
('description', models.CharField(blank=True, max_length=200)),
(
'interface',
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name='virtual_circuit_termination',
to='dcim.interface',
),
),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
(
'virtual_circuit',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='terminations',
to='circuits.virtualcircuit',
),
),
],
options={
'verbose_name': 'virtual circuit termination',
'verbose_name_plural': 'virtual circuit terminations',
'ordering': ['virtual_circuit', 'role', 'pk'],
},
),
migrations.AddConstraint(
model_name='virtualcircuit',
constraint=models.UniqueConstraint(
fields=('provider_network', 'cid'), name='circuits_virtualcircuit_unique_provider_network_cid'
),
),
migrations.AddConstraint(
model_name='virtualcircuit',
constraint=models.UniqueConstraint(
fields=('provider_account', 'cid'), name='circuits_virtualcircuit_unique_provideraccount_cid'
),
),
]

View File

@ -1,2 +1,3 @@
from .circuits import *
from .providers import *
from .virtual_circuits import *

View File

@ -11,7 +11,9 @@ from circuits.constants import *
from dcim.models import CabledObjectModel
from netbox.models import ChangeLoggedModel, OrganizationalModel, PrimaryModel
from netbox.models.mixins import DistanceMixin
from netbox.models.features import ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin
from netbox.models.features import (
ContactsMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, ImageAttachmentsMixin, TagsMixin,
)
from utilities.fields import ColorField
__all__ = (

View File

@ -0,0 +1,164 @@
from functools import cached_property
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from circuits.choices import *
from netbox.models import ChangeLoggedModel, PrimaryModel
from netbox.models.features import CustomFieldsMixin, CustomLinksMixin, TagsMixin
__all__ = (
'VirtualCircuit',
'VirtualCircuitTermination',
)
class VirtualCircuit(PrimaryModel):
"""
A virtual connection between two or more endpoints, delivered across one or more physical circuits.
"""
cid = models.CharField(
max_length=100,
verbose_name=_('circuit ID'),
help_text=_('Unique circuit ID')
)
provider_network = models.ForeignKey(
to='circuits.ProviderNetwork',
on_delete=models.PROTECT,
related_name='virtual_circuits'
)
provider_account = models.ForeignKey(
to='circuits.ProviderAccount',
on_delete=models.PROTECT,
related_name='virtual_circuits',
blank=True,
null=True
)
status = models.CharField(
verbose_name=_('status'),
max_length=50,
choices=CircuitStatusChoices,
default=CircuitStatusChoices.STATUS_ACTIVE
)
tenant = models.ForeignKey(
to='tenancy.Tenant',
on_delete=models.PROTECT,
related_name='virtual_circuits',
blank=True,
null=True
)
clone_fields = (
'provider_network', 'provider_account', 'status', 'tenant', 'description',
)
prerequisite_models = (
'circuits.ProviderNetwork',
)
class Meta:
ordering = ['provider_network', 'provider_account', 'cid']
constraints = (
models.UniqueConstraint(
fields=('provider_network', 'cid'),
name='%(app_label)s_%(class)s_unique_provider_network_cid'
),
models.UniqueConstraint(
fields=('provider_account', 'cid'),
name='%(app_label)s_%(class)s_unique_provideraccount_cid'
),
)
verbose_name = _('virtual circuit')
verbose_name_plural = _('virtual circuits')
def __str__(self):
return self.cid
def get_status_color(self):
return CircuitStatusChoices.colors.get(self.status)
def clean(self):
super().clean()
if self.provider_account and self.provider_network.provider != self.provider_account.provider:
raise ValidationError({
'provider_account': "The assigned account must belong to the provider of the assigned network."
})
@property
def provider(self):
return self.provider_network.provider
class VirtualCircuitTermination(
CustomFieldsMixin,
CustomLinksMixin,
TagsMixin,
ChangeLoggedModel
):
virtual_circuit = models.ForeignKey(
to='circuits.VirtualCircuit',
on_delete=models.CASCADE,
related_name='terminations'
)
role = models.CharField(
verbose_name=_('role'),
max_length=50,
choices=VirtualCircuitTerminationRoleChoices,
default=VirtualCircuitTerminationRoleChoices.ROLE_PEER
)
interface = models.OneToOneField(
to='dcim.Interface',
on_delete=models.CASCADE,
related_name='virtual_circuit_termination'
)
description = models.CharField(
verbose_name=_('description'),
max_length=200,
blank=True
)
class Meta:
ordering = ['virtual_circuit', 'role', 'pk']
verbose_name = _('virtual circuit termination')
verbose_name_plural = _('virtual circuit terminations')
def __str__(self):
return f'{self.virtual_circuit}: {self.get_role_display()} termination'
def get_absolute_url(self):
return reverse('circuits:virtualcircuittermination', args=[self.pk])
def get_role_color(self):
return VirtualCircuitTerminationRoleChoices.colors.get(self.role)
def to_objectchange(self, action):
objectchange = super().to_objectchange(action)
objectchange.related_object = self.virtual_circuit
return objectchange
@property
def parent_object(self):
return self.virtual_circuit
@cached_property
def peer_terminations(self):
if self.role == VirtualCircuitTerminationRoleChoices.ROLE_PEER:
return self.virtual_circuit.terminations.exclude(pk=self.pk).filter(
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER
)
if self.role == VirtualCircuitTerminationRoleChoices.ROLE_HUB:
return self.virtual_circuit.terminations.filter(
role=VirtualCircuitTerminationRoleChoices.ROLE_SPOKE
)
if self.role == VirtualCircuitTerminationRoleChoices.ROLE_SPOKE:
return self.virtual_circuit.terminations.filter(
role=VirtualCircuitTerminationRoleChoices.ROLE_HUB
)
def clean(self):
super().clean()
if self.interface and not self.interface.is_virtual:
raise ValidationError("Virtual circuits may be terminated only to virtual interfaces.")

View File

@ -80,3 +80,23 @@ class ProviderNetworkIndex(SearchIndex):
('comments', 5000),
)
display_attrs = ('provider', 'service_id', 'description')
@register_search
class VirtualCircuitIndex(SearchIndex):
model = models.VirtualCircuit
fields = (
('cid', 100),
('description', 500),
('comments', 5000),
)
display_attrs = ('provider', 'provider_network', 'provider_account', 'status', 'tenant', 'description')
@register_search
class VirtualCircuitTerminationIndex(SearchIndex):
model = models.VirtualCircuitTermination
fields = (
('description', 500),
)
display_attrs = ('virtual_circuit', 'role', 'description')

View File

@ -1,3 +1,4 @@
from .circuits import *
from .columns import *
from .providers import *
from .virtual_circuits import *

View File

@ -42,7 +42,8 @@ class CircuitTypeTable(NetBoxTable):
class Meta(NetBoxTable.Meta):
model = CircuitType
fields = (
'pk', 'id', 'name', 'circuit_count', 'color', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
'pk', 'id', 'name', 'circuit_count', 'color', 'description', 'slug', 'tags', 'created', 'last_updated',
'actions',
)
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug')

View File

@ -0,0 +1,95 @@
import django_tables2 as tables
from django.utils.translation import gettext_lazy as _
from circuits.models import *
from netbox.tables import NetBoxTable, columns
from tenancy.tables import ContactsColumnMixin, TenancyColumnsMixin
__all__ = (
'VirtualCircuitTable',
'VirtualCircuitTerminationTable',
)
class VirtualCircuitTable(TenancyColumnsMixin, ContactsColumnMixin, NetBoxTable):
cid = tables.Column(
linkify=True,
verbose_name=_('Circuit ID')
)
provider = tables.Column(
accessor=tables.A('provider_network__provider'),
verbose_name=_('Provider'),
linkify=True
)
provider_network = tables.Column(
linkify=True,
verbose_name=_('Provider network')
)
provider_account = tables.Column(
linkify=True,
verbose_name=_('Account')
)
status = columns.ChoiceFieldColumn()
termination_count = columns.LinkedCountColumn(
viewname='circuits:virtualcircuittermination_list',
url_params={'virtual_circuit_id': 'pk'},
verbose_name=_('Terminations')
)
comments = columns.MarkdownColumn(
verbose_name=_('Comments')
)
tags = columns.TagColumn(
url_name='circuits:virtualcircuit_list'
)
class Meta(NetBoxTable.Meta):
model = VirtualCircuit
fields = (
'pk', 'id', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'tenant_group',
'description', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'cid', 'provider', 'provider_account', 'provider_network', 'status', 'tenant', 'termination_count',
'description',
)
class VirtualCircuitTerminationTable(NetBoxTable):
virtual_circuit = tables.Column(
verbose_name=_('Virtual circuit'),
linkify=True
)
provider = tables.Column(
accessor=tables.A('virtual_circuit__provider_network__provider'),
verbose_name=_('Provider'),
linkify=True
)
provider_network = tables.Column(
accessor=tables.A('virtual_circuit__provider_network'),
linkify=True,
verbose_name=_('Provider network')
)
provider_account = tables.Column(
linkify=True,
verbose_name=_('Account')
)
role = columns.ChoiceFieldColumn()
device = tables.Column(
accessor=tables.A('interface__device'),
linkify=True,
verbose_name=_('Device')
)
interface = tables.Column(
verbose_name=_('Interface'),
linkify=True
)
class Meta(NetBoxTable.Meta):
model = VirtualCircuitTermination
fields = (
'pk', 'id', 'virtual_circuit', 'provider', 'provider_network', 'provider_account', 'role', 'interfaces',
'description', 'created', 'last_updated', 'actions',
)
default_columns = (
'pk', 'id', 'virtual_circuit', 'role', 'device', 'interface', 'description',
)

View File

@ -2,7 +2,8 @@ from django.urls import reverse
from circuits.choices import *
from circuits.models import *
from dcim.models import Site
from dcim.choices import InterfaceTypeChoices
from dcim.models import Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
from ipam.models import ASN, RIR
from utilities.testing import APITestCase, APIViewTestCases
@ -120,9 +121,15 @@ class CircuitTest(APIViewTestCases.APIViewTestCase):
CircuitType.objects.bulk_create(circuit_types)
circuits = (
Circuit(cid='Circuit 1', provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0]),
Circuit(cid='Circuit 2', provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0]),
Circuit(cid='Circuit 3', provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0]),
Circuit(
cid='Circuit 1', provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0]
),
Circuit(
cid='Circuit 2', provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0]
),
Circuit(
cid='Circuit 3', provider=providers[0], provider_account=provider_accounts[0], type=circuit_types[0]
),
)
Circuit.objects.bulk_create(circuits)
@ -397,3 +404,240 @@ class ProviderNetworkTest(APIViewTestCases.APIViewTestCase):
'provider': providers[1].pk,
'description': 'New description',
}
class VirtualCircuitTest(APIViewTestCases.APIViewTestCase):
model = VirtualCircuit
brief_fields = ['cid', 'description', 'display', 'id', 'provider_network', 'url']
bulk_update_data = {
'status': 'planned',
}
@classmethod
def setUpTestData(cls):
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
provider_network = ProviderNetwork.objects.create(provider=provider, name='Provider Network 1')
provider_account = ProviderAccount.objects.create(provider=provider, account='Provider Account 1')
virtual_circuits = (
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 1'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 2'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 3'
),
)
VirtualCircuit.objects.bulk_create(virtual_circuits)
cls.create_data = [
{
'cid': 'Virtual Circuit 4',
'provider_network': provider_network.pk,
'provider_account': provider_account.pk,
'status': CircuitStatusChoices.STATUS_PLANNED,
},
{
'cid': 'Virtual Circuit 5',
'provider_network': provider_network.pk,
'provider_account': provider_account.pk,
'status': CircuitStatusChoices.STATUS_PLANNED,
},
{
'cid': 'Virtual Circuit 6',
'provider_network': provider_network.pk,
'provider_account': provider_account.pk,
'status': CircuitStatusChoices.STATUS_PLANNED,
},
]
class VirtualCircuitTerminationTest(APIViewTestCases.APIViewTestCase):
model = VirtualCircuitTermination
brief_fields = ['description', 'display', 'id', 'interface', 'role', 'url', 'virtual_circuit']
bulk_update_data = {
'description': 'New description',
}
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
site = Site.objects.create(name='Site 1', slug='site-1')
devices = (
Device(site=site, name='hub', device_type=device_type, role=device_role),
Device(site=site, name='spoke1', device_type=device_type, role=device_role),
Device(site=site, name='spoke2', device_type=device_type, role=device_role),
Device(site=site, name='spoke3', device_type=device_type, role=device_role),
)
Device.objects.bulk_create(devices)
physical_interfaces = (
Interface(device=devices[0], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[1], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[2], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[3], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
)
Interface.objects.bulk_create(physical_interfaces)
virtual_interfaces = (
# Point-to-point VCs
Interface(
device=devices[0],
name='eth0.1',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[0],
name='eth0.2',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[0],
name='eth0.3',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[1],
name='eth0.1',
parent=physical_interfaces[1],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[2],
name='eth0.1',
parent=physical_interfaces[2],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[3],
name='eth0.1',
parent=physical_interfaces[3],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
# Hub and spoke VCs
Interface(
device=devices[0],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[1],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[2],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[3],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
)
Interface.objects.bulk_create(virtual_interfaces)
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
provider_network = ProviderNetwork.objects.create(provider=provider, name='Provider Network 1')
provider_account = ProviderAccount.objects.create(provider=provider, account='Provider Account 1')
virtual_circuits = (
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 1'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 2'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 3'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 4'
),
)
VirtualCircuit.objects.bulk_create(virtual_circuits)
virtual_circuit_terminations = (
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[0],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[0]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[0],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[3]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[1],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[1]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[1],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[4]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[2],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[2]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[2],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[5]
),
)
VirtualCircuitTermination.objects.bulk_create(virtual_circuit_terminations)
cls.create_data = [
{
'virtual_circuit': virtual_circuits[3].pk,
'role': VirtualCircuitTerminationRoleChoices.ROLE_HUB,
'interface': virtual_interfaces[6].pk
},
{
'virtual_circuit': virtual_circuits[3].pk,
'role': VirtualCircuitTerminationRoleChoices.ROLE_SPOKE,
'interface': virtual_interfaces[7].pk
},
{
'virtual_circuit': virtual_circuits[3].pk,
'role': VirtualCircuitTerminationRoleChoices.ROLE_SPOKE,
'interface': virtual_interfaces[8].pk
},
{
'virtual_circuit': virtual_circuits[3].pk,
'role': VirtualCircuitTerminationRoleChoices.ROLE_SPOKE,
'interface': virtual_interfaces[9].pk
},
]

View File

@ -3,7 +3,8 @@ from django.test import TestCase
from circuits.choices import *
from circuits.filtersets import *
from circuits.models import *
from dcim.models import Cable, Region, Site, SiteGroup
from dcim.choices import InterfaceTypeChoices
from dcim.models import Cable, Device, DeviceRole, DeviceType, Interface, Manufacturer, Region, Site, SiteGroup
from ipam.models import ASN, RIR
from netbox.choices import DistanceUnitChoices
from tenancy.models import Tenant, TenantGroup
@ -225,12 +226,80 @@ class CircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
ProviderNetwork.objects.bulk_create(provider_networks)
circuits = (
Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 1', install_date='2020-01-01', termination_date='2021-01-01', commit_rate=1000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar1', distance=10, distance_unit=DistanceUnitChoices.UNIT_FOOT),
Circuit(provider=providers[0], provider_account=provider_accounts[0], tenant=tenants[0], type=circuit_types[0], cid='Test Circuit 2', install_date='2020-01-02', termination_date='2021-01-02', commit_rate=2000, status=CircuitStatusChoices.STATUS_ACTIVE, description='foobar2', distance=20, distance_unit=DistanceUnitChoices.UNIT_METER),
Circuit(provider=providers[0], provider_account=provider_accounts[1], tenant=tenants[1], type=circuit_types[0], cid='Test Circuit 3', install_date='2020-01-03', termination_date='2021-01-03', commit_rate=3000, status=CircuitStatusChoices.STATUS_PLANNED, distance=30, distance_unit=DistanceUnitChoices.UNIT_METER),
Circuit(provider=providers[1], provider_account=provider_accounts[1], tenant=tenants[1], type=circuit_types[1], cid='Test Circuit 4', install_date='2020-01-04', termination_date='2021-01-04', commit_rate=4000, status=CircuitStatusChoices.STATUS_PLANNED),
Circuit(provider=providers[1], provider_account=provider_accounts[2], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 5', install_date='2020-01-05', termination_date='2021-01-05', commit_rate=5000, status=CircuitStatusChoices.STATUS_OFFLINE),
Circuit(provider=providers[1], provider_account=provider_accounts[2], tenant=tenants[2], type=circuit_types[1], cid='Test Circuit 6', install_date='2020-01-06', termination_date='2021-01-06', commit_rate=6000, status=CircuitStatusChoices.STATUS_OFFLINE),
Circuit(
provider=providers[0],
provider_account=provider_accounts[0],
tenant=tenants[0],
type=circuit_types[0],
cid='Test Circuit 1',
install_date='2020-01-01',
termination_date='2021-01-01',
commit_rate=1000,
status=CircuitStatusChoices.STATUS_ACTIVE,
description='foobar1',
distance=10,
distance_unit=DistanceUnitChoices.UNIT_FOOT,
),
Circuit(
provider=providers[0],
provider_account=provider_accounts[0],
tenant=tenants[0],
type=circuit_types[0],
cid='Test Circuit 2',
install_date='2020-01-02',
termination_date='2021-01-02',
commit_rate=2000,
status=CircuitStatusChoices.STATUS_ACTIVE,
description='foobar2',
distance=20,
distance_unit=DistanceUnitChoices.UNIT_METER,
),
Circuit(
provider=providers[0],
provider_account=provider_accounts[1],
tenant=tenants[1],
type=circuit_types[0],
cid='Test Circuit 3',
install_date='2020-01-03',
termination_date='2021-01-03',
commit_rate=3000,
status=CircuitStatusChoices.STATUS_PLANNED,
distance=30,
distance_unit=DistanceUnitChoices.UNIT_METER,
),
Circuit(
provider=providers[1],
provider_account=provider_accounts[1],
tenant=tenants[1],
type=circuit_types[1],
cid='Test Circuit 4',
install_date='2020-01-04',
termination_date='2021-01-04',
commit_rate=4000,
status=CircuitStatusChoices.STATUS_PLANNED,
),
Circuit(
provider=providers[1],
provider_account=provider_accounts[2],
tenant=tenants[2],
type=circuit_types[1],
cid='Test Circuit 5',
install_date='2020-01-05',
termination_date='2021-01-05',
commit_rate=5000,
status=CircuitStatusChoices.STATUS_OFFLINE,
),
Circuit(
provider=providers[1],
provider_account=provider_accounts[2],
tenant=tenants[2],
type=circuit_types[1],
cid='Test Circuit 6',
install_date='2020-01-06',
termination_date='2021-01-06',
commit_rate=6000,
status=CircuitStatusChoices.STATUS_OFFLINE,
),
)
Circuit.objects.bulk_create(circuits)
@ -386,18 +455,64 @@ class CircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
)
Circuit.objects.bulk_create(circuits)
circuit_terminations = ((
CircuitTermination(circuit=circuits[0], termination=sites[0], term_side='A', port_speed=1000, upstream_speed=1000, xconnect_id='ABC', description='foobar1'),
CircuitTermination(circuit=circuits[0], termination=sites[1], term_side='Z', port_speed=1000, upstream_speed=1000, xconnect_id='DEF', description='foobar2'),
CircuitTermination(circuit=circuits[1], termination=sites[1], term_side='A', port_speed=2000, upstream_speed=2000, xconnect_id='GHI'),
CircuitTermination(circuit=circuits[1], termination=sites[2], term_side='Z', port_speed=2000, upstream_speed=2000, xconnect_id='JKL'),
CircuitTermination(circuit=circuits[2], termination=sites[2], term_side='A', port_speed=3000, upstream_speed=3000, xconnect_id='MNO'),
CircuitTermination(circuit=circuits[2], termination=sites[0], term_side='Z', port_speed=3000, upstream_speed=3000, xconnect_id='PQR'),
circuit_terminations = (
CircuitTermination(
circuit=circuits[0],
termination=sites[0],
term_side='A',
port_speed=1000,
upstream_speed=1000,
xconnect_id='ABC',
description='foobar1',
),
CircuitTermination(
circuit=circuits[0],
termination=sites[1],
term_side='Z',
port_speed=1000,
upstream_speed=1000,
xconnect_id='DEF',
description='foobar2',
),
CircuitTermination(
circuit=circuits[1],
termination=sites[1],
term_side='A',
port_speed=2000,
upstream_speed=2000,
xconnect_id='GHI',
),
CircuitTermination(
circuit=circuits[1],
termination=sites[2],
term_side='Z',
port_speed=2000,
upstream_speed=2000,
xconnect_id='JKL',
),
CircuitTermination(
circuit=circuits[2],
termination=sites[2],
term_side='A',
port_speed=3000,
upstream_speed=3000,
xconnect_id='MNO',
),
CircuitTermination(
circuit=circuits[2],
termination=sites[0],
term_side='Z',
port_speed=3000,
upstream_speed=3000,
xconnect_id='PQR',
),
CircuitTermination(circuit=circuits[3], termination=provider_networks[0], term_side='A'),
CircuitTermination(circuit=circuits[4], termination=provider_networks[1], term_side='A'),
CircuitTermination(circuit=circuits[5], termination=provider_networks[2], term_side='A'),
CircuitTermination(circuit=circuits[6], termination=provider_networks[0], term_side='A', mark_connected=True),
))
CircuitTermination(
circuit=circuits[6], termination=provider_networks[0], term_side='A', mark_connected=True
),
)
for ct in circuit_terminations:
ct.save()
@ -678,3 +793,293 @@ class ProviderAccountTestCase(TestCase, ChangeLoggedFilterSetTests):
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'provider': [providers[0].slug, providers[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class VirtualCircuitTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = VirtualCircuit.objects.all()
filterset = VirtualCircuitFilterSet
@classmethod
def setUpTestData(cls):
tenant_groups = (
TenantGroup(name='Tenant group 1', slug='tenant-group-1'),
TenantGroup(name='Tenant group 2', slug='tenant-group-2'),
TenantGroup(name='Tenant group 3', slug='tenant-group-3'),
)
for tenantgroup in tenant_groups:
tenantgroup.save()
tenants = (
Tenant(name='Tenant 1', slug='tenant-1', group=tenant_groups[0]),
Tenant(name='Tenant 2', slug='tenant-2', group=tenant_groups[1]),
Tenant(name='Tenant 3', slug='tenant-3', group=tenant_groups[2]),
)
Tenant.objects.bulk_create(tenants)
providers = (
Provider(name='Provider 1', slug='provider-1'),
Provider(name='Provider 2', slug='provider-2'),
Provider(name='Provider 3', slug='provider-3'),
)
Provider.objects.bulk_create(providers)
provider_accounts = (
ProviderAccount(name='Provider Account 1', provider=providers[0], account='A'),
ProviderAccount(name='Provider Account 2', provider=providers[1], account='B'),
ProviderAccount(name='Provider Account 3', provider=providers[2], account='C'),
)
ProviderAccount.objects.bulk_create(provider_accounts)
provider_networks = (
ProviderNetwork(name='Provider Network 1', provider=providers[0]),
ProviderNetwork(name='Provider Network 2', provider=providers[1]),
ProviderNetwork(name='Provider Network 3', provider=providers[2]),
)
ProviderNetwork.objects.bulk_create(provider_networks)
virutal_circuits = (
VirtualCircuit(
provider_network=provider_networks[0],
provider_account=provider_accounts[0],
tenant=tenants[0],
cid='Virtual Circuit 1',
status=CircuitStatusChoices.STATUS_PLANNED,
description='virtualcircuit1',
),
VirtualCircuit(
provider_network=provider_networks[1],
provider_account=provider_accounts[1],
tenant=tenants[1],
cid='Virtual Circuit 2',
status=CircuitStatusChoices.STATUS_ACTIVE,
description='virtualcircuit2',
),
VirtualCircuit(
provider_network=provider_networks[2],
provider_account=provider_accounts[2],
tenant=tenants[2],
cid='Virtual Circuit 3',
status=CircuitStatusChoices.STATUS_DEPROVISIONING,
description='virtualcircuit3',
),
)
VirtualCircuit.objects.bulk_create(virutal_circuits)
def test_q(self):
params = {'q': 'virtualcircuit1'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_cid(self):
params = {'cid': ['Virtual Circuit 1', 'Virtual Circuit 2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_provider(self):
providers = Provider.objects.all()[:2]
params = {'provider_id': [providers[0].pk, providers[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'provider': [providers[0].slug, providers[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_provider_account(self):
provider_accounts = ProviderAccount.objects.all()[:2]
params = {'provider_account_id': [provider_accounts[0].pk, provider_accounts[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_provider_network(self):
provider_networks = ProviderNetwork.objects.all()[:2]
params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_status(self):
params = {'status': [CircuitStatusChoices.STATUS_ACTIVE, CircuitStatusChoices.STATUS_PLANNED]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_description(self):
params = {'description': ['virtualcircuit1', 'virtualcircuit2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_tenant(self):
tenants = Tenant.objects.all()[:2]
params = {'tenant_id': [tenants[0].pk, tenants[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'tenant': [tenants[0].slug, tenants[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_tenant_group(self):
tenant_groups = TenantGroup.objects.all()[:2]
params = {'tenant_group_id': [tenant_groups[0].pk, tenant_groups[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
params = {'tenant_group': [tenant_groups[0].slug, tenant_groups[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
class VirtualCircuitTerminationTestCase(TestCase, ChangeLoggedFilterSetTests):
queryset = VirtualCircuitTermination.objects.all()
filterset = VirtualCircuitTerminationFilterSet
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
site = Site.objects.create(name='Site 1', slug='site-1')
devices = (
Device(site=site, name='Device 1', device_type=device_type, role=device_role),
Device(site=site, name='Device 2', device_type=device_type, role=device_role),
Device(site=site, name='Device 3', device_type=device_type, role=device_role),
)
Device.objects.bulk_create(devices)
virtual_interfaces = (
# Device 1
Interface(
device=devices[0],
name='eth0.1',
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[0],
name='eth0.2',
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
# Device 2
Interface(
device=devices[1],
name='eth0.1',
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[1],
name='eth0.2',
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
# Device 3
Interface(
device=devices[2],
name='eth0.1',
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[2],
name='eth0.2',
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
)
Interface.objects.bulk_create(virtual_interfaces)
providers = (
Provider(name='Provider 1', slug='provider-1'),
Provider(name='Provider 2', slug='provider-2'),
Provider(name='Provider 3', slug='provider-3'),
)
Provider.objects.bulk_create(providers)
provider_networks = (
ProviderNetwork(provider=providers[0], name='Provider Network 1'),
ProviderNetwork(provider=providers[1], name='Provider Network 2'),
ProviderNetwork(provider=providers[2], name='Provider Network 3'),
)
ProviderNetwork.objects.bulk_create(provider_networks)
provider_accounts = (
ProviderAccount(provider=providers[0], account='Provider Account 1'),
ProviderAccount(provider=providers[1], account='Provider Account 2'),
ProviderAccount(provider=providers[2], account='Provider Account 3'),
)
ProviderAccount.objects.bulk_create(provider_accounts)
virtual_circuits = (
VirtualCircuit(
provider_network=provider_networks[0],
provider_account=provider_accounts[0],
cid='Virtual Circuit 1'
),
VirtualCircuit(
provider_network=provider_networks[1],
provider_account=provider_accounts[1],
cid='Virtual Circuit 2'
),
VirtualCircuit(
provider_network=provider_networks[2],
provider_account=provider_accounts[2],
cid='Virtual Circuit 3'
),
)
VirtualCircuit.objects.bulk_create(virtual_circuits)
virtual_circuit_terminations = (
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[0],
role=VirtualCircuitTerminationRoleChoices.ROLE_HUB,
interface=virtual_interfaces[0],
description='termination1'
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[0],
role=VirtualCircuitTerminationRoleChoices.ROLE_SPOKE,
interface=virtual_interfaces[3],
description='termination2'
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[1],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[1],
description='termination3'
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[1],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[4],
description='termination4'
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[2],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[2],
description='termination5'
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[2],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[5],
description='termination6'
),
)
VirtualCircuitTermination.objects.bulk_create(virtual_circuit_terminations)
def test_q(self):
params = {'q': 'termination1'}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
def test_description(self):
params = {'description': ['termination1', 'termination2']}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
def test_virtual_circuit_id(self):
virtual_circuits = VirtualCircuit.objects.filter()[:2]
params = {'virtual_circuit_id': [virtual_circuits[0].pk, virtual_circuits[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_provider(self):
providers = Provider.objects.all()[:2]
params = {'provider_id': [providers[0].pk, providers[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'provider': [providers[0].slug, providers[1].slug]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_provider_network(self):
provider_networks = ProviderNetwork.objects.all()[:2]
params = {'provider_network_id': [provider_networks[0].pk, provider_networks[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_provider_account(self):
provider_accounts = ProviderAccount.objects.all()[:2]
params = {'provider_account_id': [provider_accounts[0].pk, provider_accounts[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
params = {'provider_account': [provider_accounts[0].account, provider_accounts[1].account]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
def test_interface(self):
interfaces = Interface.objects.all()[:2]
params = {'interface_id': [interfaces[0].pk, interfaces[1].pk]}
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

View File

@ -7,7 +7,8 @@ from django.urls import reverse
from circuits.choices import *
from circuits.models import *
from core.models import ObjectType
from dcim.models import Cable, Interface, Site
from dcim.choices import InterfaceTypeChoices
from dcim.models import Cable, Device, DeviceRole, DeviceType, Interface, Manufacturer, Site
from ipam.models import ASN, RIR
from netbox.choices import ImportFormatChoices
from users.models import ObjectPermission
@ -140,9 +141,15 @@ class CircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
CircuitType.objects.bulk_create(circuittypes)
circuits = (
Circuit(cid='Circuit 1', provider=providers[0], provider_account=provider_accounts[0], type=circuittypes[0]),
Circuit(cid='Circuit 2', provider=providers[0], provider_account=provider_accounts[0], type=circuittypes[0]),
Circuit(cid='Circuit 3', provider=providers[0], provider_account=provider_accounts[0], type=circuittypes[0]),
Circuit(
cid='Circuit 1', provider=providers[0], provider_account=provider_accounts[0], type=circuittypes[0]
),
Circuit(
cid='Circuit 2', provider=providers[0], provider_account=provider_accounts[0], type=circuittypes[0]
),
Circuit(
cid='Circuit 3', provider=providers[0], provider_account=provider_accounts[0], type=circuittypes[0]
),
)
Circuit.objects.bulk_create(circuits)
@ -341,7 +348,7 @@ class ProviderNetworkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
}
class TestCase(ViewTestCases.PrimaryObjectViewTestCase):
class CircuitTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = CircuitTermination
@classmethod
@ -518,3 +525,319 @@ class CircuitGroupAssignmentTestCase(
cls.bulk_edit_data = {
'priority': CircuitPriorityChoices.PRIORITY_INACTIVE,
}
class VirtualCircuitTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = VirtualCircuit
def setUp(self):
super().setUp()
self.add_permissions(
'circuits.add_virtualcircuittermination',
)
@classmethod
def setUpTestData(cls):
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
provider_networks = (
ProviderNetwork(provider=provider, name='Provider Network 1'),
ProviderNetwork(provider=provider, name='Provider Network 2'),
)
ProviderNetwork.objects.bulk_create(provider_networks)
provider_accounts = (
ProviderAccount(provider=provider, account='Provider Account 1'),
ProviderAccount(provider=provider, account='Provider Account 2'),
)
ProviderAccount.objects.bulk_create(provider_accounts)
virtual_circuits = (
VirtualCircuit(
provider_network=provider_networks[0],
provider_account=provider_accounts[0],
cid='Virtual Circuit 1'
),
VirtualCircuit(
provider_network=provider_networks[0],
provider_account=provider_accounts[0],
cid='Virtual Circuit 2'
),
VirtualCircuit(
provider_network=provider_networks[0],
provider_account=provider_accounts[0],
cid='Virtual Circuit 3'
),
)
VirtualCircuit.objects.bulk_create(virtual_circuits)
device = create_test_device('Device 1')
interfaces = (
Interface(device=device, name='Interface 1', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 2', type=InterfaceTypeChoices.TYPE_VIRTUAL),
Interface(device=device, name='Interface 3', type=InterfaceTypeChoices.TYPE_VIRTUAL),
)
Interface.objects.bulk_create(interfaces)
tags = create_tags('Alpha', 'Bravo', 'Charlie')
cls.form_data = {
'cid': 'Virtual Circuit X',
'provider_network': provider_networks[1].pk,
'provider_account': provider_accounts[1].pk,
'status': CircuitStatusChoices.STATUS_PLANNED,
'description': 'A new virtual circuit',
'comments': 'Some comments',
'tags': [t.pk for t in tags],
}
cls.csv_data = (
"cid,provider_network,provider_account,status",
f"Virtual Circuit 4,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}",
f"Virtual Circuit 5,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}",
f"Virtual Circuit 6,Provider Network 1,Provider Account 1,{CircuitStatusChoices.STATUS_PLANNED}",
)
cls.csv_update_data = (
"id,cid,description,status",
f"{virtual_circuits[0].pk},Virtual Circuit A,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}",
f"{virtual_circuits[1].pk},Virtual Circuit B,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}",
f"{virtual_circuits[2].pk},Virtual Circuit C,New description,{CircuitStatusChoices.STATUS_DECOMMISSIONED}",
)
cls.bulk_edit_data = {
'provider_network': provider_networks[1].pk,
'provider_account': provider_accounts[1].pk,
'status': CircuitStatusChoices.STATUS_DECOMMISSIONED,
'description': 'New description',
'comments': 'New comments',
}
@override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
def test_bulk_import_objects_with_terminations(self):
interfaces = Interface.objects.filter(type=InterfaceTypeChoices.TYPE_VIRTUAL)
json_data = f"""
[
{{
"cid": "Virtual Circuit 7",
"provider_network": "Provider Network 1",
"status": "active",
"terminations": [
{{
"role": "hub",
"interface": {interfaces[0].pk}
}},
{{
"role": "spoke",
"interface": {interfaces[1].pk}
}},
{{
"role": "spoke",
"interface": {interfaces[2].pk}
}}
]
}}
]
"""
initial_count = self._get_queryset().count()
data = {
'data': json_data,
'format': ImportFormatChoices.JSON,
}
# Assign model-level permission
obj_perm = ObjectPermission(
name='Test permission',
actions=['add']
)
obj_perm.save()
obj_perm.users.add(self.user)
obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
# Try GET with model-level permission
self.assertHttpStatus(self.client.get(self._get_url('import')), 200)
# Test POST with permission
self.assertHttpStatus(self.client.post(self._get_url('import'), data), 302)
self.assertEqual(self._get_queryset().count(), initial_count + 1)
class VirtualCircuitTerminationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
model = VirtualCircuitTermination
@classmethod
def setUpTestData(cls):
manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
device_type = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1')
device_role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
site = Site.objects.create(name='Site 1', slug='site-1')
devices = (
Device(site=site, name='hub', device_type=device_type, role=device_role),
Device(site=site, name='spoke1', device_type=device_type, role=device_role),
Device(site=site, name='spoke2', device_type=device_type, role=device_role),
Device(site=site, name='spoke3', device_type=device_type, role=device_role),
)
Device.objects.bulk_create(devices)
physical_interfaces = (
Interface(device=devices[0], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[1], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[2], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
Interface(device=devices[3], name='eth0', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
)
Interface.objects.bulk_create(physical_interfaces)
virtual_interfaces = (
# Point-to-point VCs
Interface(
device=devices[0],
name='eth0.1',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[0],
name='eth0.2',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[0],
name='eth0.3',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[1],
name='eth0.1',
parent=physical_interfaces[1],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[2],
name='eth0.1',
parent=physical_interfaces[2],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[3],
name='eth0.1',
parent=physical_interfaces[3],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
# Hub and spoke VCs
Interface(
device=devices[0],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[1],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[2],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
Interface(
device=devices[3],
name='eth0.9',
parent=physical_interfaces[0],
type=InterfaceTypeChoices.TYPE_VIRTUAL
),
)
Interface.objects.bulk_create(virtual_interfaces)
provider = Provider.objects.create(name='Provider 1', slug='provider-1')
provider_network = ProviderNetwork.objects.create(provider=provider, name='Provider Network 1')
provider_account = ProviderAccount.objects.create(provider=provider, account='Provider Account 1')
virtual_circuits = (
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 1'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 2'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 3'
),
VirtualCircuit(
provider_network=provider_network,
provider_account=provider_account,
cid='Virtual Circuit 4'
),
)
VirtualCircuit.objects.bulk_create(virtual_circuits)
virtual_circuit_terminations = (
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[0],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[0]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[0],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[3]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[1],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[1]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[1],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[4]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[2],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[2]
),
VirtualCircuitTermination(
virtual_circuit=virtual_circuits[2],
role=VirtualCircuitTerminationRoleChoices.ROLE_PEER,
interface=virtual_interfaces[5]
),
)
VirtualCircuitTermination.objects.bulk_create(virtual_circuit_terminations)
cls.form_data = {
'virtual_circuit': virtual_circuits[3].pk,
'role': VirtualCircuitTerminationRoleChoices.ROLE_HUB,
'interface': virtual_interfaces[6].pk
}
cls.csv_data = (
"virtual_circuit,role,interface,description",
f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_HUB},{virtual_interfaces[6].pk},Hub",
f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},{virtual_interfaces[7].pk},Spoke 1",
f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},{virtual_interfaces[8].pk},Spoke 2",
f"Virtual Circuit 4,{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},{virtual_interfaces[9].pk},Spoke 3",
)
cls.csv_update_data = (
"id,role,description",
f"{virtual_circuit_terminations[0].pk},{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},New description",
f"{virtual_circuit_terminations[1].pk},{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},New description",
f"{virtual_circuit_terminations[2].pk},{VirtualCircuitTerminationRoleChoices.ROLE_SPOKE},New description",
)
cls.bulk_edit_data = {
'description': 'New description',
}

View File

@ -5,69 +5,68 @@ from . import views
app_name = 'circuits'
urlpatterns = [
# Providers
path('providers/', views.ProviderListView.as_view(), name='provider_list'),
path('providers/add/', views.ProviderEditView.as_view(), name='provider_add'),
path('providers/import/', views.ProviderBulkImportView.as_view(), name='provider_import'),
path('providers/edit/', views.ProviderBulkEditView.as_view(), name='provider_bulk_edit'),
path('providers/delete/', views.ProviderBulkDeleteView.as_view(), name='provider_bulk_delete'),
path('providers/', include(get_model_urls('circuits', 'provider', detail=False))),
path('providers/<int:pk>/', include(get_model_urls('circuits', 'provider'))),
# Provider accounts
path('provider-accounts/', views.ProviderAccountListView.as_view(), name='provideraccount_list'),
path('provider-accounts/add/', views.ProviderAccountEditView.as_view(), name='provideraccount_add'),
path('provider-accounts/import/', views.ProviderAccountBulkImportView.as_view(), name='provideraccount_import'),
path('provider-accounts/edit/', views.ProviderAccountBulkEditView.as_view(), name='provideraccount_bulk_edit'),
path('provider-accounts/delete/', views.ProviderAccountBulkDeleteView.as_view(), name='provideraccount_bulk_delete'),
path('provider-accounts/', include(get_model_urls('circuits', 'provideraccount', detail=False))),
path('provider-accounts/<int:pk>/', include(get_model_urls('circuits', 'provideraccount'))),
# Provider networks
path('provider-networks/', views.ProviderNetworkListView.as_view(), name='providernetwork_list'),
path('provider-networks/add/', views.ProviderNetworkEditView.as_view(), name='providernetwork_add'),
path('provider-networks/import/', views.ProviderNetworkBulkImportView.as_view(), name='providernetwork_import'),
path('provider-networks/edit/', views.ProviderNetworkBulkEditView.as_view(), name='providernetwork_bulk_edit'),
path('provider-networks/delete/', views.ProviderNetworkBulkDeleteView.as_view(), name='providernetwork_bulk_delete'),
path('provider-networks/', include(get_model_urls('circuits', 'providernetwork', detail=False))),
path('provider-networks/<int:pk>/', include(get_model_urls('circuits', 'providernetwork'))),
# Circuit types
path('circuit-types/', views.CircuitTypeListView.as_view(), name='circuittype_list'),
path('circuit-types/add/', views.CircuitTypeEditView.as_view(), name='circuittype_add'),
path('circuit-types/import/', views.CircuitTypeBulkImportView.as_view(), name='circuittype_import'),
path('circuit-types/edit/', views.CircuitTypeBulkEditView.as_view(), name='circuittype_bulk_edit'),
path('circuit-types/delete/', views.CircuitTypeBulkDeleteView.as_view(), name='circuittype_bulk_delete'),
path('circuit-types/', include(get_model_urls('circuits', 'circuittype', detail=False))),
path('circuit-types/<int:pk>/', include(get_model_urls('circuits', 'circuittype'))),
# Circuits
path('circuits/', views.CircuitListView.as_view(), name='circuit_list'),
path('circuits/add/', views.CircuitEditView.as_view(), name='circuit_add'),
path('circuits/import/', views.CircuitBulkImportView.as_view(), name='circuit_import'),
path('circuits/edit/', views.CircuitBulkEditView.as_view(), name='circuit_bulk_edit'),
path('circuits/delete/', views.CircuitBulkDeleteView.as_view(), name='circuit_bulk_delete'),
path('circuits/<int:pk>/terminations/swap/', views.CircuitSwapTerminations.as_view(), name='circuit_terminations_swap'),
path('circuits/', include(get_model_urls('circuits', 'circuit', detail=False))),
path(
'circuits/<int:pk>/terminations/swap/',
views.CircuitSwapTerminations.as_view(),
name='circuit_terminations_swap'
),
path('circuits/<int:pk>/', include(get_model_urls('circuits', 'circuit'))),
# Circuit terminations
path('circuit-terminations/', views.CircuitTerminationListView.as_view(), name='circuittermination_list'),
path('circuit-terminations/add/', views.CircuitTerminationEditView.as_view(), name='circuittermination_add'),
path('circuit-terminations/import/', views.CircuitTerminationBulkImportView.as_view(), name='circuittermination_import'),
path('circuit-terminations/edit/', views.CircuitTerminationBulkEditView.as_view(), name='circuittermination_bulk_edit'),
path('circuit-terminations/delete/', views.CircuitTerminationBulkDeleteView.as_view(), name='circuittermination_bulk_delete'),
path('circuit-terminations/', include(get_model_urls('circuits', 'circuittermination', detail=False))),
path('circuit-terminations/<int:pk>/', include(get_model_urls('circuits', 'circuittermination'))),
# Circuit Groups
path('circuit-groups/', views.CircuitGroupListView.as_view(), name='circuitgroup_list'),
path('circuit-groups/add/', views.CircuitGroupEditView.as_view(), name='circuitgroup_add'),
path('circuit-groups/import/', views.CircuitGroupBulkImportView.as_view(), name='circuitgroup_import'),
path('circuit-groups/edit/', views.CircuitGroupBulkEditView.as_view(), name='circuitgroup_bulk_edit'),
path('circuit-groups/delete/', views.CircuitGroupBulkDeleteView.as_view(), name='circuitgroup_bulk_delete'),
path('circuit-groups/', include(get_model_urls('circuits', 'circuitgroup', detail=False))),
path('circuit-groups/<int:pk>/', include(get_model_urls('circuits', 'circuitgroup'))),
# Circuit Group Assignments
path('circuit-group-assignments/', views.CircuitGroupAssignmentListView.as_view(), name='circuitgroupassignment_list'),
path('circuit-group-assignments/add/', views.CircuitGroupAssignmentEditView.as_view(), name='circuitgroupassignment_add'),
path('circuit-group-assignments/import/', views.CircuitGroupAssignmentBulkImportView.as_view(), name='circuitgroupassignment_import'),
path('circuit-group-assignments/edit/', views.CircuitGroupAssignmentBulkEditView.as_view(), name='circuitgroupassignment_bulk_edit'),
path('circuit-group-assignments/delete/', views.CircuitGroupAssignmentBulkDeleteView.as_view(), name='circuitgroupassignment_bulk_delete'),
path('circuit-group-assignments/', include(get_model_urls('circuits', 'circuitgroupassignment', detail=False))),
path('circuit-group-assignments/<int:pk>/', include(get_model_urls('circuits', 'circuitgroupassignment'))),
# Virtual circuits
path('virtual-circuits/', views.VirtualCircuitListView.as_view(), name='virtualcircuit_list'),
path('virtual-circuits/add/', views.VirtualCircuitEditView.as_view(), name='virtualcircuit_add'),
path('virtual-circuits/import/', views.VirtualCircuitBulkImportView.as_view(), name='virtualcircuit_import'),
path('virtual-circuits/edit/', views.VirtualCircuitBulkEditView.as_view(), name='virtualcircuit_bulk_edit'),
path('virtual-circuits/delete/', views.VirtualCircuitBulkDeleteView.as_view(), name='virtualcircuit_bulk_delete'),
path('virtual-circuits/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuit'))),
# Virtual circuit terminations
path(
'virtual-circuit-terminations/',
views.VirtualCircuitTerminationListView.as_view(),
name='virtualcircuittermination_list',
),
path(
'virtual-circuit-terminations/add/',
views.VirtualCircuitTerminationEditView.as_view(),
name='virtualcircuittermination_add',
),
path(
'virtual-circuit-terminations/import/',
views.VirtualCircuitTerminationBulkImportView.as_view(),
name='virtualcircuittermination_import',
),
path(
'virtual-circuit-terminations/edit/',
views.VirtualCircuitTerminationBulkEditView.as_view(),
name='virtualcircuittermination_bulk_edit',
),
path(
'virtual-circuit-terminations/delete/',
views.VirtualCircuitTerminationBulkDeleteView.as_view(),
name='virtualcircuittermination_bulk_delete',
),
path('virtual-circuit-terminations/<int:pk>/', include(get_model_urls('circuits', 'virtualcircuittermination'))),
]

View File

@ -17,6 +17,7 @@ from .models import *
# Providers
#
@register_model_view(Provider, 'list', path='', detail=False)
class ProviderListView(generic.ObjectListView):
queryset = Provider.objects.annotate(
count_circuits=count_related(Circuit, 'provider')
@ -36,6 +37,7 @@ class ProviderView(GetRelatedModelsMixin, generic.ObjectView):
}
@register_model_view(Provider, 'add', detail=False)
@register_model_view(Provider, 'edit')
class ProviderEditView(generic.ObjectEditView):
queryset = Provider.objects.all()
@ -47,11 +49,13 @@ class ProviderDeleteView(generic.ObjectDeleteView):
queryset = Provider.objects.all()
@register_model_view(Provider, 'import', detail=False)
class ProviderBulkImportView(generic.BulkImportView):
queryset = Provider.objects.all()
model_form = forms.ProviderImportForm
@register_model_view(Provider, 'bulk_edit', path='edit', detail=False)
class ProviderBulkEditView(generic.BulkEditView):
queryset = Provider.objects.annotate(
count_circuits=count_related(Circuit, 'provider')
@ -61,6 +65,7 @@ class ProviderBulkEditView(generic.BulkEditView):
form = forms.ProviderBulkEditForm
@register_model_view(Provider, 'bulk_delete', path='delete', detail=False)
class ProviderBulkDeleteView(generic.BulkDeleteView):
queryset = Provider.objects.annotate(
count_circuits=count_related(Circuit, 'provider')
@ -78,6 +83,7 @@ class ProviderContactsView(ObjectContactsView):
# ProviderAccounts
#
@register_model_view(ProviderAccount, 'list', path='', detail=False)
class ProviderAccountListView(generic.ObjectListView):
queryset = ProviderAccount.objects.annotate(
count_circuits=count_related(Circuit, 'provider_account')
@ -97,6 +103,7 @@ class ProviderAccountView(GetRelatedModelsMixin, generic.ObjectView):
}
@register_model_view(ProviderAccount, 'add', detail=False)
@register_model_view(ProviderAccount, 'edit')
class ProviderAccountEditView(generic.ObjectEditView):
queryset = ProviderAccount.objects.all()
@ -108,12 +115,14 @@ class ProviderAccountDeleteView(generic.ObjectDeleteView):
queryset = ProviderAccount.objects.all()
@register_model_view(ProviderAccount, 'import', detail=False)
class ProviderAccountBulkImportView(generic.BulkImportView):
queryset = ProviderAccount.objects.all()
model_form = forms.ProviderAccountImportForm
table = tables.ProviderAccountTable
@register_model_view(ProviderAccount, 'bulk_edit', path='edit', detail=False)
class ProviderAccountBulkEditView(generic.BulkEditView):
queryset = ProviderAccount.objects.annotate(
count_circuits=count_related(Circuit, 'provider_account')
@ -123,6 +132,7 @@ class ProviderAccountBulkEditView(generic.BulkEditView):
form = forms.ProviderAccountBulkEditForm
@register_model_view(ProviderAccount, 'bulk_delete', path='delete', detail=False)
class ProviderAccountBulkDeleteView(generic.BulkDeleteView):
queryset = ProviderAccount.objects.annotate(
count_circuits=count_related(Circuit, 'provider_account')
@ -140,6 +150,7 @@ class ProviderAccountContactsView(ObjectContactsView):
# Provider networks
#
@register_model_view(ProviderNetwork, 'list', path='', detail=False)
class ProviderNetworkListView(generic.ObjectListView):
queryset = ProviderNetwork.objects.all()
filterset = filtersets.ProviderNetworkFilterSet
@ -166,6 +177,7 @@ class ProviderNetworkView(GetRelatedModelsMixin, generic.ObjectView):
}
@register_model_view(ProviderNetwork, 'add', detail=False)
@register_model_view(ProviderNetwork, 'edit')
class ProviderNetworkEditView(generic.ObjectEditView):
queryset = ProviderNetwork.objects.all()
@ -177,11 +189,13 @@ class ProviderNetworkDeleteView(generic.ObjectDeleteView):
queryset = ProviderNetwork.objects.all()
@register_model_view(ProviderNetwork, 'import', detail=False)
class ProviderNetworkBulkImportView(generic.BulkImportView):
queryset = ProviderNetwork.objects.all()
model_form = forms.ProviderNetworkImportForm
@register_model_view(ProviderNetwork, 'bulk_edit', path='edit', detail=False)
class ProviderNetworkBulkEditView(generic.BulkEditView):
queryset = ProviderNetwork.objects.all()
filterset = filtersets.ProviderNetworkFilterSet
@ -189,6 +203,7 @@ class ProviderNetworkBulkEditView(generic.BulkEditView):
form = forms.ProviderNetworkBulkEditForm
@register_model_view(ProviderNetwork, 'bulk_delete', path='delete', detail=False)
class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
queryset = ProviderNetwork.objects.all()
filterset = filtersets.ProviderNetworkFilterSet
@ -199,6 +214,7 @@ class ProviderNetworkBulkDeleteView(generic.BulkDeleteView):
# Circuit Types
#
@register_model_view(CircuitType, 'list', path='', detail=False)
class CircuitTypeListView(generic.ObjectListView):
queryset = CircuitType.objects.annotate(
circuit_count=count_related(Circuit, 'type')
@ -218,6 +234,7 @@ class CircuitTypeView(GetRelatedModelsMixin, generic.ObjectView):
}
@register_model_view(CircuitType, 'add', detail=False)
@register_model_view(CircuitType, 'edit')
class CircuitTypeEditView(generic.ObjectEditView):
queryset = CircuitType.objects.all()
@ -229,11 +246,13 @@ class CircuitTypeDeleteView(generic.ObjectDeleteView):
queryset = CircuitType.objects.all()
@register_model_view(CircuitType, 'import', detail=False)
class CircuitTypeBulkImportView(generic.BulkImportView):
queryset = CircuitType.objects.all()
model_form = forms.CircuitTypeImportForm
@register_model_view(CircuitType, 'bulk_edit', path='edit', detail=False)
class CircuitTypeBulkEditView(generic.BulkEditView):
queryset = CircuitType.objects.annotate(
circuit_count=count_related(Circuit, 'type')
@ -243,6 +262,7 @@ class CircuitTypeBulkEditView(generic.BulkEditView):
form = forms.CircuitTypeBulkEditForm
@register_model_view(CircuitType, 'bulk_delete', path='delete', detail=False)
class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
queryset = CircuitType.objects.annotate(
circuit_count=count_related(Circuit, 'type')
@ -255,6 +275,7 @@ class CircuitTypeBulkDeleteView(generic.BulkDeleteView):
# Circuits
#
@register_model_view(Circuit, 'list', path='', detail=False)
class CircuitListView(generic.ObjectListView):
queryset = Circuit.objects.prefetch_related(
'tenant__group', 'termination_a__termination', 'termination_z__termination',
@ -269,6 +290,7 @@ class CircuitView(generic.ObjectView):
queryset = Circuit.objects.all()
@register_model_view(Circuit, 'add', detail=False)
@register_model_view(Circuit, 'edit')
class CircuitEditView(generic.ObjectEditView):
queryset = Circuit.objects.all()
@ -280,6 +302,7 @@ class CircuitDeleteView(generic.ObjectDeleteView):
queryset = Circuit.objects.all()
@register_model_view(Circuit, 'import', detail=False)
class CircuitBulkImportView(generic.BulkImportView):
queryset = Circuit.objects.all()
model_form = forms.CircuitImportForm
@ -295,6 +318,7 @@ class CircuitBulkImportView(generic.BulkImportView):
return data
@register_model_view(Circuit, 'bulk_edit', path='edit', detail=False)
class CircuitBulkEditView(generic.BulkEditView):
queryset = Circuit.objects.prefetch_related(
'tenant__group', 'termination_a__termination', 'termination_z__termination',
@ -304,6 +328,7 @@ class CircuitBulkEditView(generic.BulkEditView):
form = forms.CircuitBulkEditForm
@register_model_view(Circuit, 'bulk_delete', path='delete', detail=False)
class CircuitBulkDeleteView(generic.BulkDeleteView):
queryset = Circuit.objects.prefetch_related(
'tenant__group', 'termination_a__termination', 'termination_z__termination',
@ -397,6 +422,7 @@ class CircuitContactsView(ObjectContactsView):
# Circuit terminations
#
@register_model_view(CircuitTermination, 'list', path='', detail=False)
class CircuitTerminationListView(generic.ObjectListView):
queryset = CircuitTermination.objects.all()
filterset = filtersets.CircuitTerminationFilterSet
@ -409,6 +435,7 @@ class CircuitTerminationView(generic.ObjectView):
queryset = CircuitTermination.objects.all()
@register_model_view(CircuitTermination, 'add', detail=False)
@register_model_view(CircuitTermination, 'edit')
class CircuitTerminationEditView(generic.ObjectEditView):
queryset = CircuitTermination.objects.all()
@ -420,11 +447,13 @@ class CircuitTerminationDeleteView(generic.ObjectDeleteView):
queryset = CircuitTermination.objects.all()
@register_model_view(CircuitTermination, 'import', detail=False)
class CircuitTerminationBulkImportView(generic.BulkImportView):
queryset = CircuitTermination.objects.all()
model_form = forms.CircuitTerminationImportForm
@register_model_view(CircuitTermination, 'bulk_edit', path='edit', detail=False)
class CircuitTerminationBulkEditView(generic.BulkEditView):
queryset = CircuitTermination.objects.all()
filterset = filtersets.CircuitTerminationFilterSet
@ -432,6 +461,7 @@ class CircuitTerminationBulkEditView(generic.BulkEditView):
form = forms.CircuitTerminationBulkEditForm
@register_model_view(CircuitTermination, 'bulk_delete', path='delete', detail=False)
class CircuitTerminationBulkDeleteView(generic.BulkDeleteView):
queryset = CircuitTermination.objects.all()
filterset = filtersets.CircuitTerminationFilterSet
@ -446,6 +476,7 @@ register_model_view(CircuitTermination, 'trace', kwargs={'model': CircuitTermina
# Circuit Groups
#
@register_model_view(CircuitGroup, 'list', path='', detail=False)
class CircuitGroupListView(generic.ObjectListView):
queryset = CircuitGroup.objects.annotate(
circuit_group_assignment_count=count_related(CircuitGroupAssignment, 'group')
@ -465,6 +496,7 @@ class CircuitGroupView(GetRelatedModelsMixin, generic.ObjectView):
}
@register_model_view(CircuitGroup, 'add', detail=False)
@register_model_view(CircuitGroup, 'edit')
class CircuitGroupEditView(generic.ObjectEditView):
queryset = CircuitGroup.objects.all()
@ -476,11 +508,13 @@ class CircuitGroupDeleteView(generic.ObjectDeleteView):
queryset = CircuitGroup.objects.all()
@register_model_view(CircuitGroup, 'import', detail=False)
class CircuitGroupBulkImportView(generic.BulkImportView):
queryset = CircuitGroup.objects.all()
model_form = forms.CircuitGroupImportForm
@register_model_view(CircuitGroup, 'bulk_edit', path='edit', detail=False)
class CircuitGroupBulkEditView(generic.BulkEditView):
queryset = CircuitGroup.objects.all()
filterset = filtersets.CircuitGroupFilterSet
@ -488,6 +522,7 @@ class CircuitGroupBulkEditView(generic.BulkEditView):
form = forms.CircuitGroupBulkEditForm
@register_model_view(CircuitGroup, 'bulk_delete', path='delete', detail=False)
class CircuitGroupBulkDeleteView(generic.BulkDeleteView):
queryset = CircuitGroup.objects.all()
filterset = filtersets.CircuitGroupFilterSet
@ -498,6 +533,7 @@ class CircuitGroupBulkDeleteView(generic.BulkDeleteView):
# Circuit Groups
#
@register_model_view(CircuitGroupAssignment, 'list', path='', detail=False)
class CircuitGroupAssignmentListView(generic.ObjectListView):
queryset = CircuitGroupAssignment.objects.all()
filterset = filtersets.CircuitGroupAssignmentFilterSet
@ -510,6 +546,7 @@ class CircuitGroupAssignmentView(generic.ObjectView):
queryset = CircuitGroupAssignment.objects.all()
@register_model_view(CircuitGroupAssignment, 'add', detail=False)
@register_model_view(CircuitGroupAssignment, 'edit')
class CircuitGroupAssignmentEditView(generic.ObjectEditView):
queryset = CircuitGroupAssignment.objects.all()
@ -521,11 +558,13 @@ class CircuitGroupAssignmentDeleteView(generic.ObjectDeleteView):
queryset = CircuitGroupAssignment.objects.all()
@register_model_view(CircuitGroupAssignment, 'import', detail=False)
class CircuitGroupAssignmentBulkImportView(generic.BulkImportView):
queryset = CircuitGroupAssignment.objects.all()
model_form = forms.CircuitGroupAssignmentImportForm
@register_model_view(CircuitGroupAssignment, 'bulk_edit', path='edit', detail=False)
class CircuitGroupAssignmentBulkEditView(generic.BulkEditView):
queryset = CircuitGroupAssignment.objects.all()
filterset = filtersets.CircuitGroupAssignmentFilterSet
@ -533,7 +572,114 @@ class CircuitGroupAssignmentBulkEditView(generic.BulkEditView):
form = forms.CircuitGroupAssignmentBulkEditForm
@register_model_view(CircuitGroupAssignment, 'bulk_delete', path='delete', detail=False)
class CircuitGroupAssignmentBulkDeleteView(generic.BulkDeleteView):
queryset = CircuitGroupAssignment.objects.all()
filterset = filtersets.CircuitGroupAssignmentFilterSet
table = tables.CircuitGroupAssignmentTable
#
# Virtual circuits
#
class VirtualCircuitListView(generic.ObjectListView):
queryset = VirtualCircuit.objects.annotate(
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
)
filterset = filtersets.VirtualCircuitFilterSet
filterset_form = forms.VirtualCircuitFilterForm
table = tables.VirtualCircuitTable
@register_model_view(VirtualCircuit)
class VirtualCircuitView(generic.ObjectView):
queryset = VirtualCircuit.objects.all()
@register_model_view(VirtualCircuit, 'edit')
class VirtualCircuitEditView(generic.ObjectEditView):
queryset = VirtualCircuit.objects.all()
form = forms.VirtualCircuitForm
@register_model_view(VirtualCircuit, 'delete')
class VirtualCircuitDeleteView(generic.ObjectDeleteView):
queryset = VirtualCircuit.objects.all()
class VirtualCircuitBulkImportView(generic.BulkImportView):
queryset = VirtualCircuit.objects.all()
model_form = forms.VirtualCircuitImportForm
additional_permissions = [
'circuits.add_virtualcircuittermination',
]
related_object_forms = {
'terminations': forms.VirtualCircuitTerminationImportRelatedForm,
}
def prep_related_object_data(self, parent, data):
data.update({'virtual_circuit': parent})
return data
class VirtualCircuitBulkEditView(generic.BulkEditView):
queryset = VirtualCircuit.objects.annotate(
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
)
filterset = filtersets.VirtualCircuitFilterSet
table = tables.VirtualCircuitTable
form = forms.VirtualCircuitBulkEditForm
class VirtualCircuitBulkDeleteView(generic.BulkDeleteView):
queryset = VirtualCircuit.objects.annotate(
termination_count=count_related(VirtualCircuitTermination, 'virtual_circuit')
)
filterset = filtersets.VirtualCircuitFilterSet
table = tables.VirtualCircuitTable
#
# Virtual circuit terminations
#
class VirtualCircuitTerminationListView(generic.ObjectListView):
queryset = VirtualCircuitTermination.objects.all()
filterset = filtersets.VirtualCircuitTerminationFilterSet
filterset_form = forms.VirtualCircuitTerminationFilterForm
table = tables.VirtualCircuitTerminationTable
@register_model_view(VirtualCircuitTermination)
class VirtualCircuitTerminationView(generic.ObjectView):
queryset = VirtualCircuitTermination.objects.all()
@register_model_view(VirtualCircuitTermination, 'edit')
class VirtualCircuitTerminationEditView(generic.ObjectEditView):
queryset = VirtualCircuitTermination.objects.all()
form = forms.VirtualCircuitTerminationForm
@register_model_view(VirtualCircuitTermination, 'delete')
class VirtualCircuitTerminationDeleteView(generic.ObjectDeleteView):
queryset = VirtualCircuitTermination.objects.all()
class VirtualCircuitTerminationBulkImportView(generic.BulkImportView):
queryset = VirtualCircuitTermination.objects.all()
model_form = forms.VirtualCircuitTerminationImportForm
class VirtualCircuitTerminationBulkEditView(generic.BulkEditView):
queryset = VirtualCircuitTermination.objects.all()
filterset = filtersets.VirtualCircuitTerminationFilterSet
table = tables.VirtualCircuitTerminationTable
form = forms.VirtualCircuitTerminationBulkEditForm
class VirtualCircuitTerminationBulkDeleteView(generic.BulkDeleteView):
queryset = VirtualCircuitTermination.objects.all()
filterset = filtersets.VirtualCircuitTerminationFilterSet
table = tables.VirtualCircuitTerminationTable

View File

@ -35,7 +35,10 @@ class ChoiceFieldFix(OpenApiSerializerFieldExtension):
elif direction == "response":
value = build_cf
label = {**build_basic_type(OpenApiTypes.STR), "enum": list(OrderedDict.fromkeys(self.target.choices.values()))}
label = {
**build_basic_type(OpenApiTypes.STR),
"enum": list(OrderedDict.fromkeys(self.target.choices.values()))
}
return build_object_type(
properties={

View File

@ -22,7 +22,7 @@ class JobSerializer(BaseModelSerializer):
class Meta:
model = Job
fields = [
'id', 'url', 'display_url', 'display', 'object_type', 'object_id', 'name', 'status', 'created', 'scheduled', 'interval',
'started', 'completed', 'user', 'data', 'error', 'job_id',
'id', 'url', 'display_url', 'display', 'object_type', 'object_id', 'name', 'status', 'created', 'scheduled',
'interval', 'started', 'completed', 'user', 'data', 'error', 'job_id',
]
brief_fields = ('url', 'created', 'completed', 'user', 'status')

View File

@ -8,13 +8,12 @@ import utilities.json
class Migration(migrations.Migration):
replaces = [
('core', '0001_initial'),
('core', '0002_managedfile'),
('core', '0003_job'),
('core', '0004_replicate_jobresults'),
('core', '0005_job_created_auto_now')
('core', '0005_job_created_auto_now'),
]
dependencies = [
@ -30,7 +29,10 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('description', models.CharField(blank=True, max_length=200)),
('comments', models.TextField(blank=True)),
('name', models.CharField(max_length=100, unique=True)),
@ -55,9 +57,28 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(editable=False)),
('path', models.CharField(editable=False, max_length=1000)),
('size', models.PositiveIntegerField(editable=False)),
('hash', models.CharField(editable=False, max_length=64, validators=[django.core.validators.RegexValidator(message='Length must be 64 hexadecimal characters.', regex='^[0-9a-f]{64}$')])),
(
'hash',
models.CharField(
editable=False,
max_length=64,
validators=[
django.core.validators.RegexValidator(
message='Length must be 64 hexadecimal characters.', regex='^[0-9a-f]{64}$'
)
],
),
),
('data', models.BinaryField()),
('source', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='datafiles', to='core.datasource')),
(
'source',
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.CASCADE,
related_name='datafiles',
to='core.datasource',
),
),
],
options={
'ordering': ('source', 'path'),
@ -76,8 +97,18 @@ class Migration(migrations.Migration):
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('object_id', models.PositiveBigIntegerField()),
('datafile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='core.datafile')),
('object_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype')),
(
'datafile',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='+', to='core.datafile'
),
),
(
'object_type',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype'
),
),
],
options={
'indexes': [models.Index(fields=['object_type', 'object_id'], name='core_autosy_object__c17bac_idx')],
@ -97,8 +128,26 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(blank=True, editable=False, null=True)),
('file_root', models.CharField(max_length=1000)),
('file_path', models.FilePathField(editable=False)),
('data_file', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='core.datafile')),
('data_source', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='core.datasource')),
(
'data_file',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='core.datafile',
),
),
(
'data_source',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='core.datasource',
),
),
('auto_sync_enabled', models.BooleanField(default=False)),
],
options={
@ -108,7 +157,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='managedfile',
constraint=models.UniqueConstraint(fields=('file_root', 'file_path'), name='core_managedfile_unique_root_path'),
constraint=models.UniqueConstraint(
fields=('file_root', 'file_path'), name='core_managedfile_unique_root_path'
),
),
migrations.CreateModel(
name='Job',
@ -118,14 +169,33 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=200)),
('created', models.DateTimeField()),
('scheduled', models.DateTimeField(blank=True, null=True)),
('interval', models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])),
(
'interval',
models.PositiveIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
('started', models.DateTimeField(blank=True, null=True)),
('completed', models.DateTimeField(blank=True, null=True)),
('status', models.CharField(default='pending', max_length=30)),
('data', models.JSONField(blank=True, null=True)),
('job_id', models.UUIDField(unique=True)),
('object_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='contenttypes.contenttype')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
(
'object_type',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='contenttypes.contenttype'
),
),
(
'user',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to=settings.AUTH_USER_MODEL,
),
),
],
options={
'ordering': ['-created'],

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_job_created_auto_now'),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_datasource_type_remove_choices'),
]

View File

@ -3,7 +3,6 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('core', '0007_job_add_error_field'),
@ -12,8 +11,7 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='ObjectType',
fields=[
],
fields=[],
options={
'proxy': True,
'indexes': [],

View File

@ -2,7 +2,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_contenttype_proxy'),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_configrevision'),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('core', '0010_gfk_indexes'),
@ -27,15 +26,49 @@ class Migration(migrations.Migration):
('object_repr', models.CharField(editable=False, max_length=200)),
('prechange_data', models.JSONField(blank=True, editable=False, null=True)),
('postchange_data', models.JSONField(blank=True, editable=False, null=True)),
('changed_object_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
('related_object_type', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='changes', to=settings.AUTH_USER_MODEL)),
(
'changed_object_type',
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
(
'related_object_type',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
(
'user',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='changes',
to=settings.AUTH_USER_MODEL,
),
),
],
options={
'verbose_name': 'object change',
'verbose_name_plural': 'object changes',
'ordering': ['-time'],
'indexes': [models.Index(fields=['changed_object_type', 'changed_object_id'], name='core_object_changed_c227ce_idx'), models.Index(fields=['related_object_type', 'related_object_id'], name='core_object_related_3375d6_idx')],
'indexes': [
models.Index(
fields=['changed_object_type', 'changed_object_id'],
name='core_object_changed_c227ce_idx',
),
models.Index(
fields=['related_object_type', 'related_object_id'],
name='core_object_related_3375d6_idx',
),
],
},
),
],

View File

@ -3,7 +3,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('core', '0011_move_objectchange'),
@ -18,7 +17,7 @@ class Migration(migrations.Migration):
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='jobs',
to='contenttypes.contenttype'
to='contenttypes.contenttype',
),
),
]

View File

@ -93,9 +93,14 @@ class ManagedFile(SyncedDataMixin, models.Model):
self.file_path = os.path.basename(self.data_path)
# Ensure that the file root and path make a unique pair
if self._meta.model.objects.filter(file_root=self.file_root, file_path=self.file_path).exclude(pk=self.pk).exists():
if self._meta.model.objects.filter(
file_root=self.file_root, file_path=self.file_path
).exclude(pk=self.pk).exists():
raise ValidationError(
f"A {self._meta.verbose_name.lower()} with this file path already exists ({self.file_root}/{self.file_path}).")
_("A {model} with this file path already exists ({path}).").format(
model=self._meta.verbose_name.lower(),
path=f"{self.file_root}/{self.file_path}"
))
def delete(self, *args, **kwargs):
# Delete file from disk

View File

@ -9,6 +9,7 @@ from django.db import models
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import gettext as _
from rq.exceptions import InvalidJobOperation
from core.choices import JobStatusChoices
from core.models import ObjectType
@ -158,7 +159,11 @@ class Job(models.Model):
job = queue.fetch_job(str(self.job_id))
if job:
job.cancel()
try:
job.cancel()
except InvalidJobOperation:
# Job may raise this exception from get_status() if missing from Redis
pass
def start(self):
"""
@ -198,7 +203,17 @@ class Job(models.Model):
job_end.send(self)
@classmethod
def enqueue(cls, func, instance=None, name='', user=None, schedule_at=None, interval=None, immediate=False, **kwargs):
def enqueue(
cls,
func,
instance=None,
name='',
user=None,
schedule_at=None,
interval=None,
immediate=False,
**kwargs
):
"""
Create a Job instance and enqueue a job using the given callable

View File

@ -308,6 +308,7 @@ class BackgroundTaskTestCase(TestCase):
worker = get_worker('default')
job = queue.enqueue(self.dummy_job_default)
worker.prepare_job_execution(job)
worker.prepare_execution(job)
self.assertEqual(job.get_status(), JobStatus.STARTED)
@ -345,3 +346,32 @@ class BackgroundTaskTestCase(TestCase):
self.assertIn(str(worker1.name), str(response.content))
self.assertIn('Birth', str(response.content))
self.assertIn('Total working time', str(response.content))
class SystemTestCase(TestCase):
def setUp(self):
super().setUp()
self.user.is_staff = True
self.user.save()
def test_system_view_default(self):
# Test UI render
response = self.client.get(reverse('core:system'))
self.assertEqual(response.status_code, 200)
# Test export
response = self.client.get(f"{reverse('core:system')}?export=true")
self.assertEqual(response.status_code, 200)
def test_system_view_with_config_revision(self):
ConfigRevision.objects.create()
# Test UI render
response = self.client.get(reverse('core:system'))
self.assertEqual(response.status_code, 200)
# Test export
response = self.client.get(f"{reverse('core:system')}?export=true")
self.assertEqual(response.status_code, 200)

View File

@ -6,51 +6,50 @@ from . import views
app_name = 'core'
urlpatterns = (
# Data sources
path('data-sources/', views.DataSourceListView.as_view(), name='datasource_list'),
path('data-sources/add/', views.DataSourceEditView.as_view(), name='datasource_add'),
path('data-sources/import/', views.DataSourceBulkImportView.as_view(), name='datasource_import'),
path('data-sources/edit/', views.DataSourceBulkEditView.as_view(), name='datasource_bulk_edit'),
path('data-sources/delete/', views.DataSourceBulkDeleteView.as_view(), name='datasource_bulk_delete'),
path('data-sources/', include(get_model_urls('core', 'datasource', detail=False))),
path('data-sources/<int:pk>/', include(get_model_urls('core', 'datasource'))),
# Data files
path('data-files/', views.DataFileListView.as_view(), name='datafile_list'),
path('data-files/delete/', views.DataFileBulkDeleteView.as_view(), name='datafile_bulk_delete'),
path('data-files/', include(get_model_urls('core', 'datafile', detail=False))),
path('data-files/<int:pk>/', include(get_model_urls('core', 'datafile'))),
# Job results
path('jobs/', views.JobListView.as_view(), name='job_list'),
path('jobs/delete/', views.JobBulkDeleteView.as_view(), name='job_bulk_delete'),
path('jobs/<int:pk>/', views.JobView.as_view(), name='job'),
path('jobs/<int:pk>/delete/', views.JobDeleteView.as_view(), name='job_delete'),
path('jobs/', include(get_model_urls('core', 'job', detail=False))),
path('jobs/<int:pk>/', include(get_model_urls('core', 'job'))),
# Change logging
path('changelog/', views.ObjectChangeListView.as_view(), name='objectchange_list'),
path('changelog/', include(get_model_urls('core', 'objectchange', detail=False))),
path('changelog/<int:pk>/', include(get_model_urls('core', 'objectchange'))),
# Background Tasks
path('background-queues/', views.BackgroundQueueListView.as_view(), name='background_queue_list'),
path('background-queues/<int:queue_index>/<str:status>/', views.BackgroundTaskListView.as_view(), name='background_task_list'),
path(
'background-queues/<int:queue_index>/<str:status>/',
views.BackgroundTaskListView.as_view(),
name='background_task_list'
),
path('background-tasks/<str:job_id>/', views.BackgroundTaskView.as_view(), name='background_task'),
path('background-tasks/<str:job_id>/delete/', views.BackgroundTaskDeleteView.as_view(), name='background_task_delete'),
path('background-tasks/<str:job_id>/requeue/', views.BackgroundTaskRequeueView.as_view(), name='background_task_requeue'),
path('background-tasks/<str:job_id>/enqueue/', views.BackgroundTaskEnqueueView.as_view(), name='background_task_enqueue'),
path(
'background-tasks/<str:job_id>/delete/',
views.BackgroundTaskDeleteView.as_view(),
name='background_task_delete'
),
path(
'background-tasks/<str:job_id>/requeue/',
views.BackgroundTaskRequeueView.as_view(),
name='background_task_requeue'
),
path(
'background-tasks/<str:job_id>/enqueue/',
views.BackgroundTaskEnqueueView.as_view(),
name='background_task_enqueue'
),
path('background-tasks/<str:job_id>/stop/', views.BackgroundTaskStopView.as_view(), name='background_task_stop'),
path('background-workers/<int:queue_index>/', views.WorkerListView.as_view(), name='worker_list'),
path('background-workers/<str:key>/', views.WorkerView.as_view(), name='worker'),
# Config revisions
path('config-revisions/', views.ConfigRevisionListView.as_view(), name='configrevision_list'),
path('config-revisions/add/', views.ConfigRevisionEditView.as_view(), name='configrevision_add'),
path('config-revisions/delete/', views.ConfigRevisionBulkDeleteView.as_view(), name='configrevision_bulk_delete'),
path('config-revisions/<int:pk>/restore/', views.ConfigRevisionRestoreView.as_view(), name='configrevision_restore'),
path('config-revisions/', include(get_model_urls('core', 'configrevision', detail=False))),
path('config-revisions/<int:pk>/', include(get_model_urls('core', 'configrevision'))),
# System
path('system/', views.SystemView.as_view(), name='system'),
# Plugins
path('plugins/', views.PluginListView.as_view(), name='plugin_list'),
path('plugins/<str:name>/', views.PluginView.as_view(), name='plugin'),
)

View File

@ -43,6 +43,7 @@ from .tables import CatalogPluginTable, PluginVersionTable
# Data sources
#
@register_model_view(DataSource, 'list', path='', detail=False)
class DataSourceListView(generic.ObjectListView):
queryset = DataSource.objects.annotate(
file_count=count_related(DataFile, 'source')
@ -89,6 +90,7 @@ class DataSourceSyncView(BaseObjectView):
return redirect(datasource.get_absolute_url())
@register_model_view(DataSource, 'add', detail=False)
@register_model_view(DataSource, 'edit')
class DataSourceEditView(generic.ObjectEditView):
queryset = DataSource.objects.all()
@ -100,11 +102,13 @@ class DataSourceDeleteView(generic.ObjectDeleteView):
queryset = DataSource.objects.all()
@register_model_view(DataSource, 'import', detail=False)
class DataSourceBulkImportView(generic.BulkImportView):
queryset = DataSource.objects.all()
model_form = forms.DataSourceImportForm
@register_model_view(DataSource, 'bulk_edit', path='edit', detail=False)
class DataSourceBulkEditView(generic.BulkEditView):
queryset = DataSource.objects.annotate(
count_files=count_related(DataFile, 'source')
@ -114,6 +118,7 @@ class DataSourceBulkEditView(generic.BulkEditView):
form = forms.DataSourceBulkEditForm
@register_model_view(DataSource, 'bulk_delete', path='delete', detail=False)
class DataSourceBulkDeleteView(generic.BulkDeleteView):
queryset = DataSource.objects.annotate(
count_files=count_related(DataFile, 'source')
@ -126,6 +131,7 @@ class DataSourceBulkDeleteView(generic.BulkDeleteView):
# Data files
#
@register_model_view(DataFile, 'list', path='', detail=False)
class DataFileListView(generic.ObjectListView):
queryset = DataFile.objects.defer('data')
filterset = filtersets.DataFileFilterSet
@ -146,6 +152,7 @@ class DataFileDeleteView(generic.ObjectDeleteView):
queryset = DataFile.objects.all()
@register_model_view(DataFile, 'bulk_delete', path='delete', detail=False)
class DataFileBulkDeleteView(generic.BulkDeleteView):
queryset = DataFile.objects.defer('data')
filterset = filtersets.DataFileFilterSet
@ -156,6 +163,7 @@ class DataFileBulkDeleteView(generic.BulkDeleteView):
# Jobs
#
@register_model_view(Job, 'list', path='', detail=False)
class JobListView(generic.ObjectListView):
queryset = Job.objects.all()
filterset = filtersets.JobFilterSet
@ -167,14 +175,17 @@ class JobListView(generic.ObjectListView):
}
@register_model_view(Job)
class JobView(generic.ObjectView):
queryset = Job.objects.all()
@register_model_view(Job, 'delete')
class JobDeleteView(generic.ObjectDeleteView):
queryset = Job.objects.all()
@register_model_view(Job, 'bulk_delete', path='delete', detail=False)
class JobBulkDeleteView(generic.BulkDeleteView):
queryset = Job.objects.all()
filterset = filtersets.JobFilterSet
@ -185,6 +196,7 @@ class JobBulkDeleteView(generic.BulkDeleteView):
# Change logging
#
@register_model_view(ObjectChange, 'list', path='', detail=False)
class ObjectChangeListView(generic.ObjectListView):
queryset = ObjectChange.objects.valid_models()
filterset = filtersets.ObjectChangeFilterSet
@ -254,6 +266,7 @@ class ObjectChangeView(generic.ObjectView):
# Config Revisions
#
@register_model_view(ConfigRevision, 'list', path='', detail=False)
class ConfigRevisionListView(generic.ObjectListView):
queryset = ConfigRevision.objects.all()
filterset = filtersets.ConfigRevisionFilterSet
@ -266,6 +279,7 @@ class ConfigRevisionView(generic.ObjectView):
queryset = ConfigRevision.objects.all()
@register_model_view(ConfigRevision, 'add', detail=False)
class ConfigRevisionEditView(generic.ObjectEditView):
queryset = ConfigRevision.objects.all()
form = forms.ConfigRevisionForm
@ -276,12 +290,14 @@ class ConfigRevisionDeleteView(generic.ObjectDeleteView):
queryset = ConfigRevision.objects.all()
@register_model_view(ConfigRevision, 'bulk_delete', path='delete', detail=False)
class ConfigRevisionBulkDeleteView(generic.BulkDeleteView):
queryset = ConfigRevision.objects.all()
filterset = filtersets.ConfigRevisionFilterSet
table = tables.ConfigRevisionTable
@register_model_view(ConfigRevision, 'restore')
class ConfigRevisionRestoreView(ContentTypePermissionRequiredMixin, View):
def get_required_permission(self):
@ -536,11 +552,7 @@ class SystemView(UserPassesTestMixin, View):
}
# Configuration
try:
config = ConfigRevision.objects.get(pk=cache.get('config_version'))
except ConfigRevision.DoesNotExist:
# Fall back to using the active config data if no record is found
config = get_config()
config = get_config()
# Raw data export
if 'export' in request.GET:

View File

@ -21,7 +21,7 @@ from wireless.choices import *
from wireless.models import WirelessLAN
from .base import ConnectedEndpointsSerializer
from .cables import CabledObjectSerializer
from .devices import DeviceSerializer, ModuleSerializer, VirtualDeviceContextSerializer
from .devices import DeviceSerializer, MACAddressSerializer, ModuleSerializer, VirtualDeviceContextSerializer
from .manufacturers import ManufacturerSerializer
from .nested import NestedInterfaceSerializer
from .roles import InventoryItemRoleSerializer
@ -210,24 +210,23 @@ class InterfaceSerializer(NetBoxModelSerializer, CabledObjectSerializer, Connect
)
count_ipaddresses = serializers.IntegerField(read_only=True)
count_fhrp_groups = serializers.IntegerField(read_only=True)
mac_address = serializers.CharField(
required=False,
default=None,
allow_blank=True,
allow_null=True
)
# Maintains backward compatibility with NetBox <v4.2
mac_address = serializers.CharField(allow_null=True, read_only=True)
primary_mac_address = MACAddressSerializer(nested=True, required=False, allow_null=True)
mac_addresses = MACAddressSerializer(many=True, nested=True, read_only=True, allow_null=True)
wwn = serializers.CharField(required=False, default=None, allow_blank=True, allow_null=True)
class Meta:
model = Interface
fields = [
'id', 'url', 'display_url', 'display', 'device', 'vdcs', 'module', 'name', 'label', 'type', 'enabled',
'parent', 'bridge', 'lag', 'mtu', 'mac_address', 'speed', 'duplex', 'wwn', 'mgmt_only', 'description',
'mode', 'rf_role', 'rf_channel', 'poe_mode', 'poe_type', 'rf_channel_frequency', 'rf_channel_width',
'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'mark_connected',
'cable', 'cable_end', 'wireless_link', 'link_peers', 'link_peers_type', 'wireless_lans', 'vrf',
'l2vpn_termination', 'connected_endpoints', 'connected_endpoints_type', 'connected_endpoints_reachable',
'tags', 'custom_fields', 'created', 'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
'parent', 'bridge', 'lag', 'mtu', 'mac_address', 'primary_mac_address', 'mac_addresses', 'speed', 'duplex',
'wwn', 'mgmt_only', 'description', 'mode', 'rf_role', 'rf_channel', 'poe_mode', 'poe_type',
'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan',
'vlan_translation_policy', 'mark_connected', 'cable', 'cable_end', 'wireless_link', 'link_peers',
'link_peers_type', 'wireless_lans', 'vrf', 'l2vpn_termination', 'connected_endpoints',
'connected_endpoints_type', 'connected_endpoints_reachable', 'tags', 'custom_fields', 'created',
'last_updated', 'count_ipaddresses', 'count_fhrp_groups', '_occupied',
]
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', 'cable', '_occupied')
@ -352,9 +351,9 @@ class InventoryItemSerializer(NetBoxModelSerializer):
class Meta:
model = InventoryItem
fields = [
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role', 'manufacturer',
'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type', 'component_id',
'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
'id', 'url', 'display_url', 'display', 'device', 'parent', 'name', 'label', 'status', 'role',
'manufacturer', 'part_id', 'serial', 'asset_tag', 'discovered', 'description', 'component_type',
'component_id', 'component', 'tags', 'custom_fields', 'created', 'last_updated', '_depth',
]
brief_fields = ('id', 'url', 'display', 'device', 'name', 'description', '_depth')

View File

@ -1,16 +1,19 @@
import decimal
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from drf_spectacular.utils import extend_schema_field
from rest_framework import serializers
from dcim.choices import *
from dcim.models import Device, DeviceBay, Module, VirtualDeviceContext
from dcim.constants import MACADDRESS_ASSIGNMENT_MODELS
from dcim.models import Device, DeviceBay, MACAddress, Module, VirtualDeviceContext
from extras.api.serializers_.configtemplates import ConfigTemplateSerializer
from ipam.api.serializers_.ip import IPAddressSerializer
from netbox.api.fields import ChoiceField, RelatedObjectCountField
from netbox.api.fields import ChoiceField, ContentTypeField, RelatedObjectCountField
from netbox.api.serializers import NetBoxModelSerializer
from tenancy.api.serializers_.tenants import TenantSerializer
from utilities.api import get_serializer_for_model
from virtualization.api.serializers_.clusters import ClusterSerializer
from .devicetypes import *
from .platforms import PlatformSerializer
@ -23,6 +26,7 @@ from .virtualchassis import VirtualChassisSerializer
__all__ = (
'DeviceSerializer',
'DeviceWithConfigContextSerializer',
'MACAddressSerializer',
'ModuleSerializer',
'VirtualDeviceContextSerializer',
)
@ -153,3 +157,28 @@ class ModuleSerializer(NetBoxModelSerializer):
'asset_tag', 'description', 'comments', 'tags', 'custom_fields', 'created', 'last_updated',
]
brief_fields = ('id', 'url', 'display', 'device', 'module_bay', 'module_type', 'description')
class MACAddressSerializer(NetBoxModelSerializer):
assigned_object_type = ContentTypeField(
queryset=ContentType.objects.filter(MACADDRESS_ASSIGNMENT_MODELS),
required=False,
allow_null=True
)
assigned_object = serializers.SerializerMethodField(read_only=True)
class Meta:
model = MACAddress
fields = [
'id', 'url', 'display_url', 'display', 'mac_address', 'assigned_object_type', 'assigned_object',
'description', 'comments',
]
brief_fields = ('id', 'url', 'display', 'mac_address', 'description')
@extend_schema_field(serializers.JSONField(allow_null=True))
def get_assigned_object(self, obj):
if obj.assigned_object is None:
return None
serializer = get_serializer_for_model(obj.assigned_object)
context = {'request': self.context['request']}
return serializer(obj.assigned_object, nested=True, context=context).data

View File

@ -56,6 +56,9 @@ router.register('inventory-items', views.InventoryItemViewSet)
# Device component roles
router.register('inventory-item-roles', views.InventoryItemRoleViewSet)
# Addressing
router.register('mac-addresses', views.MACAddressViewSet)
# Cables
router.register('cables', views.CableViewSet)
router.register('cable-terminations', views.CableTerminationViewSet)

View File

@ -499,6 +499,16 @@ class InventoryItemRoleViewSet(NetBoxModelViewSet):
filterset_class = filtersets.InventoryItemRoleFilterSet
#
# Addressing
#
class MACAddressViewSet(NetBoxModelViewSet):
queryset = MACAddress.objects.all()
serializer_class = serializers.MACAddressSerializer
filterset_class = filtersets.MACAddressFilterSet
#
# Cables
#

View File

@ -871,6 +871,7 @@ class InterfaceTypeChoices(ChoiceSet):
TYPE_100ME_T1 = '100base-t1'
TYPE_100ME_SFP = '100base-x-sfp'
TYPE_1GE_FIXED = '1000base-t'
TYPE_1GE_LX_FIXED = '1000base-lx'
TYPE_1GE_TX_FIXED = '1000base-tx'
TYPE_1GE_GBIC = '1000base-x-gbic'
TYPE_1GE_SFP = '1000base-x-sfp'
@ -1033,6 +1034,7 @@ class InterfaceTypeChoices(ChoiceSet):
(TYPE_100ME_FIXED, '100BASE-TX (10/100ME)'),
(TYPE_100ME_T1, '100BASE-T1 (10/100ME Single Pair)'),
(TYPE_1GE_FIXED, '1000BASE-T (1GE)'),
(TYPE_1GE_LX_FIXED, '1000BASE-LX (1GE)'),
(TYPE_1GE_TX_FIXED, '1000BASE-TX (1GE)'),
(TYPE_2GE_FIXED, '2.5GBASE-T (2.5GE)'),
(TYPE_5GE_FIXED, '5GBASE-T (5GE)'),

View File

@ -128,3 +128,13 @@ COMPATIBLE_TERMINATION_TYPES = {
LOCATION_SCOPE_TYPES = (
'region', 'sitegroup', 'site', 'location',
)
#
# MAC addresses
#
MACADDRESS_ASSIGNMENT_MODELS = Q(
Q(app_label='dcim', model='interface') |
Q(app_label='virtualization', model='vminterface')
)

View File

@ -4,7 +4,7 @@ from django.utils.translation import gettext as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import extend_schema_field
from circuits.models import CircuitTermination
from circuits.models import CircuitTermination, VirtualCircuit, VirtualCircuitTermination
from extras.filtersets import LocalConfigContextFilterSet
from extras.models import ConfigTemplate
from ipam.filtersets import PrimaryIPFilterSet
@ -20,7 +20,7 @@ from utilities.filters import (
ContentTypeFilter, MultiValueCharFilter, MultiValueMACAddressFilter, MultiValueNumberFilter, MultiValueWWNFilter,
NumericArrayFilter, TreeNodeMultipleChoiceFilter,
)
from virtualization.models import Cluster, ClusterGroup
from virtualization.models import Cluster, ClusterGroup, VMInterface, VirtualMachine
from vpn.models import L2VPN
from wireless.choices import WirelessRoleChoices, WirelessChannelChoices
from wireless.models import WirelessLAN, WirelessLink
@ -52,6 +52,7 @@ __all__ = (
'InventoryItemRoleFilterSet',
'InventoryItemTemplateFilterSet',
'LocationFilterSet',
'MACAddressFilterSet',
'ManufacturerFilterSet',
'ModuleBayFilterSet',
'ModuleBayTemplateFilterSet',
@ -311,8 +312,8 @@ class RackTypeFilterSet(NetBoxModelFilterSet):
class Meta:
model = RackType
fields = (
'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth', 'outer_unit',
'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
'id', 'model', 'slug', 'u_height', 'starting_unit', 'desc_units', 'outer_width', 'outer_depth',
'outer_unit', 'mounting_depth', 'weight', 'max_weight', 'weight_unit', 'description',
)
def search(self, queryset, name, value):
@ -1099,7 +1100,7 @@ class DeviceFilterSet(
label=_('Is full depth'),
)
mac_address = MultiValueMACAddressFilter(
field_name='interfaces__mac_address',
field_name='interfaces__mac_addresses__mac_address',
label=_('MAC address'),
)
serial = MultiValueCharFilter(
@ -1598,6 +1599,87 @@ class PowerOutletFilterSet(
)
class MACAddressFilterSet(NetBoxModelFilterSet):
mac_address = MultiValueMACAddressFilter()
device = MultiValueCharFilter(
method='filter_device',
field_name='name',
label=_('Device (name)'),
)
device_id = MultiValueNumberFilter(
method='filter_device',
field_name='pk',
label=_('Device (ID)'),
)
virtual_machine = MultiValueCharFilter(
method='filter_virtual_machine',
field_name='name',
label=_('Virtual machine (name)'),
)
virtual_machine_id = MultiValueNumberFilter(
method='filter_virtual_machine',
field_name='pk',
label=_('Virtual machine (ID)'),
)
interface = django_filters.ModelMultipleChoiceFilter(
field_name='interface__name',
queryset=Interface.objects.all(),
to_field_name='name',
label=_('Interface (name)'),
)
interface_id = django_filters.ModelMultipleChoiceFilter(
field_name='interface',
queryset=Interface.objects.all(),
label=_('Interface (ID)'),
)
vminterface = django_filters.ModelMultipleChoiceFilter(
field_name='vminterface__name',
queryset=VMInterface.objects.all(),
to_field_name='name',
label=_('VM interface (name)'),
)
vminterface_id = django_filters.ModelMultipleChoiceFilter(
field_name='vminterface',
queryset=VMInterface.objects.all(),
label=_('VM interface (ID)'),
)
class Meta:
model = MACAddress
fields = ('id', 'description', 'assigned_object_type', 'assigned_object_id')
def search(self, queryset, name, value):
if not value.strip():
return queryset
qs_filter = (
Q(mac_address__icontains=value) |
Q(description__icontains=value)
)
return queryset.filter(qs_filter)
def filter_device(self, queryset, name, value):
devices = Device.objects.filter(**{f'{name}__in': value})
if not devices.exists():
return queryset.none()
interface_ids = []
for device in devices:
interface_ids.extend(device.vc_interfaces().values_list('id', flat=True))
return queryset.filter(
interface__in=interface_ids
)
def filter_virtual_machine(self, queryset, name, value):
virtual_machines = VirtualMachine.objects.filter(**{f'{name}__in': value})
if not virtual_machines.exists():
return queryset.none()
interface_ids = []
for vm in virtual_machines:
interface_ids.extend(vm.interfaces.values_list('id', flat=True))
return queryset.filter(
vminterface__in=interface_ids
)
class CommonInterfaceFilterSet(django_filters.FilterSet):
vlan_id = django_filters.CharFilter(
method='filter_vlan_id',
@ -1702,7 +1784,21 @@ class InterfaceFilterSet(
duplex = django_filters.MultipleChoiceFilter(
choices=InterfaceDuplexChoices
)
mac_address = MultiValueMACAddressFilter()
mac_address = MultiValueMACAddressFilter(
field_name='mac_addresses__mac_address',
label=_('MAC Address')
)
primary_mac_address_id = django_filters.ModelMultipleChoiceFilter(
field_name='primary_mac_address',
queryset=MACAddress.objects.all(),
label=_('Primary MAC address (ID)'),
)
primary_mac_address = django_filters.ModelMultipleChoiceFilter(
field_name='primary_mac_address__mac_address',
queryset=MACAddress.objects.all(),
to_field_name='mac_address',
label=_('Primary MAC address'),
)
wwn = MultiValueWWNFilter()
poe_mode = django_filters.MultipleChoiceFilter(
choices=InterfacePoEModeChoices
@ -1746,6 +1842,16 @@ class InterfaceFilterSet(
queryset=WirelessLink.objects.all(),
label=_('Wireless link')
)
virtual_circuit_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_circuit_termination__virtual_circuit',
queryset=VirtualCircuit.objects.all(),
label=_('Virtual circuit (ID)'),
)
virtual_circuit_termination_id = django_filters.ModelMultipleChoiceFilter(
field_name='virtual_circuit_termination',
queryset=VirtualCircuitTermination.objects.all(),
label=_('Virtual circuit termination (ID)'),
)
class Meta:
model = Interface

View File

@ -14,10 +14,11 @@ from tenancy.models import Tenant
from users.models import User
from utilities.forms import BulkEditForm, add_blank_choice, form_from_model
from utilities.forms.fields import ColorField, CommentField, DynamicModelChoiceField, DynamicModelMultipleChoiceField
from utilities.forms.rendering import FieldSet, InlineFields
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
from utilities.forms.widgets import BulkEditNullBooleanSelect, NumberWithOptions
from wireless.models import WirelessLAN, WirelessLANGroup
from virtualization.models import Cluster
from wireless.choices import WirelessRoleChoices
from wireless.models import WirelessLAN, WirelessLANGroup
__all__ = (
'CableBulkEditForm',
@ -38,6 +39,7 @@ __all__ = (
'InventoryItemRoleBulkEditForm',
'InventoryItemTemplateBulkEditForm',
'LocationBulkEditForm',
'MACAddressBulkEditForm',
'ManufacturerBulkEditForm',
'ModuleBulkEditForm',
'ModuleBayBulkEditForm',
@ -722,6 +724,14 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm):
queryset=ConfigTemplate.objects.all(),
required=False
)
cluster = DynamicModelChoiceField(
label=_('Cluster'),
queryset=Cluster.objects.all(),
required=False,
query_params={
'site_id': ['$site', 'null']
},
)
comments = CommentField()
model = Device
@ -730,9 +740,10 @@ class DeviceBulkEditForm(NetBoxModelBulkEditForm):
FieldSet('site', 'location', name=_('Location')),
FieldSet('manufacturer', 'device_type', 'airflow', 'serial', name=_('Hardware')),
FieldSet('config_template', name=_('Configuration')),
FieldSet('cluster', name=_('Virtualization')),
)
nullable_fields = (
'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'comments',
'location', 'tenant', 'platform', 'serial', 'airflow', 'description', 'cluster', 'comments',
)
@ -1392,9 +1403,9 @@ class PowerOutletBulkEditForm(
class InterfaceBulkEditForm(
ComponentBulkEditForm,
form_from_model(Interface, [
'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'mtu', 'mgmt_only',
'mark_connected', 'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
'tx_power', 'wireless_lans'
'label', 'type', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'mtu', 'mgmt_only', 'mark_connected',
'description', 'mode', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
'wireless_lans'
])
):
enabled = forms.NullBooleanField(
@ -1405,18 +1416,25 @@ class InterfaceBulkEditForm(
parent = DynamicModelChoiceField(
label=_('Parent'),
queryset=Interface.objects.all(),
required=False
required=False,
query_params={
'virtual_chassis_member_id': '$device',
}
)
bridge = DynamicModelChoiceField(
label=_('Bridge'),
queryset=Interface.objects.all(),
required=False
required=False,
query_params={
'virtual_chassis_member_id': '$device',
}
)
lag = DynamicModelChoiceField(
queryset=Interface.objects.all(),
required=False,
query_params={
'type': 'lag',
'virtual_chassis_member_id': '$device',
},
label=_('LAG')
)
@ -1473,6 +1491,7 @@ class InterfaceBulkEditForm(
required=False,
query_params={
'group_id': '$vlan_group',
'available_on_device': '$device',
},
label=_('Untagged VLAN')
)
@ -1481,9 +1500,28 @@ class InterfaceBulkEditForm(
required=False,
query_params={
'group_id': '$vlan_group',
'available_on_device': '$device',
},
label=_('Tagged VLANs')
)
add_tagged_vlans = DynamicModelMultipleChoiceField(
label=_('Add tagged VLANs'),
queryset=VLAN.objects.all(),
required=False,
query_params={
'group_id': '$vlan_group',
'available_on_device': '$device',
},
)
remove_tagged_vlans = DynamicModelMultipleChoiceField(
label=_('Remove tagged VLANs'),
queryset=VLAN.objects.all(),
required=False,
query_params={
'group_id': '$vlan_group',
'available_on_device': '$device',
}
)
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
@ -1506,37 +1544,31 @@ class InterfaceBulkEditForm(
model = Interface
fieldsets = (
FieldSet('module', 'type', 'label', 'speed', 'duplex', 'description'),
FieldSet('vrf', 'mac_address', 'wwn', name=_('Addressing')),
FieldSet('vrf', 'wwn', name=_('Addressing')),
FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', name=_('802.1Q Switching')),
FieldSet('mode', 'vlan_group', 'untagged_vlan', name=_('802.1Q Switching')),
FieldSet(
TabbedGroups(
FieldSet('tagged_vlans', name=_('Assignment')),
FieldSet('add_tagged_vlans', 'remove_tagged_vlans', name=_('Add/Remove')),
),
),
FieldSet(
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
name=_('Wireless')
),
)
nullable_fields = (
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'mac_address', 'wwn', 'vdcs', 'mtu',
'description', 'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
'tx_power', 'untagged_vlan', 'tagged_vlans', 'vrf', 'wireless_lans'
'module', 'label', 'parent', 'bridge', 'lag', 'speed', 'duplex', 'wwn', 'vdcs', 'mtu', 'description',
'poe_mode', 'poe_type', 'mode', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power',
'untagged_vlan', 'tagged_vlans', 'vrf', 'wireless_lans'
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.device_id:
device = Device.objects.filter(pk=self.device_id).first()
# Restrict parent/bridge/LAG interface assignment by device
self.fields['parent'].widget.add_query_param('virtual_chassis_member_id', device.pk)
self.fields['bridge'].widget.add_query_param('virtual_chassis_member_id', device.pk)
self.fields['lag'].widget.add_query_param('virtual_chassis_member_id', device.pk)
# Limit VLAN choices by device
self.fields['untagged_vlan'].widget.add_query_param('available_on_device', device.pk)
self.fields['tagged_vlans'].widget.add_query_param('available_on_device', device.pk)
else:
if not self.device_id:
# See #4523
if 'pk' in self.initial:
site = None
@ -1560,6 +1592,13 @@ class InterfaceBulkEditForm(
'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
)
self.fields['add_tagged_vlans'].widget.add_query_param(
'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
)
self.fields['remove_tagged_vlans'].widget.add_query_param(
'site_id', [site.pk, settings.FILTERS_NULL_CHOICE_VALUE]
)
self.fields['parent'].choices = ()
self.fields['parent'].widget.attrs['disabled'] = True
self.fields['bridge'].choices = ()
@ -1719,3 +1758,22 @@ class VirtualDeviceContextBulkEditForm(NetBoxModelBulkEditForm):
FieldSet('device', 'status', 'tenant'),
)
nullable_fields = ('device', 'tenant', )
#
# Addressing
#
class MACAddressBulkEditForm(NetBoxModelBulkEditForm):
description = forms.CharField(
label=_('Description'),
max_length=200,
required=False
)
comments = CommentField()
model = MACAddress
fieldsets = (
FieldSet('description'),
)
nullable_fields = ('description', 'comments')

View File

@ -17,7 +17,7 @@ from utilities.forms.fields import (
CSVChoiceField, CSVContentTypeField, CSVModelChoiceField, CSVModelMultipleChoiceField, CSVTypedChoiceField,
SlugField,
)
from virtualization.models import Cluster
from virtualization.models import Cluster, VMInterface, VirtualMachine
from wireless.choices import WirelessRoleChoices
from .common import ModuleCommonForm
@ -34,6 +34,7 @@ __all__ = (
'InventoryItemImportForm',
'InventoryItemRoleImportForm',
'LocationImportForm',
'MACAddressImportForm',
'ManufacturerImportForm',
'ModuleImportForm',
'ModuleBayImportForm',
@ -427,7 +428,10 @@ class ModuleTypeImportForm(NetBoxModelImportForm):
class Meta:
model = ModuleType
fields = ['manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'comments', 'tags']
fields = [
'manufacturer', 'model', 'part_number', 'description', 'airflow', 'weight', 'weight_unit', 'comments',
'tags',
]
class DeviceRoleImportForm(NetBoxModelImportForm):
@ -799,7 +803,10 @@ class PowerOutletImportForm(NetBoxModelImportForm):
class Meta:
model = PowerOutlet
fields = ('device', 'name', 'label', 'type', 'color', 'mark_connected', 'power_port', 'feed_leg', 'description', 'tags')
fields = (
'device', 'name', 'label', 'type', 'color', 'mark_connected', 'power_port', 'feed_leg', 'description',
'tags',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -906,7 +913,7 @@ class InterfaceImportForm(NetBoxModelImportForm):
model = Interface
fields = (
'device', 'name', 'label', 'parent', 'bridge', 'lag', 'type', 'speed', 'duplex', 'enabled',
'mark_connected', 'mac_address', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
'mark_connected', 'wwn', 'vdcs', 'mtu', 'mgmt_only', 'description', 'poe_mode', 'poe_type', 'mode',
'vrf', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'tags'
)
@ -1113,8 +1120,8 @@ class InventoryItemImportForm(NetBoxModelImportForm):
class Meta:
model = InventoryItem
fields = (
'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag', 'discovered',
'description', 'tags', 'component_type', 'component_name',
'device', 'name', 'label', 'status', 'role', 'manufacturer', 'parent', 'part_id', 'serial', 'asset_tag',
'discovered', 'description', 'tags', 'component_type', 'component_name',
)
def __init__(self, *args, **kwargs):
@ -1167,6 +1174,90 @@ class InventoryItemRoleImportForm(NetBoxModelImportForm):
fields = ('name', 'slug', 'color', 'description')
#
# Addressing
#
class MACAddressImportForm(NetBoxModelImportForm):
device = CSVModelChoiceField(
label=_('Device'),
queryset=Device.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent device of assigned interface (if any)')
)
virtual_machine = CSVModelChoiceField(
label=_('Virtual machine'),
queryset=VirtualMachine.objects.all(),
required=False,
to_field_name='name',
help_text=_('Parent VM of assigned interface (if any)')
)
interface = CSVModelChoiceField(
label=_('Interface'),
queryset=Interface.objects.none(), # Can also refer to VMInterface
required=False,
to_field_name='name',
help_text=_('Assigned interface')
)
is_primary = forms.BooleanField(
label=_('Is primary'),
help_text=_('Make this the primary MAC address for the assigned interface'),
required=False
)
class Meta:
model = MACAddress
fields = [
'mac_address', 'device', 'virtual_machine', 'interface', 'is_primary', 'description', 'comments', 'tags',
]
def __init__(self, data=None, *args, **kwargs):
super().__init__(data, *args, **kwargs)
if data:
# Limit interface queryset by assigned device
if data.get('device'):
self.fields['interface'].queryset = Interface.objects.filter(
**{f"device__{self.fields['device'].to_field_name}": data['device']}
)
# Limit interface queryset by assigned device
elif data.get('virtual_machine'):
self.fields['interface'].queryset = VMInterface.objects.filter(
**{f"virtual_machine__{self.fields['virtual_machine'].to_field_name}": data['virtual_machine']}
)
def clean(self):
super().clean()
device = self.cleaned_data.get('device')
virtual_machine = self.cleaned_data.get('virtual_machine')
interface = self.cleaned_data.get('interface')
# Validate interface assignment
if interface and not device and not virtual_machine:
raise forms.ValidationError({
"interface": _("Must specify the parent device or VM when assigning an interface")
})
def save(self, *args, **kwargs):
# Set interface assignment
if interface := self.cleaned_data.get('interface'):
self.instance.assigned_object = interface
instance = super().save(*args, **kwargs)
# Assign the MAC address as primary for its interface, if designated as such
if interface and self.cleaned_data['is_primary'] and self.instance.pk:
interface.primary_mac_address = self.instance
interface.save()
return instance
#
# Cables
#

View File

@ -3,7 +3,9 @@ from django.utils.translation import gettext_lazy as _
from dcim.choices import *
from dcim.constants import *
from dcim.models import MACAddress
from utilities.forms import get_field_value
from utilities.forms.fields import DynamicModelChoiceField
__all__ = (
'InterfaceCommonForm',
@ -12,17 +14,17 @@ __all__ = (
class InterfaceCommonForm(forms.Form):
mac_address = forms.CharField(
empty_value=None,
required=False,
label=_('MAC address')
)
mtu = forms.IntegerField(
required=False,
min_value=INTERFACE_MTU_MIN,
max_value=INTERFACE_MTU_MAX,
label=_('MTU')
)
primary_mac_address = DynamicModelChoiceField(
queryset=MACAddress.objects.all(),
label=_('Primary MAC address'),
required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@ -40,6 +42,10 @@ class InterfaceCommonForm(forms.Form):
if interface_mode != InterfaceModeChoices.MODE_Q_IN_Q:
del self.fields['qinq_svlan']
if self.instance and self.instance.pk:
filter_name = f'{self._meta.model._meta.model_name}_id'
self.fields['primary_mac_address'].widget.add_query_param(filter_name, self.instance.pk)
def clean(self):
super().clean()
@ -130,7 +136,10 @@ class ModuleCommonForm(forms.Form):
if len(module_bays) != template.name.count(MODULE_TOKEN):
raise forms.ValidationError(
_("Cannot install module with placeholder values in a module bay tree {level} in tree but {tokens} placeholders given.").format(
_(
"Cannot install module with placeholder values in a module bay tree {level} in tree "
"but {tokens} placeholders given."
).format(
level=len(module_bays), tokens=template.name.count(MODULE_TOKEN)
)
)

View File

@ -111,9 +111,15 @@ def get_cable_form(a_type, b_type):
if self.instance and self.instance.pk:
# Initialize A/B terminations when modifying an existing Cable instance
if a_type and self.instance.a_terminations and a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0]):
if (
a_type and self.instance.a_terminations and
a_ct == ContentType.objects.get_for_model(self.instance.a_terminations[0])
):
self.initial['a_terminations'] = self.instance.a_terminations
if b_type and self.instance.b_terminations and b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0]):
if (
b_type and self.instance.b_terminations and
b_ct == ContentType.objects.get_for_model(self.instance.b_terminations[0])
):
self.initial['b_terminations'] = self.instance.b_terminations
else:
# Need to clear terminations if swapped type - but need to do it only

View File

@ -15,7 +15,7 @@ from utilities.forms import BOOLEAN_WITH_BLANK_CHOICES, FilterForm, add_blank_ch
from utilities.forms.fields import ColorField, DynamicModelMultipleChoiceField, TagFilterField
from utilities.forms.rendering import FieldSet
from utilities.forms.widgets import NumberWithOptions
from virtualization.models import Cluster, ClusterGroup
from virtualization.models import Cluster, ClusterGroup, VirtualMachine
from vpn.models import L2VPN
from wireless.choices import *
@ -34,6 +34,7 @@ __all__ = (
'InventoryItemFilterForm',
'InventoryItemRoleFilterForm',
'LocationFilterForm',
'MACAddressFilterForm',
'ManufacturerFilterForm',
'ModuleFilterForm',
'ModuleBayFilterForm',
@ -1574,6 +1575,34 @@ class InventoryItemRoleFilterForm(NetBoxModelFilterSetForm):
tag = TagFilterField(model)
#
# Addressing
#
class MACAddressFilterForm(NetBoxModelFilterSetForm):
model = MACAddress
fieldsets = (
FieldSet('q', 'filter_id', 'tag'),
FieldSet('mac_address', 'device_id', 'virtual_machine_id', name=_('MAC address')),
)
selector_fields = ('filter_id', 'q', 'device_id', 'virtual_machine_id')
mac_address = forms.CharField(
required=False,
label=_('MAC address')
)
device_id = DynamicModelMultipleChoiceField(
queryset=Device.objects.all(),
required=False,
label=_('Assigned Device'),
)
virtual_machine_id = DynamicModelMultipleChoiceField(
queryset=VirtualMachine.objects.all(),
required=False,
label=_('Assigned VM'),
)
tag = TagFilterField(model)
#
# Connections
#

View File

@ -18,7 +18,7 @@ from utilities.forms.fields import (
)
from utilities.forms.rendering import FieldSet, InlineFields, TabbedGroups
from utilities.forms.widgets import APISelect, ClearableFileInput, HTMXSelect, NumberWithOptions, SelectWithPK
from virtualization.models import Cluster
from virtualization.models import Cluster, VMInterface
from wireless.models import WirelessLAN, WirelessLANGroup
from .common import InterfaceCommonForm, ModuleCommonForm
@ -42,6 +42,7 @@ __all__ = (
'InventoryItemRoleForm',
'InventoryItemTemplateForm',
'LocationForm',
'MACAddressForm',
'ManufacturerForm',
'ModuleForm',
'ModuleBayForm',
@ -112,12 +113,14 @@ class SiteForm(TenancyForm, NetBoxModelForm):
region = DynamicModelChoiceField(
label=_('Region'),
queryset=Region.objects.all(),
required=False
required=False,
quick_add=True
)
group = DynamicModelChoiceField(
label=_('Group'),
queryset=SiteGroup.objects.all(),
required=False
required=False,
quick_add=True
)
asns = DynamicModelMultipleChoiceField(
queryset=ASN.objects.all(),
@ -206,7 +209,8 @@ class RackRoleForm(NetBoxModelForm):
class RackTypeForm(NetBoxModelForm):
manufacturer = DynamicModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all()
queryset=Manufacturer.objects.all(),
quick_add=True
)
comments = CommentField()
slug = SlugField(
@ -262,7 +266,10 @@ class RackForm(TenancyForm, NetBoxModelForm):
comments = CommentField()
fieldsets = (
FieldSet('site', 'location', 'name', 'status', 'role', 'rack_type', 'description', 'airflow', 'tags', name=_('Rack')),
FieldSet(
'site', 'location', 'name', 'status', 'role', 'rack_type', 'description', 'airflow', 'tags',
name=_('Rack')
),
FieldSet('facility_id', 'serial', 'asset_tag', name=_('Inventory Control')),
FieldSet('tenant_group', 'tenant', name=_('Tenancy')),
)
@ -348,7 +355,8 @@ class ManufacturerForm(NetBoxModelForm):
class DeviceTypeForm(NetBoxModelForm):
manufacturer = DynamicModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all()
queryset=Manufacturer.objects.all(),
quick_add=True
)
default_platform = DynamicModelChoiceField(
label=_('Default platform'),
@ -436,7 +444,8 @@ class PlatformForm(NetBoxModelForm):
manufacturer = DynamicModelChoiceField(
label=_('Manufacturer'),
queryset=Manufacturer.objects.all(),
required=False
required=False,
quick_add=True
)
config_template = DynamicModelChoiceField(
label=_('Config template'),
@ -508,7 +517,8 @@ class DeviceForm(TenancyForm, NetBoxModelForm):
)
role = DynamicModelChoiceField(
label=_('Device role'),
queryset=DeviceRole.objects.all()
queryset=DeviceRole.objects.all(),
quick_add=True
)
platform = DynamicModelChoiceField(
label=_('Platform'),
@ -750,7 +760,8 @@ class PowerFeedForm(TenancyForm, NetBoxModelForm):
power_panel = DynamicModelChoiceField(
label=_('Power panel'),
queryset=PowerPanel.objects.all(),
selector=True
selector=True,
quick_add=True
)
rack = DynamicModelChoiceField(
label=_('Rack'),
@ -910,6 +921,13 @@ class ModularComponentTemplateForm(ComponentTemplateForm):
if self.instance.pk:
self.fields['module_type'].disabled = True
# Components attached to a module need to present this standardized substitution help text.
self.fields['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>). The token <code>{module}</code>, if present, will be "
"automatically replaced with the position value when creating a new module."
)
class ConsolePortTemplateForm(ModularComponentTemplateForm):
fieldsets = (
@ -992,7 +1010,8 @@ class InterfaceTemplateForm(ModularComponentTemplateForm):
class Meta:
model = InterfaceTemplate
fields = [
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode', 'poe_type', 'bridge', 'rf_role',
'device_type', 'module_type', 'name', 'label', 'type', 'mgmt_only', 'enabled', 'description', 'poe_mode',
'poe_type', 'bridge', 'rf_role',
]
@ -1174,7 +1193,10 @@ class InventoryItemTemplateForm(ComponentTemplateForm):
break
elif component_type and component_id:
# When adding the InventoryItem from a component page
if content_type := ContentType.objects.filter(MODULAR_COMPONENT_TEMPLATE_MODELS).filter(pk=component_type).first():
content_type = ContentType.objects.filter(
MODULAR_COMPONENT_TEMPLATE_MODELS
).filter(pk=component_type).first()
if content_type:
if component := content_type.model_class().objects.filter(pk=component_id).first():
initial[content_type.model] = component
@ -1286,16 +1308,16 @@ class PowerOutletForm(ModularDeviceComponentForm):
fieldsets = (
FieldSet(
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description',
'tags',
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected',
'description', 'tags',
),
)
class Meta:
model = PowerOutlet
fields = [
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected', 'description',
'tags',
'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected',
'description', 'tags',
]
@ -1403,7 +1425,7 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
FieldSet(
'device', 'module', 'name', 'label', 'type', 'speed', 'duplex', 'description', 'tags', name=_('Interface')
),
FieldSet('vrf', 'mac_address', 'wwn', name=_('Addressing')),
FieldSet('vrf', 'primary_mac_address', 'wwn', name=_('Addressing')),
FieldSet('vdcs', 'mtu', 'tx_power', 'enabled', 'mgmt_only', 'mark_connected', name=_('Operation')),
FieldSet('parent', 'bridge', 'lag', name=_('Related Interfaces')),
FieldSet('poe_mode', 'poe_type', name=_('PoE')),
@ -1420,10 +1442,11 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
class Meta:
model = Interface
fields = [
'device', 'module', 'vdcs', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge', 'lag',
'mac_address', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode',
'device', 'module', 'vdcs', 'name', 'label', 'type', 'speed', 'duplex', 'enabled', 'parent', 'bridge',
'lag', 'wwn', 'mtu', 'mgmt_only', 'mark_connected', 'description', 'poe_mode', 'poe_type', 'mode',
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'tx_power', 'wireless_lans',
'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'tags',
'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'primary_mac_address',
'tags',
]
widgets = {
'speed': NumberWithOptions(
@ -1595,7 +1618,10 @@ class InventoryItemForm(DeviceComponentForm):
)
fieldsets = (
FieldSet('device', 'parent', 'name', 'label', 'status', 'role', 'description', 'tags', name=_('Inventory Item')),
FieldSet(
'device', 'parent', 'name', 'label', 'status', 'role', 'description', 'tags',
name=_('Inventory Item')
),
FieldSet('manufacturer', 'part_id', 'serial', 'asset_tag', name=_('Hardware')),
FieldSet(
TabbedGroups(
@ -1717,3 +1743,72 @@ class VirtualDeviceContextForm(TenancyForm, NetBoxModelForm):
'device', 'name', 'status', 'identifier', 'primary_ip4', 'primary_ip6', 'tenant_group', 'tenant',
'comments', 'tags'
]
#
# Addressing
#
class MACAddressForm(NetBoxModelForm):
mac_address = forms.CharField(
required=True,
label=_('MAC address')
)
interface = DynamicModelChoiceField(
label=_('Interface'),
queryset=Interface.objects.all(),
required=False,
)
vminterface = DynamicModelChoiceField(
label=_('VM Interface'),
queryset=VMInterface.objects.all(),
required=False,
)
fieldsets = (
FieldSet(
'mac_address', 'description', 'tags',
),
FieldSet(
TabbedGroups(
FieldSet('interface', name=_('Device')),
FieldSet('vminterface', name=_('Virtual Machine')),
),
),
)
class Meta:
model = MACAddress
fields = [
'mac_address', 'interface', 'vminterface', 'description', 'tags',
]
def __init__(self, *args, **kwargs):
# Initialize helper selectors
instance = kwargs.get('instance')
initial = kwargs.get('initial', {}).copy()
if instance:
if type(instance.assigned_object) is Interface:
initial['interface'] = instance.assigned_object
elif type(instance.assigned_object) is VMInterface:
initial['vminterface'] = instance.assigned_object
kwargs['initial'] = initial
super().__init__(*args, **kwargs)
def clean(self):
super().clean()
# Handle object assignment
selected_objects = [
field for field in ('interface', 'vminterface') if self.cleaned_data[field]
]
if len(selected_objects) > 1:
raise forms.ValidationError({
selected_objects[1]: _("A MAC address can only be assigned to a single object.")
})
elif selected_objects:
self.instance.assigned_object = self.cleaned_data[selected_objects[0]]
else:
self.instance.assigned_object = None

View File

@ -243,14 +243,6 @@ 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):
device = DynamicModelChoiceField(
@ -424,7 +416,8 @@ class VirtualChassisCreateForm(NetBoxModelForm):
class Meta:
model = VirtualChassis
fields = [
'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position', 'tags',
'name', 'domain', 'description', 'region', 'site_group', 'site', 'rack', 'members', 'initial_position',
'tags',
]
def clean(self):

View File

@ -136,7 +136,8 @@ class FrontPortTemplateImportForm(forms.ModelForm):
class Meta:
model = FrontPortTemplate
fields = [
'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label', 'description',
'device_type', 'module_type', 'name', 'type', 'color', 'rear_port', 'rear_port_position', 'label',
'description',
]

View File

@ -23,6 +23,7 @@ __all__ = (
'InventoryItemFilter',
'InventoryItemRoleFilter',
'LocationFilter',
'MACAddressFilter',
'ManufacturerFilter',
'ModuleFilter',
'ModuleBayFilter',
@ -133,6 +134,12 @@ class FrontPortTemplateFilter(BaseFilterMixin):
pass
@strawberry_django.filter(models.MACAddress, lookups=True)
@autotype_decorator(filtersets.MACAddressFilterSet)
class MACAddressFilter(BaseFilterMixin):
pass
@strawberry_django.filter(models.Interface, lookups=True)
@autotype_decorator(filtersets.InterfaceFilterSet)
class InterfaceFilter(BaseFilterMixin):

View File

@ -44,6 +44,9 @@ class DCIMQuery:
front_port_template: FrontPortTemplateType = strawberry_django.field()
front_port_template_list: List[FrontPortTemplateType] = strawberry_django.field()
mac_address: MACAddressType = strawberry_django.field()
mac_address_list: List[MACAddressType] = strawberry_django.field()
interface: InterfaceType = strawberry_django.field()
interface_list: List[InterfaceType] = strawberry_django.field()

View File

@ -34,6 +34,7 @@ __all__ = (
'InventoryItemRoleType',
'InventoryItemTemplateType',
'LocationType',
'MACAddressType',
'ManufacturerType',
'ModularComponentType',
'ModuleType',
@ -366,6 +367,22 @@ class FrontPortTemplateType(ModularComponentTemplateType):
rear_port: Annotated["RearPortTemplateType", strawberry.lazy('dcim.graphql.types')]
@strawberry_django.type(
models.MACAddress,
exclude=('assigned_object_type', 'assigned_object_id'),
filters=MACAddressFilter
)
class MACAddressType(NetBoxObjectType):
mac_address: str
@strawberry_django.field
def assigned_object(self) -> Annotated[Union[
Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')],
Annotated["VMInterfaceType", strawberry.lazy('virtualization.graphql.types')],
], strawberry.union("MACAddressAssignmentType")] | None:
return self.assigned_object
@strawberry_django.type(
models.Interface,
exclude=('_path',),
@ -373,7 +390,6 @@ class FrontPortTemplateType(ModularComponentTemplateType):
)
class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin):
_name: str
mac_address: str | None
wwn: str | None
parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
@ -381,6 +397,7 @@ class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, P
wireless_link: Annotated["WirelessLinkType", strawberry.lazy('wireless.graphql.types')] | None
untagged_vlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
vrf: Annotated["VRFType", strawberry.lazy('ipam.graphql.types')] | None
primary_mac_address: Annotated["MACAddressType", strawberry.lazy('dcim.graphql.types')] | None
qinq_svlan: Annotated["VLANType", strawberry.lazy('ipam.graphql.types')] | None
vlan_translation_policy: Annotated["VLANTranslationPolicyType", strawberry.lazy('ipam.graphql.types')] | None
@ -390,6 +407,7 @@ class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, P
wireless_lans: List[Annotated["WirelessLANType", strawberry.lazy('wireless.graphql.types')]]
member_interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
child_interfaces: List[Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')]]
mac_addresses: List[Annotated["MACAddressType", strawberry.lazy('dcim.graphql.types')]]
@strawberry_django.type(
@ -464,7 +482,9 @@ class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, Organi
return self.cluster_set.all()
@strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all()
@ -710,7 +730,9 @@ class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
return self.cluster_set.all()
@strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all()
@ -742,7 +764,9 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObje
return self.cluster_set.all()
@strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all()
@ -766,7 +790,9 @@ class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
return self.cluster_set.all()
@strawberry_django.field
def circuit_terminations(self) -> List[Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]]:
def circuit_terminations(self) -> List[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
]:
return self.circuit_terminations.all()

View File

@ -13,11 +13,9 @@ import utilities.validators
class Migration(migrations.Migration):
initial = True
dependencies = [
]
dependencies = []
replaces = [
('dcim', '0001_initial'),
@ -64,7 +62,12 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)),
@ -83,7 +86,12 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('type', models.CharField(blank=True, max_length=50)),
@ -100,7 +108,12 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)),
@ -119,7 +132,12 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('type', models.CharField(blank=True, max_length=50)),
@ -137,14 +155,34 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(primary_key=True, serialize=False)),
('local_context_data', models.JSONField(blank=True, null=True)),
('name', models.CharField(blank=True, max_length=64, null=True)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize, null=True)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize, null=True
),
),
('serial', models.CharField(blank=True, max_length=50)),
('asset_tag', models.CharField(blank=True, max_length=50, null=True, unique=True)),
('position', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])),
(
'position',
models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
('face', models.CharField(blank=True, max_length=50)),
('status', models.CharField(default='active', max_length=50)),
('vc_position', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)])),
('vc_priority', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)])),
(
'vc_position',
models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]
),
),
(
'vc_priority',
models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MaxValueValidator(255)]
),
),
('comments', models.TextField(blank=True)),
],
options={
@ -159,7 +197,12 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
],
@ -174,7 +217,12 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
],
@ -228,13 +276,27 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)),
('mark_connected', models.BooleanField(default=False)),
('type', models.CharField(max_length=50)),
('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])),
(
'rear_port_position',
models.PositiveSmallIntegerField(
default=1,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(1024),
],
),
),
],
options={
'ordering': ('device', '_name'),
@ -247,11 +309,25 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('type', models.CharField(max_length=50)),
('rear_port_position', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])),
(
'rear_port_position',
models.PositiveSmallIntegerField(
default=1,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(1024),
],
),
),
],
options={
'ordering': ('device_type', '_name'),
@ -271,9 +347,24 @@ class Migration(migrations.Migration):
('mark_connected', models.BooleanField(default=False)),
('enabled', models.BooleanField(default=True)),
('mac_address', dcim.fields.MACAddressField(blank=True, null=True)),
('mtu', models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(65536)])),
(
'mtu',
models.PositiveIntegerField(
blank=True,
null=True,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(65536),
],
),
),
('mode', models.CharField(blank=True, max_length=50)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface
),
),
('type', models.CharField(max_length=50)),
('mgmt_only', models.BooleanField(default=False)),
],
@ -290,7 +381,12 @@ class Migration(migrations.Migration):
('name', models.CharField(max_length=64)),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize_interface
),
),
('type', models.CharField(max_length=50)),
('mgmt_only', models.BooleanField(default=False)),
],
@ -306,7 +402,12 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('part_id', models.CharField(blank=True, max_length=50)),
@ -388,8 +489,19 @@ class Migration(migrations.Migration):
('supply', models.CharField(default='ac', max_length=50)),
('phase', models.CharField(default='single-phase', max_length=50)),
('voltage', models.SmallIntegerField(validators=[utilities.validators.ExclusionValidator([0])])),
('amperage', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1)])),
('max_utilization', models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])),
(
'amperage',
models.PositiveSmallIntegerField(validators=[django.core.validators.MinValueValidator(1)]),
),
(
'max_utilization',
models.PositiveSmallIntegerField(
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100),
]
),
),
('available_power', models.PositiveIntegerField(default=0, editable=False)),
('comments', models.TextField(blank=True)),
],
@ -405,7 +517,12 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)),
@ -424,7 +541,12 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('type', models.CharField(blank=True, max_length=50)),
@ -455,14 +577,29 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)),
('mark_connected', models.BooleanField(default=False)),
('type', models.CharField(blank=True, max_length=50)),
('maximum_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])),
('allocated_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])),
(
'maximum_draw',
models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
(
'allocated_draw',
models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
],
options={
'ordering': ('device', '_name'),
@ -475,12 +612,27 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('type', models.CharField(blank=True, max_length=50)),
('maximum_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])),
('allocated_draw', models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)])),
(
'maximum_draw',
models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
(
'allocated_draw',
models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
],
options={
'ordering': ('device_type', '_name'),
@ -494,14 +646,28 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=100)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('facility_id', models.CharField(blank=True, max_length=50, null=True)),
('status', models.CharField(default='active', max_length=50)),
('serial', models.CharField(blank=True, max_length=50)),
('asset_tag', models.CharField(blank=True, max_length=50, null=True, unique=True)),
('type', models.CharField(blank=True, max_length=50)),
('width', models.PositiveSmallIntegerField(default=19)),
('u_height', models.PositiveSmallIntegerField(default=42, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100)])),
(
'u_height',
models.PositiveSmallIntegerField(
default=42,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100),
],
),
),
('desc_units', models.BooleanField(default=False)),
('outer_width', models.PositiveSmallIntegerField(blank=True, null=True)),
('outer_depth', models.PositiveSmallIntegerField(blank=True, null=True)),
@ -519,7 +685,10 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('units', django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(), size=None)),
(
'units',
django.contrib.postgres.fields.ArrayField(base_field=models.PositiveSmallIntegerField(), size=None),
),
('description', models.CharField(max_length=200)),
],
options={
@ -550,13 +719,27 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('_cable_peer_id', models.PositiveIntegerField(blank=True, null=True)),
('mark_connected', models.BooleanField(default=False)),
('type', models.CharField(max_length=50)),
('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])),
(
'positions',
models.PositiveSmallIntegerField(
default=1,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(1024),
],
),
),
],
options={
'ordering': ('device', '_name'),
@ -569,11 +752,25 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('type', models.CharField(max_length=50)),
('positions', models.PositiveSmallIntegerField(default=1, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(1024)])),
(
'positions',
models.PositiveSmallIntegerField(
default=1,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(1024),
],
),
),
],
options={
'ordering': ('device_type', '_name'),
@ -606,7 +803,12 @@ class Migration(migrations.Migration):
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=CustomFieldJSONEncoder)),
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=100, unique=True)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('slug', models.SlugField(max_length=100, unique=True)),
('status', models.CharField(default='active', max_length=50)),
('facility', models.CharField(blank=True, max_length=50)),
@ -654,7 +856,16 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('domain', models.CharField(blank=True, max_length=30)),
('master', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vc_master_for', to='dcim.device')),
(
'master',
models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='vc_master_for',
to='dcim.device',
),
),
],
options={
'verbose_name_plural': 'virtual chassis',

View File

@ -6,7 +6,6 @@ import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('dcim', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
@ -28,17 +27,35 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='sitegroup',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.sitegroup'),
field=mptt.fields.TreeForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.sitegroup',
),
),
migrations.AddField(
model_name='site',
name='group',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.sitegroup'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='sites',
to='dcim.sitegroup',
),
),
migrations.AddField(
model_name='site',
name='region',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sites', to='dcim.region'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='sites',
to='dcim.region',
),
),
migrations.AddField(
model_name='site',
@ -48,32 +65,56 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='site',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='sites',
to='tenancy.tenant',
),
),
migrations.AddField(
model_name='region',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.region'),
field=mptt.fields.TreeForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.region',
),
),
migrations.AddField(
model_name='rearporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='rearport',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='rearport',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='rearport',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='rearport',
@ -83,7 +124,9 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='rackreservation',
name='rack',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='dcim.rack'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='dcim.rack'
),
),
migrations.AddField(
model_name='rackreservation',
@ -93,7 +136,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='rackreservation',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='rackreservations', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='rackreservations',
to='tenancy.tenant',
),
),
migrations.AddField(
model_name='rackreservation',
@ -103,12 +152,24 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='rack',
name='location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='racks', to='dcim.location'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='racks',
to='dcim.location',
),
),
migrations.AddField(
model_name='rack',
name='role',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='dcim.rackrole'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='racks',
to='dcim.rackrole',
),
),
migrations.AddField(
model_name='rack',
@ -123,32 +184,52 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='rack',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='racks',
to='tenancy.tenant',
),
),
migrations.AddField(
model_name='powerporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='powerport',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='powerport',
name='_path',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'
),
),
migrations.AddField(
model_name='powerport',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='powerport',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='powerport',
@ -158,7 +239,9 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='powerpanel',
name='location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.location'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='dcim.location'
),
),
migrations.AddField(
model_name='powerpanel',
@ -173,37 +256,63 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='poweroutlettemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='poweroutlettemplate',
name='power_port',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlet_templates', to='dcim.powerporttemplate'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='poweroutlet_templates',
to='dcim.powerporttemplate',
),
),
migrations.AddField(
model_name='poweroutlet',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='poweroutlet',
name='_path',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'
),
),
migrations.AddField(
model_name='poweroutlet',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='poweroutlet',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='poweroutlet',
name='power_port',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='poweroutlets', to='dcim.powerport'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='poweroutlets',
to='dcim.powerport',
),
),
migrations.AddField(
model_name='poweroutlet',
@ -213,27 +322,45 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='powerfeed',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='powerfeed',
name='_path',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'
),
),
migrations.AddField(
model_name='powerfeed',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='powerfeed',
name='power_panel',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='powerfeeds', to='dcim.powerpanel'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='powerfeeds', to='dcim.powerpanel'
),
),
migrations.AddField(
model_name='powerfeed',
name='rack',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='powerfeeds', to='dcim.rack'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='powerfeeds',
to='dcim.rack',
),
),
migrations.AddField(
model_name='powerfeed',
@ -243,32 +370,60 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='platform',
name='manufacturer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='platforms', to='dcim.manufacturer'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='platforms',
to='dcim.manufacturer',
),
),
migrations.AddField(
model_name='location',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.location'),
field=mptt.fields.TreeForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='children',
to='dcim.location',
),
),
migrations.AddField(
model_name='location',
name='site',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='dcim.site'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='dcim.site'
),
),
migrations.AddField(
model_name='inventoryitem',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='inventoryitem',
name='manufacturer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_items', to='dcim.manufacturer'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='inventory_items',
to='dcim.manufacturer',
),
),
migrations.AddField(
model_name='inventoryitem',
name='parent',
field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.inventoryitem'),
field=mptt.fields.TreeForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='child_items',
to='dcim.inventoryitem',
),
),
migrations.AddField(
model_name='inventoryitem',
@ -278,36 +433,62 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='interfacetemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='interface',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='interface',
name='_path',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'
),
),
migrations.AddField(
model_name='interface',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='interface',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='interface',
name='lag',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='member_interfaces', to='dcim.interface'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='member_interfaces',
to='dcim.interface',
),
),
migrations.AddField(
model_name='interface',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='child_interfaces', to='dcim.interface'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='child_interfaces',
to='dcim.interface',
),
),
]

View File

@ -4,7 +4,6 @@ import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('dcim', '0002_auto_20160622_1821'),
('virtualization', '0001_virtualization'),
@ -160,37 +159,61 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='interface',
name='untagged_vlan',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interfaces_as_untagged', to='ipam.vlan'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='interfaces_as_untagged',
to='ipam.vlan',
),
),
migrations.AddField(
model_name='frontporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='frontporttemplate',
name='rear_port',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontport_templates', to='dcim.rearporttemplate'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name='frontport_templates',
to='dcim.rearporttemplate',
),
),
migrations.AddField(
model_name='frontport',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='frontport',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='frontport',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='frontport',
name='rear_port',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.rearport'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='frontports', to='dcim.rearport'
),
),
migrations.AddField(
model_name='frontport',
@ -200,7 +223,9 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='devicetype',
name='manufacturer',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='device_types', to='dcim.manufacturer'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='device_types', to='dcim.manufacturer'
),
),
migrations.AddField(
model_name='devicetype',
@ -210,17 +235,27 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='devicebaytemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='devicebay',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='devicebay',
name='installed_device',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parent_bay', to='dcim.device'),
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='parent_bay',
to='dcim.device',
),
),
migrations.AddField(
model_name='devicebay',
@ -230,47 +265,89 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='device',
name='cluster',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='virtualization.cluster'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='devices',
to='virtualization.cluster',
),
),
migrations.AddField(
model_name='device',
name='device_role',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.devicerole'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.devicerole'
),
),
migrations.AddField(
model_name='device',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='device',
name='location',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.location'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='devices',
to='dcim.location',
),
),
migrations.AddField(
model_name='device',
name='platform',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='devices', to='dcim.platform'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='devices',
to='dcim.platform',
),
),
migrations.AddField(
model_name='device',
name='primary_ip4',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip4_for', to='ipam.ipaddress'),
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='primary_ip4_for',
to='ipam.ipaddress',
),
),
migrations.AddField(
model_name='device',
name='primary_ip6',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='primary_ip6_for', to='ipam.ipaddress'),
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='primary_ip6_for',
to='ipam.ipaddress',
),
),
migrations.AddField(
model_name='device',
name='rack',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.rack'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='devices',
to='dcim.rack',
),
),
migrations.AddField(
model_name='device',
name='site',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.site'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='dcim.site'
),
),
migrations.AddField(
model_name='device',
@ -280,37 +357,63 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='device',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='devices',
to='tenancy.tenant',
),
),
migrations.AddField(
model_name='device',
name='virtual_chassis',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='members', to='dcim.virtualchassis'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='members',
to='dcim.virtualchassis',
),
),
migrations.AddField(
model_name='consoleserverporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='consoleserverport',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='consoleserverport',
name='_path',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'
),
),
migrations.AddField(
model_name='consoleserverport',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='consoleserverport',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='consoleserverport',
@ -320,27 +423,41 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='consoleporttemplate',
name='device_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
migrations.AddField(
model_name='consoleport',
name='_cable_peer_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='consoleport',
name='_path',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='dcim.cablepath'
),
),
migrations.AddField(
model_name='consoleport',
name='cable',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.cable'
),
),
migrations.AddField(
model_name='consoleport',
name='device',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
migrations.AddField(
model_name='consoleport',
@ -350,22 +467,34 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='cablepath',
name='destination_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='cablepath',
name='origin_type',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='+', to='contenttypes.contenttype'
),
),
migrations.AddField(
model_name='cable',
name='_termination_a_device',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device'
),
),
migrations.AddField(
model_name='cable',
name='_termination_b_device',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device'),
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='dcim.device'
),
),
migrations.AddField(
model_name='cable',
@ -375,12 +504,64 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='cable',
name='termination_a_type',
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
limit_choices_to=models.Q(
models.Q(
models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))),
models.Q(
('app_label', 'dcim'),
(
'model__in',
(
'consoleport',
'consoleserverport',
'frontport',
'interface',
'powerfeed',
'poweroutlet',
'powerport',
'rearport',
),
),
),
_connector='OR',
)
),
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='cable',
name='termination_b_type',
field=models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
limit_choices_to=models.Q(
models.Q(
models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))),
models.Q(
('app_label', 'dcim'),
(
'model__in',
(
'consoleport',
'consoleserverport',
'frontport',
'interface',
'powerfeed',
'poweroutlet',
'powerport',
'rearport',
),
),
),
_connector='OR',
)
),
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AlterUniqueTogether(
name='rearporttemplate',
@ -456,7 +637,11 @@ class Migration(migrations.Migration):
),
migrations.AlterUniqueTogether(
name='device',
unique_together={('rack', 'position', 'face'), ('virtual_chassis', 'vc_position'), ('site', 'tenant', 'name')},
unique_together={
('rack', 'position', 'face'),
('virtual_chassis', 'vc_position'),
('site', 'tenant', 'name'),
},
),
migrations.AlterUniqueTogether(
name='consoleserverporttemplate',

View File

@ -10,7 +10,6 @@ import utilities.ordering
class Migration(migrations.Migration):
replaces = [
('dcim', '0131_consoleport_speed'),
('dcim', '0132_cable_length'),
@ -40,7 +39,7 @@ class Migration(migrations.Migration):
('dcim', '0156_location_status'),
('dcim', '0157_new_cabling_models'),
('dcim', '0158_populate_cable_terminations'),
('dcim', '0159_populate_cable_paths')
('dcim', '0159_populate_cable_paths'),
]
dependencies = [
@ -96,17 +95,35 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='interface',
name='bridge',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bridge_interfaces', to='dcim.interface'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='bridge_interfaces',
to='dcim.interface',
),
),
migrations.AddField(
model_name='location',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='locations', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='locations',
to='tenancy.tenant',
),
),
migrations.AddField(
model_name='cable',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='cables', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='cables',
to='tenancy.tenant',
),
),
migrations.AddField(
model_name='devicetype',
@ -148,7 +165,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='location',
constraint=models.UniqueConstraint(condition=models.Q(('parent', None)), fields=('site', 'name'), name='dcim_location_name'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent', None)), fields=('site', 'name'), name='dcim_location_name'
),
),
migrations.AddConstraint(
model_name='location',
@ -156,7 +175,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='location',
constraint=models.UniqueConstraint(condition=models.Q(('parent', None)), fields=('site', 'slug'), name='dcim_location_slug'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent', None)), fields=('site', 'slug'), name='dcim_location_slug'
),
),
migrations.AddConstraint(
model_name='region',
@ -164,7 +185,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='region',
constraint=models.UniqueConstraint(condition=models.Q(('parent', None)), fields=('name',), name='dcim_region_name'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent', None)), fields=('name',), name='dcim_region_name'
),
),
migrations.AddConstraint(
model_name='region',
@ -172,7 +195,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='region',
constraint=models.UniqueConstraint(condition=models.Q(('parent', None)), fields=('slug',), name='dcim_region_slug'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent', None)), fields=('slug',), name='dcim_region_slug'
),
),
migrations.AddConstraint(
model_name='sitegroup',
@ -180,7 +205,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='sitegroup',
constraint=models.UniqueConstraint(condition=models.Q(('parent', None)), fields=('name',), name='dcim_sitegroup_name'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent', None)), fields=('name',), name='dcim_sitegroup_name'
),
),
migrations.AddConstraint(
model_name='sitegroup',
@ -188,7 +215,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='sitegroup',
constraint=models.UniqueConstraint(condition=models.Q(('parent', None)), fields=('slug',), name='dcim_sitegroup_slug'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent', None)), fields=('slug',), name='dcim_sitegroup_slug'
),
),
migrations.AddField(
model_name='devicerole',
@ -328,7 +357,9 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='interface',
name='tx_power',
field=models.PositiveSmallIntegerField(blank=True, null=True, validators=[django.core.validators.MaxValueValidator(127)]),
field=models.PositiveSmallIntegerField(
blank=True, null=True, validators=[django.core.validators.MaxValueValidator(127)]
),
),
migrations.AddField(
model_name='interface',
@ -338,7 +369,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='interface',
name='wireless_link',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wireless.wirelesslink'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='wireless.wirelesslink',
),
),
migrations.AddField(
model_name='site',
@ -348,12 +385,24 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='device',
name='primary_ip4',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress'),
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='ipam.ipaddress',
),
),
migrations.AlterField(
model_name='device',
name='primary_ip6',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress'),
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='ipam.ipaddress',
),
),
migrations.RemoveField(
model_name='site',
@ -372,7 +421,23 @@ class Migration(migrations.Migration):
name='contact_phone',
),
migrations.RunSQL(
sql="\n DO $$\n DECLARE\n idx record;\n BEGIN\n FOR idx IN\n SELECT indexname AS old_name,\n replace(indexname, 'module', 'inventoryitem') AS new_name\n FROM pg_indexes\n WHERE schemaname = 'public' AND\n tablename = 'dcim_inventoryitem' AND\n indexname LIKE 'dcim_module_%'\n LOOP\n EXECUTE format(\n 'ALTER INDEX %I RENAME TO %I;',\n idx.old_name,\n idx.new_name\n );\n END LOOP;\n END$$;\n ",
sql="""DO $$
DECLARE idx record;
BEGIN
FOR idx IN
SELECT indexname AS old_name, replace(indexname, 'module', 'inventoryitem') AS new_name
FROM pg_indexes
WHERE schemaname = 'public' AND
tablename = 'dcim_inventoryitem' AND
indexname LIKE 'dcim_module_%'
LOOP
EXECUTE format(
'ALTER INDEX %I RENAME TO %I;',
idx.old_name,
idx.new_name
);
END LOOP;
END$$;""",
),
migrations.AlterModelOptions(
name='consoleporttemplate',
@ -405,49 +470,99 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='consoleporttemplate',
name='device_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.devicetype',
),
),
migrations.AlterField(
model_name='consoleserverporttemplate',
name='device_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.devicetype',
),
),
migrations.AlterField(
model_name='frontporttemplate',
name='device_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.devicetype',
),
),
migrations.AlterField(
model_name='interfacetemplate',
name='device_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.devicetype',
),
),
migrations.AlterField(
model_name='poweroutlettemplate',
name='device_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.devicetype',
),
),
migrations.AlterField(
model_name='powerporttemplate',
name='device_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.devicetype',
),
),
migrations.AlterField(
model_name='rearporttemplate',
name='device_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.devicetype',
),
),
migrations.CreateModel(
name='ModuleType',
fields=[
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('model', models.CharField(max_length=100)),
('part_number', models.CharField(blank=True, max_length=50)),
('comments', models.TextField(blank=True)),
('manufacturer', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='module_types', to='dcim.manufacturer')),
(
'manufacturer',
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='module_types', to='dcim.manufacturer'
),
),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
@ -460,14 +575,27 @@ class Migration(migrations.Migration):
fields=[
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('position', models.CharField(blank=True, max_length=30)),
('description', models.CharField(blank=True, max_length=200)),
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device')),
(
'device',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.device'
),
),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
@ -480,15 +608,35 @@ class Migration(migrations.Migration):
fields=[
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('local_context_data', models.JSONField(blank=True, null=True)),
('serial', models.CharField(blank=True, max_length=50)),
('asset_tag', models.CharField(blank=True, max_length=50, null=True, unique=True)),
('comments', models.TextField(blank=True)),
('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='dcim.device')),
('module_bay', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='installed_module', to='dcim.modulebay')),
('module_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.moduletype')),
(
'device',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='dcim.device'
),
),
(
'module_bay',
models.OneToOneField(
on_delete=django.db.models.deletion.CASCADE,
related_name='installed_module',
to='dcim.modulebay',
),
),
(
'module_type',
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='instances', to='dcim.moduletype'
),
),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
],
options={
@ -498,72 +646,156 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='consoleport',
name='module',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.module'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.module',
),
),
migrations.AddField(
model_name='consoleporttemplate',
name='module_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.moduletype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.moduletype',
),
),
migrations.AddField(
model_name='consoleserverport',
name='module',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.module'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.module',
),
),
migrations.AddField(
model_name='consoleserverporttemplate',
name='module_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.moduletype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.moduletype',
),
),
migrations.AddField(
model_name='frontport',
name='module',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.module'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.module',
),
),
migrations.AddField(
model_name='frontporttemplate',
name='module_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.moduletype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.moduletype',
),
),
migrations.AddField(
model_name='interface',
name='module',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.module'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.module',
),
),
migrations.AddField(
model_name='interfacetemplate',
name='module_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.moduletype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.moduletype',
),
),
migrations.AddField(
model_name='poweroutlet',
name='module',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.module'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.module',
),
),
migrations.AddField(
model_name='poweroutlettemplate',
name='module_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.moduletype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.moduletype',
),
),
migrations.AddField(
model_name='powerport',
name='module',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.module'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.module',
),
),
migrations.AddField(
model_name='powerporttemplate',
name='module_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.moduletype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.moduletype',
),
),
migrations.AddField(
model_name='rearport',
name='module',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.module'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.module',
),
),
migrations.AddField(
model_name='rearporttemplate',
name='module_type',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.moduletype'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='%(class)ss',
to='dcim.moduletype',
),
),
migrations.AlterUniqueTogether(
name='consoleporttemplate',
@ -598,7 +830,10 @@ class Migration(migrations.Migration):
fields=[
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('name', models.CharField(max_length=100, unique=True)),
('slug', models.SlugField(max_length=100, unique=True)),
@ -613,7 +848,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='inventoryitem',
name='role',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_items', to='dcim.inventoryitemrole'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='inventory_items',
to='dcim.inventoryitemrole',
),
),
migrations.AddField(
model_name='inventoryitem',
@ -623,12 +864,39 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='inventoryitem',
name='component_type',
field=models.ForeignKey(blank=True, limit_choices_to=models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'poweroutlet', 'powerport', 'rearport'))), null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype'),
field=models.ForeignKey(
blank=True,
limit_choices_to=models.Q(
('app_label', 'dcim'),
(
'model__in',
(
'consoleport',
'consoleserverport',
'frontport',
'interface',
'poweroutlet',
'powerport',
'rearport',
),
),
),
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
migrations.AddField(
model_name='interface',
name='vrf',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interfaces', to='ipam.vrf'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='interfaces',
to='ipam.vrf',
),
),
migrations.AddField(
model_name='interface',
@ -952,7 +1220,12 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('description', models.CharField(blank=True, max_length=200)),
('component_id', models.PositiveBigIntegerField(blank=True, null=True)),
@ -961,11 +1234,67 @@ class Migration(migrations.Migration):
('rght', models.PositiveIntegerField(editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(editable=False)),
('component_type', models.ForeignKey(blank=True, limit_choices_to=models.Q(('app_label', 'dcim'), ('model__in', ('consoleporttemplate', 'consoleserverporttemplate', 'frontporttemplate', 'interfacetemplate', 'poweroutlettemplate', 'powerporttemplate', 'rearporttemplate'))), null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype')),
('manufacturer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_item_templates', to='dcim.manufacturer')),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='child_items', to='dcim.inventoryitemtemplate')),
('role', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='inventory_item_templates', to='dcim.inventoryitemrole')),
(
'component_type',
models.ForeignKey(
blank=True,
limit_choices_to=models.Q(
('app_label', 'dcim'),
(
'model__in',
(
'consoleporttemplate',
'consoleserverporttemplate',
'frontporttemplate',
'interfacetemplate',
'poweroutlettemplate',
'powerporttemplate',
'rearporttemplate',
),
),
),
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
(
'device_type',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
(
'manufacturer',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='inventory_item_templates',
to='dcim.manufacturer',
),
),
(
'parent',
mptt.fields.TreeForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name='child_items',
to='dcim.inventoryitemtemplate',
),
),
(
'role',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='inventory_item_templates',
to='dcim.inventoryitemrole',
),
),
],
options={
'ordering': ('device_type__id', 'parent__id', '_name'),
@ -989,11 +1318,21 @@ class Migration(migrations.Migration):
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('name', models.CharField(max_length=64)),
('_name', utilities.fields.NaturalOrderingField('name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize)),
(
'_name',
utilities.fields.NaturalOrderingField(
'name', blank=True, max_length=100, naturalize_function=utilities.ordering.naturalize
),
),
('label', models.CharField(blank=True, max_length=64)),
('position', models.CharField(blank=True, max_length=30)),
('description', models.CharField(blank=True, max_length=200)),
('device_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype')),
(
'device_type',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='%(class)ss', to='dcim.devicetype'
),
),
],
options={
'ordering': ('device_type', '_name'),
@ -1088,7 +1427,16 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='device',
name='position',
field=models.DecimalField(blank=True, decimal_places=1, max_digits=4, null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100.5)]),
field=models.DecimalField(
blank=True,
decimal_places=1,
max_digits=4,
null=True,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100.5),
],
),
),
migrations.AddField(
model_name='interface',
@ -1121,12 +1469,66 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('cable_end', models.CharField(max_length=1)),
('termination_id', models.PositiveBigIntegerField()),
('cable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='dcim.cable')),
('termination_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))), models.Q(('app_label', 'dcim'), ('model__in', ('consoleport', 'consoleserverport', 'frontport', 'interface', 'powerfeed', 'poweroutlet', 'powerport', 'rearport'))), _connector='OR')), on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.contenttype')),
('_device', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.device')),
('_rack', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.rack')),
('_location', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.location')),
('_site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.site')),
(
'cable',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name='terminations', to='dcim.cable'
),
),
(
'termination_type',
models.ForeignKey(
limit_choices_to=models.Q(
models.Q(
models.Q(('app_label', 'circuits'), ('model__in', ('circuittermination',))),
models.Q(
('app_label', 'dcim'),
(
'model__in',
(
'consoleport',
'consoleserverport',
'frontport',
'interface',
'powerfeed',
'poweroutlet',
'powerport',
'rearport',
),
),
),
_connector='OR',
)
),
on_delete=django.db.models.deletion.PROTECT,
related_name='+',
to='contenttypes.contenttype',
),
),
(
'_device',
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.device'
),
),
(
'_rack',
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.rack'
),
),
(
'_location',
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.location'
),
),
(
'_site',
models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='dcim.site'
),
),
],
options={
'ordering': ('cable', 'cable_end', 'pk'),
@ -1134,7 +1536,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='cabletermination',
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='dcim_cable_termination_unique_termination'),
constraint=models.UniqueConstraint(
fields=('termination_type', 'termination_id'), name='dcim_cable_termination_unique_termination'
),
),
migrations.RenameField(
model_name='cablepath',

View File

@ -6,7 +6,6 @@ import utilities.json
class Migration(migrations.Migration):
replaces = [
('dcim', '0160_populate_cable_ends'),
('dcim', '0161_cabling_cleanup'),
@ -14,7 +13,7 @@ class Migration(migrations.Migration):
('dcim', '0163_weight_fields'),
('dcim', '0164_rack_mounting_depth'),
('dcim', '0165_standardize_description_comments'),
('dcim', '0166_virtualdevicecontext')
('dcim', '0166_virtualdevicecontext'),
]
dependencies = [
@ -275,7 +274,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='cabletermination',
constraint=models.UniqueConstraint(fields=('termination_type', 'termination_id'), name='dcim_cabletermination_unique_termination'),
constraint=models.UniqueConstraint(
fields=('termination_type', 'termination_id'), name='dcim_cabletermination_unique_termination'
),
),
migrations.AddConstraint(
model_name='consoleport',
@ -283,39 +284,64 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='consoleporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_consoleporttemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_consoleporttemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='consoleporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_consoleporttemplate_unique_module_type_name'),
constraint=models.UniqueConstraint(
fields=('module_type', 'name'), name='dcim_consoleporttemplate_unique_module_type_name'
),
),
migrations.AddConstraint(
model_name='consoleserverport',
constraint=models.UniqueConstraint(fields=('device', 'name'), name='dcim_consoleserverport_unique_device_name'),
constraint=models.UniqueConstraint(
fields=('device', 'name'), name='dcim_consoleserverport_unique_device_name'
),
),
migrations.AddConstraint(
model_name='consoleserverporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_consoleserverporttemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_consoleserverporttemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='consoleserverporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_consoleserverporttemplate_unique_module_type_name'),
constraint=models.UniqueConstraint(
fields=('module_type', 'name'), name='dcim_consoleserverporttemplate_unique_module_type_name'
),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(django.db.models.functions.text.Lower('name'), models.F('site'), models.F('tenant'), name='dcim_device_unique_name_site_tenant'),
constraint=models.UniqueConstraint(
django.db.models.functions.text.Lower('name'),
models.F('site'),
models.F('tenant'),
name='dcim_device_unique_name_site_tenant',
),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(django.db.models.functions.text.Lower('name'), models.F('site'), condition=models.Q(('tenant__isnull', True)), name='dcim_device_unique_name_site', violation_error_message='Device name must be unique per site.'),
constraint=models.UniqueConstraint(
django.db.models.functions.text.Lower('name'),
models.F('site'),
condition=models.Q(('tenant__isnull', True)),
name='dcim_device_unique_name_site',
violation_error_message='Device name must be unique per site.',
),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(fields=('rack', 'position', 'face'), name='dcim_device_unique_rack_position_face'),
constraint=models.UniqueConstraint(
fields=('rack', 'position', 'face'), name='dcim_device_unique_rack_position_face'
),
),
migrations.AddConstraint(
model_name='device',
constraint=models.UniqueConstraint(fields=('virtual_chassis', 'vc_position'), name='dcim_device_unique_virtual_chassis_vc_position'),
constraint=models.UniqueConstraint(
fields=('virtual_chassis', 'vc_position'), name='dcim_device_unique_virtual_chassis_vc_position'
),
),
migrations.AddConstraint(
model_name='devicebay',
@ -323,15 +349,21 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='devicebaytemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_devicebaytemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_devicebaytemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='devicetype',
constraint=models.UniqueConstraint(fields=('manufacturer', 'model'), name='dcim_devicetype_unique_manufacturer_model'),
constraint=models.UniqueConstraint(
fields=('manufacturer', 'model'), name='dcim_devicetype_unique_manufacturer_model'
),
),
migrations.AddConstraint(
model_name='devicetype',
constraint=models.UniqueConstraint(fields=('manufacturer', 'slug'), name='dcim_devicetype_unique_manufacturer_slug'),
constraint=models.UniqueConstraint(
fields=('manufacturer', 'slug'), name='dcim_devicetype_unique_manufacturer_slug'
),
),
migrations.AddConstraint(
model_name='frontport',
@ -339,19 +371,27 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='frontport',
constraint=models.UniqueConstraint(fields=('rear_port', 'rear_port_position'), name='dcim_frontport_unique_rear_port_position'),
constraint=models.UniqueConstraint(
fields=('rear_port', 'rear_port_position'), name='dcim_frontport_unique_rear_port_position'
),
),
migrations.AddConstraint(
model_name='frontporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_frontporttemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_frontporttemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='frontporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_frontporttemplate_unique_module_type_name'),
constraint=models.UniqueConstraint(
fields=('module_type', 'name'), name='dcim_frontporttemplate_unique_module_type_name'
),
),
migrations.AddConstraint(
model_name='frontporttemplate',
constraint=models.UniqueConstraint(fields=('rear_port', 'rear_port_position'), name='dcim_frontporttemplate_unique_rear_port_position'),
constraint=models.UniqueConstraint(
fields=('rear_port', 'rear_port_position'), name='dcim_frontporttemplate_unique_rear_port_position'
),
),
migrations.AddConstraint(
model_name='interface',
@ -359,27 +399,46 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='interfacetemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_interfacetemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_interfacetemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='interfacetemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_interfacetemplate_unique_module_type_name'),
constraint=models.UniqueConstraint(
fields=('module_type', 'name'), name='dcim_interfacetemplate_unique_module_type_name'
),
),
migrations.AddConstraint(
model_name='inventoryitem',
constraint=models.UniqueConstraint(fields=('device', 'parent', 'name'), name='dcim_inventoryitem_unique_device_parent_name'),
constraint=models.UniqueConstraint(
fields=('device', 'parent', 'name'), name='dcim_inventoryitem_unique_device_parent_name'
),
),
migrations.AddConstraint(
model_name='inventoryitemtemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'parent', 'name'), name='dcim_inventoryitemtemplate_unique_device_type_parent_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'parent', 'name'),
name='dcim_inventoryitemtemplate_unique_device_type_parent_name',
),
),
migrations.AddConstraint(
model_name='location',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('site', 'name'), name='dcim_location_name', violation_error_message='A location with this name already exists within the specified site.'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent__isnull', True)),
fields=('site', 'name'),
name='dcim_location_name',
violation_error_message='A location with this name already exists within the specified site.',
),
),
migrations.AddConstraint(
model_name='location',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('site', 'slug'), name='dcim_location_slug', violation_error_message='A location with this slug already exists within the specified site.'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent__isnull', True)),
fields=('site', 'slug'),
name='dcim_location_slug',
violation_error_message='A location with this slug already exists within the specified site.',
),
),
migrations.AddConstraint(
model_name='modulebay',
@ -387,15 +446,21 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='modulebaytemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_modulebaytemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_modulebaytemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='moduletype',
constraint=models.UniqueConstraint(fields=('manufacturer', 'model'), name='dcim_moduletype_unique_manufacturer_model'),
constraint=models.UniqueConstraint(
fields=('manufacturer', 'model'), name='dcim_moduletype_unique_manufacturer_model'
),
),
migrations.AddConstraint(
model_name='powerfeed',
constraint=models.UniqueConstraint(fields=('power_panel', 'name'), name='dcim_powerfeed_unique_power_panel_name'),
constraint=models.UniqueConstraint(
fields=('power_panel', 'name'), name='dcim_powerfeed_unique_power_panel_name'
),
),
migrations.AddConstraint(
model_name='poweroutlet',
@ -403,11 +468,15 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='poweroutlettemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_poweroutlettemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_poweroutlettemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='poweroutlettemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_poweroutlettemplate_unique_module_type_name'),
constraint=models.UniqueConstraint(
fields=('module_type', 'name'), name='dcim_poweroutlettemplate_unique_module_type_name'
),
),
migrations.AddConstraint(
model_name='powerpanel',
@ -419,11 +488,15 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='powerporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_powerporttemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_powerporttemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='powerporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_powerporttemplate_unique_module_type_name'),
constraint=models.UniqueConstraint(
fields=('module_type', 'name'), name='dcim_powerporttemplate_unique_module_type_name'
),
),
migrations.AddConstraint(
model_name='rack',
@ -431,7 +504,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='rack',
constraint=models.UniqueConstraint(fields=('location', 'facility_id'), name='dcim_rack_unique_location_facility_id'),
constraint=models.UniqueConstraint(
fields=('location', 'facility_id'), name='dcim_rack_unique_location_facility_id'
),
),
migrations.AddConstraint(
model_name='rearport',
@ -439,27 +514,51 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='rearporttemplate',
constraint=models.UniqueConstraint(fields=('device_type', 'name'), name='dcim_rearporttemplate_unique_device_type_name'),
constraint=models.UniqueConstraint(
fields=('device_type', 'name'), name='dcim_rearporttemplate_unique_device_type_name'
),
),
migrations.AddConstraint(
model_name='rearporttemplate',
constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_rearporttemplate_unique_module_type_name'),
constraint=models.UniqueConstraint(
fields=('module_type', 'name'), name='dcim_rearporttemplate_unique_module_type_name'
),
),
migrations.AddConstraint(
model_name='region',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('name',), name='dcim_region_name', violation_error_message='A top-level region with this name already exists.'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent__isnull', True)),
fields=('name',),
name='dcim_region_name',
violation_error_message='A top-level region with this name already exists.',
),
),
migrations.AddConstraint(
model_name='region',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('slug',), name='dcim_region_slug', violation_error_message='A top-level region with this slug already exists.'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent__isnull', True)),
fields=('slug',),
name='dcim_region_slug',
violation_error_message='A top-level region with this slug already exists.',
),
),
migrations.AddConstraint(
model_name='sitegroup',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('name',), name='dcim_sitegroup_name', violation_error_message='A top-level site group with this name already exists.'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent__isnull', True)),
fields=('name',),
name='dcim_sitegroup_name',
violation_error_message='A top-level site group with this name already exists.',
),
),
migrations.AddConstraint(
model_name='sitegroup',
constraint=models.UniqueConstraint(condition=models.Q(('parent__isnull', True)), fields=('slug',), name='dcim_sitegroup_slug', violation_error_message='A top-level site group with this slug already exists.'),
constraint=models.UniqueConstraint(
condition=models.Q(('parent__isnull', True)),
fields=('slug',),
name='dcim_sitegroup_slug',
violation_error_message='A top-level site group with this slug already exists.',
),
),
migrations.AddField(
model_name='devicetype',
@ -592,17 +691,56 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('description', models.CharField(blank=True, max_length=200)),
('name', models.CharField(max_length=64)),
('status', models.CharField(max_length=50)),
('identifier', models.PositiveSmallIntegerField(blank=True, null=True)),
('comments', models.TextField(blank=True)),
('device', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vdcs', to='dcim.device')),
('primary_ip4', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress')),
('primary_ip6', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress')),
(
'device',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='vdcs',
to='dcim.device',
),
),
(
'primary_ip4',
models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='ipam.ipaddress',
),
),
(
'primary_ip6',
models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='ipam.ipaddress',
),
),
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
('tenant', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vdcs', to='tenancy.tenant')),
(
'tenant',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='vdcs',
to='tenancy.tenant',
),
),
],
options={
'ordering': ['name'],
@ -615,7 +753,9 @@ class Migration(migrations.Migration):
),
migrations.AddConstraint(
model_name='virtualdevicecontext',
constraint=models.UniqueConstraint(fields=('device', 'identifier'), name='dcim_virtualdevicecontext_device_identifier'),
constraint=models.UniqueConstraint(
fields=('device', 'identifier'), name='dcim_virtualdevicecontext_device_identifier'
),
),
migrations.AddConstraint(
model_name='virtualdevicecontext',

View File

@ -6,7 +6,6 @@ import utilities.fields
class Migration(migrations.Migration):
replaces = [
('dcim', '0167_module_status'),
('dcim', '0168_interface_template_enabled'),
@ -24,7 +23,7 @@ class Migration(migrations.Migration):
('dcim', '0179_interfacetemplate_rf_role'),
('dcim', '0180_powerfeed_tenant'),
('dcim', '0181_rename_device_role_device_role'),
('dcim', '0182_zero_length_cable_fix')
('dcim', '0182_zero_length_cable_fix'),
]
dependencies = [
@ -48,27 +47,57 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='interfacetemplate',
name='bridge',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bridge_interfaces', to='dcim.interfacetemplate'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='bridge_interfaces',
to='dcim.interfacetemplate',
),
),
migrations.AddField(
model_name='devicetype',
name='default_platform',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.platform'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='dcim.platform',
),
),
migrations.AddField(
model_name='device',
name='config_template',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='%(class)ss', to='extras.configtemplate'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='%(class)ss',
to='extras.configtemplate',
),
),
migrations.AddField(
model_name='devicerole',
name='config_template',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='device_roles', to='extras.configtemplate'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='device_roles',
to='extras.configtemplate',
),
),
migrations.AddField(
model_name='platform',
name='config_template',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='platforms', to='extras.configtemplate'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='platforms',
to='extras.configtemplate',
),
),
migrations.AddField(
model_name='cabletermination',
@ -83,22 +112,30 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='powerport',
name='allocated_draw',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
field=models.PositiveIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
migrations.AlterField(
model_name='powerport',
name='maximum_draw',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
field=models.PositiveIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
migrations.AlterField(
model_name='powerporttemplate',
name='allocated_draw',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
field=models.PositiveIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
migrations.AlterField(
model_name='powerporttemplate',
name='maximum_draw',
field=models.PositiveIntegerField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]),
field=models.PositiveIntegerField(
blank=True, null=True, validators=[django.core.validators.MinValueValidator(1)]
),
),
migrations.RemoveField(
model_name='platform',
@ -126,112 +163,160 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='device',
name='oob_ip',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='ipam.ipaddress'),
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name='+',
to='ipam.ipaddress',
),
),
migrations.AddField(
model_name='device',
name='console_port_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.ConsolePort'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.ConsolePort'
),
),
migrations.AddField(
model_name='device',
name='console_server_port_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.ConsoleServerPort'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.ConsoleServerPort'
),
),
migrations.AddField(
model_name='device',
name='power_port_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.PowerPort'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.PowerPort'
),
),
migrations.AddField(
model_name='device',
name='power_outlet_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.PowerOutlet'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.PowerOutlet'
),
),
migrations.AddField(
model_name='device',
name='interface_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.Interface'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.Interface'
),
),
migrations.AddField(
model_name='device',
name='front_port_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.FrontPort'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.FrontPort'
),
),
migrations.AddField(
model_name='device',
name='rear_port_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.RearPort'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.RearPort'
),
),
migrations.AddField(
model_name='device',
name='device_bay_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.DeviceBay'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.DeviceBay'
),
),
migrations.AddField(
model_name='device',
name='module_bay_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.ModuleBay'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.ModuleBay'
),
),
migrations.AddField(
model_name='device',
name='inventory_item_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device', to_model='dcim.InventoryItem'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device', to_model='dcim.InventoryItem'
),
),
migrations.AddField(
model_name='devicetype',
name='console_port_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.ConsolePortTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.ConsolePortTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='console_server_port_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.ConsoleServerPortTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.ConsoleServerPortTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='power_port_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.PowerPortTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.PowerPortTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='power_outlet_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.PowerOutletTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.PowerOutletTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='interface_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.InterfaceTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.InterfaceTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='front_port_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.FrontPortTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.FrontPortTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='rear_port_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.RearPortTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.RearPortTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='device_bay_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.DeviceBayTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.DeviceBayTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='module_bay_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.ModuleBayTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.ModuleBayTemplate'
),
),
migrations.AddField(
model_name='devicetype',
name='inventory_item_template_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='device_type', to_model='dcim.InventoryItemTemplate'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='device_type', to_model='dcim.InventoryItemTemplate'
),
),
migrations.AddField(
model_name='virtualchassis',
name='member_count',
field=utilities.fields.CounterCacheField(default=0, editable=False, to_field='virtual_chassis', to_model='dcim.Device'),
field=utilities.fields.CounterCacheField(
default=0, editable=False, to_field='virtual_chassis', to_model='dcim.Device'
),
),
migrations.AddField(
model_name='interfacetemplate',
@ -241,7 +326,13 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='powerfeed',
name='tenant',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='power_feeds', to='tenancy.tenant'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name='power_feeds',
to='tenancy.tenant',
),
),
migrations.RenameField(
model_name='device',

View File

@ -5,7 +5,6 @@ import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('dcim', '0183_devicetype_exclude_from_utilization'),
]
@ -14,6 +13,12 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name='interface',
name='parent',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.RESTRICT, related_name='child_interfaces', to='dcim.interface'),
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.RESTRICT,
related_name='child_interfaces',
to='dcim.interface',
),
),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0184_protect_child_interfaces'),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0185_gfk_indexes'),
]

View File

@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0186_location_facility'),
]

View File

@ -9,7 +9,6 @@ import utilities.ordering
class Migration(migrations.Migration):
dependencies = [
('extras', '0118_customfield_uniqueness'),
('dcim', '0187_alter_device_vc_position'),
@ -22,36 +21,41 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, null=True)),
('last_updated', models.DateTimeField(auto_now=True, null=True)),
('custom_field_data', models.JSONField(
blank=True,
default=dict,
encoder=utilities.json.CustomFieldJSONEncoder
)),
(
'custom_field_data',
models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
),
('description', models.CharField(blank=True, max_length=200)),
('comments', models.TextField(blank=True)),
('weight', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True)),
('weight_unit', models.CharField(blank=True, max_length=50)),
('_abs_weight', models.PositiveBigIntegerField(blank=True, null=True)),
('manufacturer', models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
related_name='rack_types',
to='dcim.manufacturer'
)),
(
'manufacturer',
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, related_name='rack_types', to='dcim.manufacturer'
),
),
('model', models.CharField(max_length=100)),
('slug', models.SlugField(max_length=100, unique=True)),
('form_factor', models.CharField(max_length=50)),
('width', models.PositiveSmallIntegerField(default=19)),
('u_height', models.PositiveSmallIntegerField(
default=42,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100),
]
)),
('starting_unit', models.PositiveSmallIntegerField(
default=1,
validators=[django.core.validators.MinValueValidator(1)]
)),
(
'u_height',
models.PositiveSmallIntegerField(
default=42,
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(100),
],
),
),
(
'starting_unit',
models.PositiveSmallIntegerField(
default=1, validators=[django.core.validators.MinValueValidator(1)]
),
),
('desc_units', models.BooleanField(default=False)),
('outer_width', models.PositiveSmallIntegerField(blank=True, null=True)),
('outer_depth', models.PositiveSmallIntegerField(blank=True, null=True)),

View File

@ -2,7 +2,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dcim', '0188_racktype'),
]

Some files were not shown because too many files have changed in this diff Show More