diff --git a/.github/ISSUE_TEMPLATE/01-feature_request.yaml b/.github/ISSUE_TEMPLATE/01-feature_request.yaml index 300d7ce6a..48a3a859c 100644 --- a/.github/ISSUE_TEMPLATE/01-feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/01-feature_request.yaml @@ -1,5 +1,6 @@ --- name: ✨ Feature Request +type: Feature description: Propose a new NetBox feature or enhancement labels: ["type: feature", "status: needs triage"] body: @@ -14,7 +15,7 @@ body: attributes: label: NetBox version description: What version of NetBox are you currently running? - placeholder: v4.1.6 + placeholder: v4.2.2 validations: required: true - type: dropdown @@ -27,19 +28,6 @@ body: - Other validations: required: true - - type: dropdown - attributes: - label: Triage priority - description: > - Issue triage may be prioritized in some cases. Select whichever of the following - conditions applies, if any. - options: - - I volunteer to perform this work (if approved) - - I'm a NetBox Labs customer - - N/A - default: 2 - validations: - required: true - type: textarea attributes: label: Proposed functionality diff --git a/.github/ISSUE_TEMPLATE/02-bug_report.yaml b/.github/ISSUE_TEMPLATE/02-bug_report.yaml index 2b782a6cd..83397944c 100644 --- a/.github/ISSUE_TEMPLATE/02-bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/02-bug_report.yaml @@ -1,5 +1,6 @@ --- name: 🐛 Bug Report +type: Bug description: Report a reproducible bug in the current release of NetBox labels: ["type: bug", "status: needs triage"] body: @@ -22,24 +23,11 @@ body: - Self-hosted validations: required: true - - type: dropdown - attributes: - label: Triage priority - description: > - Issue triage may be prioritized in some cases. Select whichever of the following - conditions applies, if any. - options: - - I volunteer to perform this work (if approved) - - I'm a NetBox Labs customer - - N/A - default: 2 - validations: - required: true - type: input attributes: label: NetBox Version description: What version of NetBox are you currently running? - placeholder: v4.1.6 + placeholder: v4.2.2 validations: required: true - type: dropdown diff --git a/.github/ISSUE_TEMPLATE/03-documentation_change.yaml b/.github/ISSUE_TEMPLATE/03-documentation_change.yaml index b5a970782..2dea61acc 100644 --- a/.github/ISSUE_TEMPLATE/03-documentation_change.yaml +++ b/.github/ISSUE_TEMPLATE/03-documentation_change.yaml @@ -1,5 +1,6 @@ --- name: 📖 Documentation Change +type: Documentation description: Suggest an addition or modification to the NetBox documentation labels: ["type: documentation", "status: needs triage"] body: diff --git a/.github/ISSUE_TEMPLATE/04-translation.yaml b/.github/ISSUE_TEMPLATE/04-translation.yaml index d07bc399d..72130ae47 100644 --- a/.github/ISSUE_TEMPLATE/04-translation.yaml +++ b/.github/ISSUE_TEMPLATE/04-translation.yaml @@ -1,5 +1,6 @@ --- name: 🌍 Translation +type: Translation description: Request support for a new language in the user interface labels: ["type: translation"] body: diff --git a/.github/ISSUE_TEMPLATE/05-housekeeping.yaml b/.github/ISSUE_TEMPLATE/05-housekeeping.yaml index 777871395..65b983e18 100644 --- a/.github/ISSUE_TEMPLATE/05-housekeeping.yaml +++ b/.github/ISSUE_TEMPLATE/05-housekeeping.yaml @@ -1,5 +1,6 @@ --- name: 🏡 Housekeeping +type: Housekeeping description: A change pertaining to the codebase itself (developers only) labels: ["type: housekeeping"] body: diff --git a/.github/ISSUE_TEMPLATE/06-deprecation.yaml b/.github/ISSUE_TEMPLATE/06-deprecation.yaml index 27e13e5c0..83905a39a 100644 --- a/.github/ISSUE_TEMPLATE/06-deprecation.yaml +++ b/.github/ISSUE_TEMPLATE/06-deprecation.yaml @@ -1,5 +1,6 @@ --- name: 🗑️ Deprecation +type: Deprecation description: The removal of an existing feature or resource labels: ["type: deprecation"] body: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index efbf38932..5b18f4525 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: 📖 Contributing Policy - url: https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md + url: https://github.com/netbox-community/netbox/blob/main/CONTRIBUTING.md about: "Please read through our contributing policy before opening an issue or pull request." - name: ❓ Discussion url: https://github.com/netbox-community/netbox/discussions diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3b4876c3..85070d98e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,11 +3,15 @@ name: CI on: push: paths-ignore: + - '.github/ISSUE_TEMPLATE/**' + - '.github/PULL_REQUEST_TEMPLATE.md' - 'contrib/**' - 'docs/**' - 'netbox/translations/**' pull_request: paths-ignore: + - '.github/ISSUE_TEMPLATE/**' + - '.github/PULL_REQUEST_TEMPLATE.md' - 'contrib/**' - 'docs/**' - 'netbox/translations/**' @@ -15,6 +19,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 @@ -23,7 +32,7 @@ jobs: strategy: matrix: python-version: ['3.10', '3.11', '3.12'] - node-version: ['18.x'] + node-version: ['20.x'] services: redis: image: redis diff --git a/.github/workflows/close-stale-issues.yml b/.github/workflows/close-stale-issues.yml index 1e0e193df..89b3d5f9a 100644 --- a/.github/workflows/close-stale-issues.yml +++ b/.github/workflows/close-stale-issues.yml @@ -38,7 +38,7 @@ jobs: issues may receive direct feedback. **Do not** attempt to circumvent this process by "bumping" the issue; doing so will result in its immediate closure and you may be barred from participating in any future discussions. Please see - our [contributing guide](https://github.com/netbox-community/netbox/blob/develop/CONTRIBUTING.md). + our [contributing guide](https://github.com/netbox-community/netbox/blob/main/CONTRIBUTING.md). # Pull request parameters close-pr-message: > diff --git a/.github/workflows/update-translation-strings.yml b/.github/workflows/update-translation-strings.yml index bcd68c887..e78cd4296 100644 --- a/.github/workflows/update-translation-strings.yml +++ b/.github/workflows/update-translation-strings.yml @@ -18,8 +18,17 @@ jobs: NETBOX_CONFIGURATION: netbox.configuration_testing steps: + - name: Create app token + uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: 1076524 + private-key: ${{ secrets.HOUSEKEEPING_SECRET_KEY }} + - name: Check out repo uses: actions/checkout@v4 + with: + token: ${{ steps.app-token.outputs.token }} - name: Set up Python uses: actions/setup-python@v5 diff --git a/README.md b/README.md index e3829c2cc..3a29a6fd2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@
The cornerstone of every automated network
NetBox Community | NetBox Cloud | diff --git a/base_requirements.txt b/base_requirements.txt index 3e5bcb0db..9cf0fbf8b 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -1,6 +1,6 @@ # The Python web framework on which NetBox is built # https://docs.djangoproject.com/en/stable/releases/ -Django<5.1 +Django<5.2 # Django middleware which permits cross-domain API requests # https://github.com/adamchainz/django-cors-headers/blob/main/CHANGELOG.rst @@ -8,8 +8,6 @@ django-cors-headers # Runtime UI tool for debugging Django # https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst -# Pinned for DNS looukp bug; see https://github.com/netbox-community/netbox/issues/16454 -# and https://github.com/jazzband/django-debug-toolbar/issues/1927 django-debug-toolbar # Library for writing reusable URL query filters @@ -101,7 +99,7 @@ netaddr nh3 # Fork of PIL (Python Imaging Library) for image processing -# https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst +# https://github.com/python-pillow/Pillow/releases Pillow # PostgreSQL database adapter for Python @@ -134,7 +132,8 @@ strawberry-graphql # Strawberry GraphQL Django extension # https://github.com/strawberry-graphql/strawberry-django/releases -strawberry-graphql-django +# Pinned to v0.52.0 for suspected upstream bug; see #18329 +strawberry-graphql-django==0.52.0 # SVG image rendering (used for rack elevations) # https://github.com/mozman/svgwrite/blob/master/NEWS.rst diff --git a/contrib/generated_schema.json b/contrib/generated_schema.json index 56ddee50e..639f0df8d 100644 --- a/contrib/generated_schema.json +++ b/contrib/generated_schema.json @@ -329,6 +329,7 @@ "100base-tx", "100base-t1", "1000base-t", + "1000base-lx", "1000base-tx", "2.5gbase-t", "5gbase-t", diff --git a/docs/configuration/miscellaneous.md b/docs/configuration/miscellaneous.md index 124de3037..c14c0ac77 100644 --- a/docs/configuration/miscellaneous.md +++ b/docs/configuration/miscellaneous.md @@ -96,14 +96,6 @@ The maximum size (in bytes) of an incoming HTTP request (i.e. `GET` or `POST` da --- -## DJANGO_ADMIN_ENABLED - -Default: False - -Setting this to True installs the `django.contrib.admin` app and enables the [Django admin UI](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/). This may be necessary to support older plugins which do not integrate with the native NetBox interface. - ---- - ## ENFORCE_GLOBAL_UNIQUE !!! tip "Dynamic Configuration Parameter" @@ -114,6 +106,16 @@ By default, NetBox will prevent the creation of duplicate prefixes and IP addres --- +## EVENTS_PIPELINE + +!!! info "This parameter was introduced in NetBox v4.2." + +Default: `['extras.events.process_event_queue',]` + +NetBox will call dotted paths to the functions listed here for events (create, update, delete) on models as well as when custom EventRules are fired. + +--- + ## FILE_UPLOAD_MAX_MEMORY_SIZE Default: `2621440` (2.5 MB) diff --git a/docs/configuration/required-parameters.md b/docs/configuration/required-parameters.md index 90eb8c0cf..f9a5a6f87 100644 --- a/docs/configuration/required-parameters.md +++ b/docs/configuration/required-parameters.md @@ -25,7 +25,7 @@ ALLOWED_HOSTS = ['*'] ## DATABASE -NetBox requires access to a PostgreSQL 12 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary: +NetBox requires access to a PostgreSQL 13 or later database service to store data. This service can run locally on the NetBox server or on a remote system. The following parameters must be defined within the `DATABASE` dictionary: * `NAME` - Database name * `USER` - PostgreSQL username diff --git a/docs/configuration/system.md b/docs/configuration/system.md index 25c724bc9..af3a6f5e6 100644 --- a/docs/configuration/system.md +++ b/docs/configuration/system.md @@ -89,8 +89,6 @@ addresses (and [`DEBUG`](./development.md#debug) is true). ## ISOLATED_DEPLOYMENT -!!! info "This feature was introduced in NetBox v4.1." - Default: False Set this configuration parameter to True for NetBox deployments which do not have Internet access. This will disable miscellaneous functionality which depends on access to the Internet. diff --git a/docs/development/adding-models.md b/docs/development/adding-models.md index f3aa9cfcc..0bf020662 100644 --- a/docs/development/adding-models.md +++ b/docs/development/adding-models.md @@ -8,7 +8,7 @@ Each model should define, at a minimum: * A `Meta` class specifying a deterministic ordering (if ordered by fields other than the primary ID) * A `__str__()` method returning a user-friendly string representation of the instance -* A `get_absolute_url()` method returning an instance's direct URL (using `reverse()`) +* A `get_absolute_url()` method if necessary; a standard version of the method is defined in the `NetBoxFeatureSet` base class, but you will need to provide your own (returning an instance's direct URL using `reverse()`) if not subclassing that base class ## 2. Define field choices @@ -78,6 +78,8 @@ Create the following for each model: Create a GraphQL object type for the model in `graphql/types.py` by subclassing the appropriate class from `netbox.graphql.types`. +**Note:** GraphQL unit tests may fail citing null values on a non-nullable field if related objects are prefetched. You may need to fix this by setting the type annotation to be `= strawberry_django.field(select_related=["policy"])` or similar. + Also extend the schema class defined in `graphql/schema.py` with the individual object and object list fields per the established convention. ## 14. Add tests diff --git a/docs/development/application-registry.md b/docs/development/application-registry.md index 570563431..fc96bfd76 100644 --- a/docs/development/application-registry.md +++ b/docs/development/application-registry.md @@ -49,6 +49,10 @@ This key lists all models which have been registered in NetBox which are not des This store maintains all registered items for plugins, such as navigation menus, template extensions, etc. +### `request_processors` + +A list of context managers to invoke when processing a request e.g. in middleware or when executing a background job. Request processors can be registered with the `@register_request_processor` decorator. + ### `search` A dictionary mapping each model (identified by its app and label) to its search index class, if one has been registered for it. diff --git a/docs/development/getting-started.md b/docs/development/getting-started.md index 6e425d5a3..0b77bfd4d 100644 --- a/docs/development/getting-started.md +++ b/docs/development/getting-started.md @@ -37,16 +37,12 @@ CHANGELOG.md CONTRIBUTING.md LICENSE.txt netbox README.md scri ### 2. Create a New Branch -The NetBox project utilizes three persistent git branches to track work: +The NetBox project utilizes two persistent git branches to track work: -* `master` - Serves as a snapshot of the current stable release -* `develop` - All development on the upcoming stable (patch) release occurs here -* `feature` - Tracks work on an upcoming minor release +* `main` - All development on the upcoming stable (patch) release occurs here. Releases are published from this branch. +* `feature` - All work planned for the upcoming minor release is done here. -Typically, you'll base pull requests off of the `develop` branch, or off of `feature` if you're working on a new major release. For example, assume that the current NetBox release is v3.3.5. Work applied to the `develop` branch will appear in v3.3.6, and work done under the `feature` branch will be included in the next minor release (v3.4.0). - -!!! warning - **Never** merge pull requests into the `master` branch: This branch only ever merges pull requests from the `develop` branch, to effect a new release. +Typically, you'll base pull requests off of the `main` branch, or off of `feature` if you're working on the upcoming minor or major release. For example, assume that the current NetBox release is v4.2.3. Work applied to the `main` branch will appear in v4.2.4, and work done under the `feature` branch will be included in the next minor release (v4.3.0). To create a new branch, first ensure that you've checked out the desired base branch, then run: diff --git a/docs/development/git-cheat-sheet.md b/docs/development/git-cheat-sheet.md index 35b8e90b5..794b25c65 100644 --- a/docs/development/git-cheat-sheet.md +++ b/docs/development/git-cheat-sheet.md @@ -128,7 +128,7 @@ Fast-forward ``` !!! warning "Avoid Merging Remote Branches" - You generally want to avoid merging branches that exist on the remote (upstream) repository, such as `develop` and `feature`: Merges into these branches should be done via a pull request on GitHub. Only merge branches when it is necessary to consolidate work you've done locally. + You generally want to avoid merging branches that exist on the remote (upstream) repository, namely `main` and `feature`: Merges into these branches should be done via a pull request on GitHub. Only merge branches when it is necessary to consolidate work you've done locally. ### Show Pending Changes @@ -196,7 +196,7 @@ index 93e125079..4344fb514 100644 +and here too +
{module}
, if present, will be "
+ "automatically replaced with the position value when creating a new module."
+ )
+
class ConsolePortTemplateForm(ModularComponentTemplateForm):
fieldsets = (
@@ -991,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',
]
@@ -1173,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
@@ -1285,16 +1308,16 @@ class PowerOutletForm(ModularDeviceComponentForm):
fieldsets = (
FieldSet(
- 'device', 'module', 'name', 'label', 'type', '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', 'power_port', 'feed_leg', 'mark_connected', 'description',
- 'tags',
+ 'device', 'module', 'name', 'label', 'type', 'color', 'power_port', 'feed_leg', 'mark_connected',
+ 'description', 'tags',
]
@@ -1372,26 +1395,51 @@ class InterfaceForm(InterfaceCommonForm, ModularDeviceComponentForm):
'available_on_device': '$device',
}
)
+ qinq_svlan = DynamicModelChoiceField(
+ queryset=VLAN.objects.all(),
+ required=False,
+ label=_('Q-in-Q Service VLAN'),
+ query_params={
+ 'group_id': '$vlan_group',
+ 'available_on_device': '$device',
+ 'qinq_role': VLANQinQRoleChoices.ROLE_SERVICE,
+ }
+ )
vrf = DynamicModelChoiceField(
queryset=VRF.objects.all(),
required=False,
label=_('VRF')
)
+ primary_mac_address = DynamicModelChoiceField(
+ queryset=MACAddress.objects.all(),
+ label=_('Primary MAC address'),
+ required=False,
+ quick_add=True,
+ quick_add_params={'interface': '$pk'}
+ )
wwn = forms.CharField(
empty_value=None,
required=False,
label=_('WWN')
)
+ vlan_translation_policy = DynamicModelChoiceField(
+ queryset=VLANTranslationPolicy.objects.all(),
+ required=False,
+ label=_('VLAN Translation Policy')
+ )
fieldsets = (
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')),
- FieldSet('mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', name=_('802.1Q Switching')),
+ FieldSet(
+ 'mode', 'vlan_group', 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy',
+ name=_('802.1Q Switching')
+ ),
FieldSet(
'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width', 'wireless_lan_group', 'wireless_lans',
name=_('Wireless')
@@ -1401,10 +1449,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', 'vrf', 'tags',
+ 'untagged_vlan', 'tagged_vlans', 'qinq_svlan', 'vlan_translation_policy', 'vrf', 'primary_mac_address',
+ 'tags',
]
widgets = {
'speed': NumberWithOptions(
@@ -1576,7 +1625,10 @@ class InventoryItemForm(DeviceComponentForm):
)
fieldsets = (
- FieldSet('device', 'parent', 'name', 'label', '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(
@@ -1596,7 +1648,7 @@ class InventoryItemForm(DeviceComponentForm):
model = InventoryItem
fields = [
'device', 'parent', 'name', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
- 'description', 'tags',
+ 'status', 'description', 'tags',
]
def __init__(self, *args, **kwargs):
@@ -1698,3 +1750,83 @@ 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,
+ context={
+ 'parent': 'device',
+ },
+ )
+ vminterface = DynamicModelChoiceField(
+ label=_('VM Interface'),
+ queryset=VMInterface.objects.all(),
+ required=False,
+ context={
+ 'parent': 'virtual_machine',
+ },
+ )
+
+ 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)
+
+ if instance and instance.assigned_object and instance.assigned_object.primary_mac_address:
+ if instance.assigned_object.primary_mac_address.pk == instance.pk:
+ self.fields['interface'].disabled = True
+ self.fields['vminterface'].disabled = True
+
+ 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
diff --git a/netbox/dcim/forms/object_create.py b/netbox/dcim/forms/object_create.py
index d18c7ed14..6f6cd8f7c 100644
--- a/netbox/dcim/forms/object_create.py
+++ b/netbox/dcim/forms/object_create.py
@@ -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 {module}
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):
diff --git a/netbox/dcim/forms/object_import.py b/netbox/dcim/forms/object_import.py
index d46ef83ad..821f91402 100644
--- a/netbox/dcim/forms/object_import.py
+++ b/netbox/dcim/forms/object_import.py
@@ -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',
]
diff --git a/netbox/dcim/graphql/filters.py b/netbox/dcim/graphql/filters.py
index 8c256aecb..94f2c6d38 100644
--- a/netbox/dcim/graphql/filters.py
+++ b/netbox/dcim/graphql/filters.py
@@ -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):
diff --git a/netbox/dcim/graphql/schema.py b/netbox/dcim/graphql/schema.py
index 65818fb20..011a2b58b 100644
--- a/netbox/dcim/graphql/schema.py
+++ b/netbox/dcim/graphql/schema.py
@@ -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()
diff --git a/netbox/dcim/graphql/types.py b/netbox/dcim/graphql/types.py
index bce6f06ac..8d992176a 100644
--- a/netbox/dcim/graphql/types.py
+++ b/netbox/dcim/graphql/types.py
@@ -34,6 +34,7 @@ __all__ = (
'InventoryItemRoleType',
'InventoryItemTemplateType',
'LocationType',
+ 'MACAddressType',
'ManufacturerType',
'ModularComponentType',
'ModuleType',
@@ -76,7 +77,6 @@ class ComponentType(
"""
Base type for device/VM components
"""
- _name: str
device: Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]
@@ -93,7 +93,6 @@ class ComponentTemplateType(
"""
Base type for device/VM components
"""
- _name: str
device_type: Annotated["DeviceTypeType", strawberry.lazy('dcim.graphql.types')]
@@ -116,7 +115,7 @@ class ModularComponentTemplateType(ComponentTemplateType):
filters=CableTerminationFilter
)
class CableTerminationType(NetBoxObjectType):
-
+ cable: Annotated["CableType", strawberry.lazy('dcim.graphql.types')] | None
termination: Annotated[Union[
Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')],
Annotated["ConsolePortType", strawberry.lazy('dcim.graphql.types')],
@@ -181,7 +180,7 @@ class ConsolePortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin
filters=ConsolePortTemplateFilter
)
class ConsolePortTemplateType(ModularComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -199,7 +198,7 @@ class ConsoleServerPortType(ModularComponentType, CabledObjectMixin, PathEndpoin
filters=ConsoleServerPortTemplateFilter
)
class ConsoleServerPortTemplateType(ModularComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -208,7 +207,6 @@ class ConsoleServerPortTemplateType(ModularComponentTemplateType):
filters=DeviceFilter
)
class DeviceType(ConfigContextMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
- _name: str
console_port_count: BigInt
console_server_port_count: BigInt
power_port_count: BigInt
@@ -273,7 +271,7 @@ class DeviceBayType(ComponentType):
filters=DeviceBayTemplateFilter
)
class DeviceBayTemplateType(ComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -282,7 +280,6 @@ class DeviceBayTemplateType(ComponentTemplateType):
filters=InventoryItemTemplateFilter
)
class InventoryItemTemplateType(ComponentTemplateType):
- _name: str
role: Annotated["InventoryItemRoleType", strawberry.lazy('dcim.graphql.types')] | None
manufacturer: Annotated["ManufacturerType", strawberry.lazy('dcim.graphql.types')]
@@ -366,18 +363,33 @@ class FrontPortType(ModularComponentType, CabledObjectMixin):
filters=FrontPortTemplateFilter
)
class FrontPortTemplateType(ModularComponentTemplateType):
- _name: str
color: str
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',),
filters=InterfaceFilter
)
class InterfaceType(IPAddressesMixin, ModularComponentType, CabledObjectMixin, PathEndpointMixin):
- mac_address: str | None
+ _name: str
wwn: str | None
parent: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
bridge: Annotated["InterfaceType", strawberry.lazy('dcim.graphql.types')] | None
@@ -385,6 +397,9 @@ 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
vdcs: List[Annotated["VirtualDeviceContextType", strawberry.lazy('dcim.graphql.types')]]
tagged_vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
@@ -392,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(
@@ -461,6 +477,16 @@ class LocationType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, Organi
devices: List[Annotated["DeviceType", strawberry.lazy('dcim.graphql.types')]]
children: List[Annotated["LocationType", strawberry.lazy('dcim.graphql.types')]]
+ @strawberry_django.field
+ def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+ return self.cluster_set.all()
+
+ @strawberry_django.field
+ def circuit_terminations(self) -> List[
+ Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
+ ]:
+ return self.circuit_terminations.all()
+
@strawberry_django.type(
models.Manufacturer,
@@ -517,7 +543,7 @@ class ModuleBayType(ModularComponentType):
filters=ModuleBayTemplateFilter
)
class ModuleBayTemplateType(ModularComponentTemplateType):
- _name: str
+ pass
@strawberry_django.type(
@@ -569,6 +595,7 @@ class PowerFeedType(NetBoxObjectType, CabledObjectMixin, PathEndpointMixin):
)
class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
power_port: Annotated["PowerPortType", strawberry.lazy('dcim.graphql.types')] | None
+ color: str
@strawberry_django.type(
@@ -577,7 +604,6 @@ class PowerOutletType(ModularComponentType, CabledObjectMixin, PathEndpointMixin
filters=PowerOutletTemplateFilter
)
class PowerOutletTemplateType(ModularComponentTemplateType):
- _name: str
power_port: Annotated["PowerPortTemplateType", strawberry.lazy('dcim.graphql.types')] | None
@@ -609,8 +635,6 @@ class PowerPortType(ModularComponentType, CabledObjectMixin, PathEndpointMixin):
filters=PowerPortTemplateFilter
)
class PowerPortTemplateType(ModularComponentTemplateType):
- _name: str
-
poweroutlet_templates: List[Annotated["PowerOutletTemplateType", strawberry.lazy('dcim.graphql.types')]]
@@ -629,7 +653,6 @@ class RackTypeType(NetBoxObjectType):
filters=RackFilter
)
class RackType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
- _name: str
site: Annotated["SiteType", strawberry.lazy('dcim.graphql.types')]
location: Annotated["LocationType", strawberry.lazy('dcim.graphql.types')] | None
tenant: Annotated["TenantType", strawberry.lazy('tenancy.graphql.types')] | None
@@ -682,7 +705,6 @@ class RearPortType(ModularComponentType, CabledObjectMixin):
filters=RearPortTemplateFilter
)
class RearPortTemplateType(ModularComponentTemplateType):
- _name: str
color: str
frontport_templates: List[Annotated["FrontPortTemplateType", strawberry.lazy('dcim.graphql.types')]]
@@ -703,6 +725,16 @@ class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
def parent(self) -> Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None:
return self.parent
+ @strawberry_django.field
+ def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+ return self.cluster_set.all()
+
+ @strawberry_django.field
+ def circuit_terminations(self) -> List[
+ Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
+ ]:
+ return self.circuit_terminations.all()
+
@strawberry_django.type(
models.Site,
@@ -710,7 +742,6 @@ class RegionType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
filters=SiteFilter
)
class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObjectType):
- _name: str
time_zone: str | None
region: Annotated["RegionType", strawberry.lazy('dcim.graphql.types')] | None
group: Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None
@@ -728,6 +759,16 @@ class SiteType(VLANGroupsMixin, ImageAttachmentsMixin, ContactsMixin, NetBoxObje
clusters: List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]
vlans: List[Annotated["VLANType", strawberry.lazy('ipam.graphql.types')]]
+ @strawberry_django.field
+ def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+ return self.cluster_set.all()
+
+ @strawberry_django.field
+ def circuit_terminations(self) -> List[
+ Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
+ ]:
+ return self.circuit_terminations.all()
+
@strawberry_django.type(
models.SiteGroup,
@@ -744,6 +785,16 @@ class SiteGroupType(VLANGroupsMixin, ContactsMixin, OrganizationalObjectType):
def parent(self) -> Annotated["SiteGroupType", strawberry.lazy('dcim.graphql.types')] | None:
return self.parent
+ @strawberry_django.field
+ def clusters(self) -> List[Annotated["ClusterType", strawberry.lazy('virtualization.graphql.types')]]:
+ return self.cluster_set.all()
+
+ @strawberry_django.field
+ def circuit_terminations(self) -> List[
+ Annotated["CircuitTerminationType", strawberry.lazy('circuits.graphql.types')]
+ ]:
+ return self.circuit_terminations.all()
+
@strawberry_django.type(
models.VirtualChassis,
diff --git a/netbox/dcim/management/commands/buildschema.py b/netbox/dcim/management/commands/buildschema.py
index 44a0e95f2..529a2462c 100644
--- a/netbox/dcim/management/commands/buildschema.py
+++ b/netbox/dcim/management/commands/buildschema.py
@@ -6,6 +6,7 @@ from django.core.management.base import BaseCommand
from jinja2 import FileSystemLoader, Environment
from dcim.choices import *
+from netbox.choices import WeightUnitChoices
TEMPLATE_FILENAME = 'devicetype_schema.jinja2'
OUTPUT_FILENAME = 'contrib/generated_schema.json'
diff --git a/netbox/dcim/migrations/0001_squashed.py b/netbox/dcim/migrations/0001_squashed.py
index cf0ef4816..f08fe1d70 100644
--- a/netbox/dcim/migrations/0001_squashed.py
+++ b/netbox/dcim/migrations/0001_squashed.py
@@ -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',
diff --git a/netbox/dcim/migrations/0002_squashed.py b/netbox/dcim/migrations/0002_squashed.py
index 786167680..2e830560f 100644
--- a/netbox/dcim/migrations/0002_squashed.py
+++ b/netbox/dcim/migrations/0002_squashed.py
@@ -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',
+ ),
),
]
diff --git a/netbox/dcim/migrations/0003_squashed_0130.py b/netbox/dcim/migrations/0003_squashed_0130.py
index 592aaf9a8..0248d9ba1 100644
--- a/netbox/dcim/migrations/0003_squashed_0130.py
+++ b/netbox/dcim/migrations/0003_squashed_0130.py
@@ -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',
diff --git a/netbox/dcim/migrations/0131_squashed_0159.py b/netbox/dcim/migrations/0131_squashed_0159.py
index f7e7cfdb2..3866e8cc8 100644
--- a/netbox/dcim/migrations/0131_squashed_0159.py
+++ b/netbox/dcim/migrations/0131_squashed_0159.py
@@ -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',
diff --git a/netbox/dcim/migrations/0160_squashed_0166.py b/netbox/dcim/migrations/0160_squashed_0166.py
index 440a8115e..0deb58bab 100644
--- a/netbox/dcim/migrations/0160_squashed_0166.py
+++ b/netbox/dcim/migrations/0160_squashed_0166.py
@@ -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',
diff --git a/netbox/dcim/migrations/0167_squashed_0182.py b/netbox/dcim/migrations/0167_squashed_0182.py
index 735cb3efa..d0ad5379f 100644
--- a/netbox/dcim/migrations/0167_squashed_0182.py
+++ b/netbox/dcim/migrations/0167_squashed_0182.py
@@ -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',
diff --git a/netbox/dcim/migrations/0184_protect_child_interfaces.py b/netbox/dcim/migrations/0184_protect_child_interfaces.py
index 3459e23fc..58eca506d 100644
--- a/netbox/dcim/migrations/0184_protect_child_interfaces.py
+++ b/netbox/dcim/migrations/0184_protect_child_interfaces.py
@@ -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',
+ ),
),
]
diff --git a/netbox/dcim/migrations/0185_gfk_indexes.py b/netbox/dcim/migrations/0185_gfk_indexes.py
index 84cdc53ff..5c099b380 100644
--- a/netbox/dcim/migrations/0185_gfk_indexes.py
+++ b/netbox/dcim/migrations/0185_gfk_indexes.py
@@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
-
dependencies = [
('dcim', '0184_protect_child_interfaces'),
]
diff --git a/netbox/dcim/migrations/0186_location_facility.py b/netbox/dcim/migrations/0186_location_facility.py
index 759ee813b..3d22503b6 100644
--- a/netbox/dcim/migrations/0186_location_facility.py
+++ b/netbox/dcim/migrations/0186_location_facility.py
@@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
-
dependencies = [
('dcim', '0185_gfk_indexes'),
]
diff --git a/netbox/dcim/migrations/0187_alter_device_vc_position.py b/netbox/dcim/migrations/0187_alter_device_vc_position.py
index d4a42dc20..10b636959 100644
--- a/netbox/dcim/migrations/0187_alter_device_vc_position.py
+++ b/netbox/dcim/migrations/0187_alter_device_vc_position.py
@@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
-
dependencies = [
('dcim', '0186_location_facility'),
]
diff --git a/netbox/dcim/migrations/0188_racktype.py b/netbox/dcim/migrations/0188_racktype.py
index aa45246e5..a5265d030 100644
--- a/netbox/dcim/migrations/0188_racktype.py
+++ b/netbox/dcim/migrations/0188_racktype.py
@@ -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)),
diff --git a/netbox/dcim/migrations/0189_moduletype_rack_airflow.py b/netbox/dcim/migrations/0189_moduletype_rack_airflow.py
index 31787b67d..c356e32f7 100644
--- a/netbox/dcim/migrations/0189_moduletype_rack_airflow.py
+++ b/netbox/dcim/migrations/0189_moduletype_rack_airflow.py
@@ -2,7 +2,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
-
dependencies = [
('dcim', '0188_racktype'),
]
diff --git a/netbox/dcim/migrations/0190_nested_modules.py b/netbox/dcim/migrations/0190_nested_modules.py
index 9cef40efb..239e08639 100644
--- a/netbox/dcim/migrations/0190_nested_modules.py
+++ b/netbox/dcim/migrations/0190_nested_modules.py
@@ -4,7 +4,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
-
dependencies = [
('dcim', '0189_moduletype_rack_airflow'),
('extras', '0121_customfield_related_object_filter'),
@@ -34,12 +33,25 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='modulebay',
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='modulebay',
name='parent',
- field=mptt.fields.TreeForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='dcim.modulebay'),
+ field=mptt.fields.TreeForeignKey(
+ blank=True,
+ editable=False,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ related_name='children',
+ to='dcim.modulebay',
+ ),
),
migrations.AddField(
model_name='modulebay',
@@ -56,19 +68,35 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='modulebaytemplate',
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.AlterField(
model_name='modulebaytemplate',
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.AddConstraint(
model_name='modulebay',
- constraint=models.UniqueConstraint(fields=('device', 'module', 'name'), name='dcim_modulebay_unique_device_module_name'),
+ constraint=models.UniqueConstraint(
+ fields=('device', 'module', 'name'), name='dcim_modulebay_unique_device_module_name'
+ ),
),
migrations.AddConstraint(
model_name='modulebaytemplate',
- constraint=models.UniqueConstraint(fields=('module_type', 'name'), name='dcim_modulebaytemplate_unique_module_type_name'),
+ constraint=models.UniqueConstraint(
+ fields=('module_type', 'name'), name='dcim_modulebaytemplate_unique_module_type_name'
+ ),
),
]
diff --git a/netbox/dcim/migrations/0191_module_bay_rebuild.py b/netbox/dcim/migrations/0191_module_bay_rebuild.py
index 260063213..4f8a461f2 100644
--- a/netbox/dcim/migrations/0191_module_bay_rebuild.py
+++ b/netbox/dcim/migrations/0191_module_bay_rebuild.py
@@ -13,14 +13,10 @@ def rebuild_mptt(apps, schema_editor):
class Migration(migrations.Migration):
-
dependencies = [
('dcim', '0190_nested_modules'),
]
operations = [
- migrations.RunPython(
- code=rebuild_mptt,
- reverse_code=migrations.RunPython.noop
- ),
+ migrations.RunPython(code=rebuild_mptt, reverse_code=migrations.RunPython.noop),
]
diff --git a/netbox/dcim/migrations/0192_inventoryitem_status.py b/netbox/dcim/migrations/0192_inventoryitem_status.py
new file mode 100644
index 000000000..027f2daef
--- /dev/null
+++ b/netbox/dcim/migrations/0192_inventoryitem_status.py
@@ -0,0 +1,17 @@
+# Generated by Django 5.0.9 on 2024-09-26 20:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0191_module_bay_rebuild'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='inventoryitem',
+ name='status',
+ field=models.CharField(default='active', max_length=50),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0193_poweroutlet_color.py b/netbox/dcim/migrations/0193_poweroutlet_color.py
new file mode 100644
index 000000000..f7e3c430c
--- /dev/null
+++ b/netbox/dcim/migrations/0193_poweroutlet_color.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.0.9 on 2024-09-26 19:31
+
+import utilities.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0192_inventoryitem_status'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='poweroutlet',
+ name='color',
+ field=utilities.fields.ColorField(blank=True, max_length=6),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0194_charfield_null_choices.py b/netbox/dcim/migrations/0194_charfield_null_choices.py
new file mode 100644
index 000000000..e13b0e10d
--- /dev/null
+++ b/netbox/dcim/migrations/0194_charfield_null_choices.py
@@ -0,0 +1,291 @@
+import timezone_field.fields
+from django.db import migrations, models
+
+
+def set_null_values(apps, schema_editor):
+ """
+ Replace empty strings with null values.
+ """
+ Cable = apps.get_model('dcim', 'Cable')
+ ConsolePort = apps.get_model('dcim', 'ConsolePort')
+ ConsolePortTemplate = apps.get_model('dcim', 'ConsolePortTemplate')
+ ConsoleServerPort = apps.get_model('dcim', 'ConsoleServerPort')
+ ConsoleServerPortTemplate = apps.get_model('dcim', 'ConsoleServerPortTemplate')
+ Device = apps.get_model('dcim', 'Device')
+ DeviceType = apps.get_model('dcim', 'DeviceType')
+ FrontPort = apps.get_model('dcim', 'FrontPort')
+ Interface = apps.get_model('dcim', 'Interface')
+ InterfaceTemplate = apps.get_model('dcim', 'InterfaceTemplate')
+ ModuleType = apps.get_model('dcim', 'ModuleType')
+ PowerFeed = apps.get_model('dcim', 'PowerFeed')
+ PowerOutlet = apps.get_model('dcim', 'PowerOutlet')
+ PowerOutletTemplate = apps.get_model('dcim', 'PowerOutletTemplate')
+ PowerPort = apps.get_model('dcim', 'PowerPort')
+ PowerPortTemplate = apps.get_model('dcim', 'PowerPortTemplate')
+ Rack = apps.get_model('dcim', 'Rack')
+ RackType = apps.get_model('dcim', 'RackType')
+ RearPort = apps.get_model('dcim', 'RearPort')
+ Site = apps.get_model('dcim', 'Site')
+
+ Cable.objects.filter(length_unit='').update(length_unit=None)
+ Cable.objects.filter(type='').update(type=None)
+ ConsolePort.objects.filter(cable_end='').update(cable_end=None)
+ ConsolePort.objects.filter(type='').update(type=None)
+ ConsolePortTemplate.objects.filter(type='').update(type=None)
+ ConsoleServerPort.objects.filter(cable_end='').update(cable_end=None)
+ ConsoleServerPort.objects.filter(type='').update(type=None)
+ ConsoleServerPortTemplate.objects.filter(type='').update(type=None)
+ Device.objects.filter(airflow='').update(airflow=None)
+ Device.objects.filter(face='').update(face=None)
+ DeviceType.objects.filter(airflow='').update(airflow=None)
+ DeviceType.objects.filter(subdevice_role='').update(subdevice_role=None)
+ DeviceType.objects.filter(weight_unit='').update(weight_unit=None)
+ FrontPort.objects.filter(cable_end='').update(cable_end=None)
+ Interface.objects.filter(cable_end='').update(cable_end=None)
+ Interface.objects.filter(mode='').update(mode=None)
+ Interface.objects.filter(poe_mode='').update(poe_mode=None)
+ Interface.objects.filter(poe_type='').update(poe_type=None)
+ Interface.objects.filter(rf_channel='').update(rf_channel=None)
+ Interface.objects.filter(rf_role='').update(rf_role=None)
+ InterfaceTemplate.objects.filter(poe_mode='').update(poe_mode=None)
+ InterfaceTemplate.objects.filter(poe_type='').update(poe_type=None)
+ InterfaceTemplate.objects.filter(rf_role='').update(rf_role=None)
+ ModuleType.objects.filter(airflow='').update(airflow=None)
+ ModuleType.objects.filter(weight_unit='').update(weight_unit=None)
+ PowerFeed.objects.filter(cable_end='').update(cable_end=None)
+ PowerOutlet.objects.filter(cable_end='').update(cable_end=None)
+ PowerOutlet.objects.filter(feed_leg='').update(feed_leg=None)
+ PowerOutlet.objects.filter(type='').update(type=None)
+ PowerOutletTemplate.objects.filter(feed_leg='').update(feed_leg=None)
+ PowerOutletTemplate.objects.filter(type='').update(type=None)
+ PowerPort.objects.filter(cable_end='').update(cable_end=None)
+ PowerPort.objects.filter(type='').update(type=None)
+ PowerPortTemplate.objects.filter(type='').update(type=None)
+ Rack.objects.filter(airflow='').update(airflow=None)
+ Rack.objects.filter(form_factor='').update(form_factor=None)
+ Rack.objects.filter(outer_unit='').update(outer_unit=None)
+ Rack.objects.filter(weight_unit='').update(weight_unit=None)
+ RackType.objects.filter(outer_unit='').update(outer_unit=None)
+ RackType.objects.filter(weight_unit='').update(weight_unit=None)
+ RearPort.objects.filter(cable_end='').update(cable_end=None)
+ Site.objects.filter(time_zone='').update(time_zone=None)
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0193_poweroutlet_color'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='cable',
+ name='length_unit',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='cable',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='consoleport',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='consoleport',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='consoleporttemplate',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='consoleserverport',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='consoleserverport',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='consoleserverporttemplate',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='device',
+ name='airflow',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='device',
+ name='face',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='devicetype',
+ name='airflow',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='devicetype',
+ name='subdevice_role',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='devicetype',
+ name='weight_unit',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='frontport',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='mode',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='poe_mode',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='poe_type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='rf_channel',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='rf_role',
+ field=models.CharField(blank=True, max_length=30, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interfacetemplate',
+ name='poe_mode',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interfacetemplate',
+ name='poe_type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interfacetemplate',
+ name='rf_role',
+ field=models.CharField(blank=True, max_length=30, null=True),
+ ),
+ migrations.AlterField(
+ model_name='moduletype',
+ name='airflow',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='moduletype',
+ name='weight_unit',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='powerfeed',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlet',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlet',
+ name='feed_leg',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlet',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlettemplate',
+ name='feed_leg',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlettemplate',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='powerport',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='powerport',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='powerporttemplate',
+ name='type',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='rack',
+ name='airflow',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='rack',
+ name='form_factor',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='rack',
+ name='outer_unit',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='rack',
+ name='weight_unit',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='racktype',
+ name='outer_unit',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='racktype',
+ name='weight_unit',
+ field=models.CharField(blank=True, max_length=50, null=True),
+ ),
+ migrations.AlterField(
+ model_name='rearport',
+ name='cable_end',
+ field=models.CharField(blank=True, max_length=1, null=True),
+ ),
+ migrations.AlterField(
+ model_name='site',
+ name='time_zone',
+ field=timezone_field.fields.TimeZoneField(blank=True, null=True),
+ ),
+ migrations.RunPython(code=set_null_values, reverse_code=migrations.RunPython.noop),
+ ]
diff --git a/netbox/dcim/migrations/0195_interface_vlan_translation_policy.py b/netbox/dcim/migrations/0195_interface_vlan_translation_policy.py
new file mode 100644
index 000000000..9ec404886
--- /dev/null
+++ b/netbox/dcim/migrations/0195_interface_vlan_translation_policy.py
@@ -0,0 +1,21 @@
+# Generated by Django 5.0.9 on 2024-10-11 19:45
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0194_charfield_null_choices'),
+ ('ipam', '0074_vlantranslationpolicy_vlantranslationrule'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='interface',
+ name='vlan_translation_policy',
+ field=models.ForeignKey(
+ blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='ipam.vlantranslationpolicy'
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0196_qinq_svlan.py b/netbox/dcim/migrations/0196_qinq_svlan.py
new file mode 100644
index 000000000..a03ad144a
--- /dev/null
+++ b/netbox/dcim/migrations/0196_qinq_svlan.py
@@ -0,0 +1,39 @@
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0195_interface_vlan_translation_policy'),
+ ('ipam', '0075_vlan_qinq'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='interface',
+ name='qinq_svlan',
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='%(class)ss_svlan',
+ to='ipam.vlan',
+ ),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='tagged_vlans',
+ field=models.ManyToManyField(blank=True, related_name='%(class)ss_as_tagged', to='ipam.vlan'),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='untagged_vlan',
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='%(class)ss_as_untagged',
+ to='ipam.vlan',
+ ),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0197_natural_sort_collation.py b/netbox/dcim/migrations/0197_natural_sort_collation.py
new file mode 100644
index 000000000..268bda7eb
--- /dev/null
+++ b/netbox/dcim/migrations/0197_natural_sort_collation.py
@@ -0,0 +1,16 @@
+from django.contrib.postgres.operations import CreateCollation
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0196_qinq_svlan'),
+ ]
+
+ operations = [
+ CreateCollation(
+ 'natural_sort',
+ provider='icu',
+ locale='und-u-kn-true',
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0198_natural_ordering.py b/netbox/dcim/migrations/0198_natural_ordering.py
new file mode 100644
index 000000000..cf4361a2b
--- /dev/null
+++ b/netbox/dcim/migrations/0198_natural_ordering.py
@@ -0,0 +1,317 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0197_natural_sort_collation'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='site',
+ options={'ordering': ('name',)},
+ ),
+ migrations.AlterField(
+ model_name='site',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100, unique=True),
+ ),
+ migrations.AlterModelOptions(
+ name='consoleport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='consoleporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='consoleserverport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='consoleserverporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='device',
+ options={'ordering': ('name', 'pk')},
+ ),
+ migrations.AlterModelOptions(
+ name='devicebay',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='devicebaytemplate',
+ options={'ordering': ('device_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='frontport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='frontporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='interfacetemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='inventoryitem',
+ options={'ordering': ('device__id', 'parent__id', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='inventoryitemtemplate',
+ options={'ordering': ('device_type__id', 'parent__id', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='modulebay',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='modulebaytemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='poweroutlet',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='poweroutlettemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='powerport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='powerporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='rack',
+ options={'ordering': ('site', 'location', 'name', 'pk')},
+ ),
+ migrations.AlterModelOptions(
+ name='rearport',
+ options={'ordering': ('device', 'name')},
+ ),
+ migrations.AlterModelOptions(
+ name='rearporttemplate',
+ options={'ordering': ('device_type', 'module_type', 'name')},
+ ),
+ migrations.RemoveField(
+ model_name='consoleport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='consoleporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='consoleserverport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='consoleserverporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='device',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='devicebay',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='devicebaytemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='frontport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='frontporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='inventoryitem',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='inventoryitemtemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='modulebay',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='modulebaytemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='poweroutlet',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='poweroutlettemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='powerport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='powerporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='rack',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='rearport',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='rearporttemplate',
+ name='_name',
+ ),
+ migrations.RemoveField(
+ model_name='site',
+ name='_name',
+ ),
+ migrations.AlterField(
+ model_name='consoleport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='consoleporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='consoleserverport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='consoleserverporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='device',
+ name='name',
+ field=models.CharField(blank=True, db_collation='natural_sort', max_length=64, null=True),
+ ),
+ migrations.AlterField(
+ model_name='devicebay',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='devicebaytemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='frontport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='frontporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='interfacetemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='inventoryitem',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='inventoryitemtemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='modulebay',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='modulebaytemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlet',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='poweroutlettemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='powerport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='powerporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='rack',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='rearport',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='rearporttemplate',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='powerfeed',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='powerpanel',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=100),
+ ),
+ migrations.AlterField(
+ model_name='virtualchassis',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ migrations.AlterField(
+ model_name='virtualdevicecontext',
+ name='name',
+ field=models.CharField(db_collation='natural_sort', max_length=64),
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0199_macaddress.py b/netbox/dcim/migrations/0199_macaddress.py
new file mode 100644
index 000000000..ae18d5f63
--- /dev/null
+++ b/netbox/dcim/migrations/0199_macaddress.py
@@ -0,0 +1,51 @@
+import django.db.models.deletion
+import taggit.managers
+from django.db import migrations, models
+
+import dcim.fields
+import utilities.json
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0198_natural_ordering'),
+ ('extras', '0122_charfield_null_choices'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='MACAddress',
+ 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)),
+ ('mac_address', dcim.fields.MACAddressField()),
+ ('assigned_object_id', models.PositiveBigIntegerField(blank=True, null=True)),
+ (
+ 'assigned_object_type',
+ models.ForeignKey(
+ blank=True,
+ limit_choices_to=models.Q(
+ models.Q(
+ models.Q(('app_label', 'dcim'), ('model', 'interface')),
+ models.Q(('app_label', 'virtualization'), ('model', 'vminterface')),
+ _connector='OR',
+ )
+ ),
+ null=True,
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name='+',
+ to='contenttypes.contenttype',
+ ),
+ ),
+ ('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
+ ],
+ options={'abstract': False, 'ordering': ('mac_address',)},
+ ),
+ ]
diff --git a/netbox/dcim/migrations/0200_populate_mac_addresses.py b/netbox/dcim/migrations/0200_populate_mac_addresses.py
new file mode 100644
index 000000000..0cd18d78e
--- /dev/null
+++ b/netbox/dcim/migrations/0200_populate_mac_addresses.py
@@ -0,0 +1,46 @@
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+def populate_mac_addresses(apps, schema_editor):
+ ContentType = apps.get_model('contenttypes', 'ContentType')
+ Interface = apps.get_model('dcim', 'Interface')
+ MACAddress = apps.get_model('dcim', 'MACAddress')
+ interface_ct = ContentType.objects.get_for_model(Interface)
+
+ mac_addresses = [
+ MACAddress(
+ mac_address=interface.mac_address, assigned_object_type=interface_ct, assigned_object_id=interface.pk
+ )
+ for interface in Interface.objects.filter(mac_address__isnull=False)
+ ]
+ MACAddress.objects.bulk_create(mac_addresses, batch_size=100)
+
+ # TODO: Optimize interface updates
+ for mac_address in mac_addresses:
+ Interface.objects.filter(pk=mac_address.assigned_object_id).update(primary_mac_address=mac_address)
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('dcim', '0199_macaddress'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='interface',
+ name='primary_mac_address',
+ field=models.OneToOneField(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.SET_NULL,
+ related_name='+',
+ to='dcim.macaddress',
+ ),
+ ),
+ migrations.RunPython(code=populate_mac_addresses, reverse_code=migrations.RunPython.noop),
+ migrations.RemoveField(
+ model_name='interface',
+ name='mac_address',
+ ),
+ ]
diff --git a/netbox/dcim/models/cables.py b/netbox/dcim/models/cables.py
index 2a4748610..7117ea7e0 100644
--- a/netbox/dcim/models/cables.py
+++ b/netbox/dcim/models/cables.py
@@ -6,7 +6,6 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Sum
from django.dispatch import Signal
-from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from core.models import ObjectType
@@ -43,7 +42,8 @@ class Cable(PrimaryModel):
verbose_name=_('type'),
max_length=50,
choices=CableTypeChoices,
- blank=True
+ blank=True,
+ null=True
)
status = models.CharField(
verbose_name=_('status'),
@@ -79,6 +79,7 @@ class Cable(PrimaryModel):
max_length=50,
choices=CableLengthUnitChoices,
blank=True,
+ null=True
)
# Stores the normalized length (in meters) for database ordering
_abs_length = models.DecimalField(
@@ -116,9 +117,6 @@ class Cable(PrimaryModel):
pk = self.pk or self._pk
return self.label or f'#{pk}'
- def get_absolute_url(self):
- return reverse('dcim:cable', args=[self.pk])
-
@property
def a_terminations(self):
if hasattr(self, '_a_terminations'):
@@ -210,7 +208,7 @@ class Cable(PrimaryModel):
# Clear length_unit if no length is defined
if self.length is None:
- self.length_unit = ''
+ self.length_unit = None
super().save(*args, **kwargs)
@@ -346,7 +344,7 @@ class CableTermination(ChangeLoggedModel):
)
# A CircuitTermination attached to a ProviderNetwork cannot have a Cable
- if self.termination_type.model == 'circuittermination' and self.termination.provider_network is not None:
+ if self.termination_type.model == 'circuittermination' and self.termination._provider_network is not None:
raise ValidationError(_("Circuit terminations attached to a provider network may not be cabled."))
def save(self, *args, **kwargs):
@@ -369,7 +367,7 @@ class CableTermination(ChangeLoggedModel):
termination = self.termination._meta.model.objects.get(pk=self.termination_id)
termination.snapshot()
termination.cable = None
- termination.cable_end = ''
+ termination.cable_end = None
termination.save()
super().delete(*args, **kwargs)
@@ -607,6 +605,10 @@ class CablePath(models.Model):
cable_end = 'A' if lct.cable_end == 'B' else 'B'
q_filter |= Q(cable=lct.cable, cable_end=cable_end)
+ # Make sure this filter has been populated; if not, we have probably been given invalid data
+ if not q_filter:
+ break
+
remote_cable_terminations = CableTermination.objects.filter(q_filter)
remote_terminations = [ct.termination for ct in remote_cable_terminations]
else:
@@ -692,19 +694,19 @@ class CablePath(models.Model):
).first()
if circuit_termination is None:
break
- elif circuit_termination.provider_network:
+ elif circuit_termination._provider_network:
# Circuit terminates to a ProviderNetwork
path.extend([
[object_to_path_node(circuit_termination)],
- [object_to_path_node(circuit_termination.provider_network)],
+ [object_to_path_node(circuit_termination._provider_network)],
])
is_complete = True
break
- elif circuit_termination.site and not circuit_termination.cable:
- # Circuit terminates to a Site
+ elif circuit_termination.termination and not circuit_termination.cable:
+ # Circuit terminates to a Region/Site/etc.
path.extend([
[object_to_path_node(circuit_termination)],
- [object_to_path_node(circuit_termination.site)],
+ [object_to_path_node(circuit_termination.termination)],
])
break
diff --git a/netbox/dcim/models/device_component_templates.py b/netbox/dcim/models/device_component_templates.py
index 3a71c424d..b4f057711 100644
--- a/netbox/dcim/models/device_component_templates.py
+++ b/netbox/dcim/models/device_component_templates.py
@@ -44,12 +44,8 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
max_length=64,
help_text=_(
"{module} is accepted as a substitution for the module bay position when attached to a module type."
- )
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True
+ ),
+ db_collation="natural_sort"
)
label = models.CharField(
verbose_name=_('label'),
@@ -65,7 +61,7 @@ class ComponentTemplateModel(ChangeLoggedModel, TrackingModelMixin):
class Meta:
abstract = True
- ordering = ('device_type', '_name')
+ ordering = ('device_type', 'name')
constraints = (
models.UniqueConstraint(
fields=('device_type', 'name'),
@@ -125,7 +121,7 @@ class ModularComponentTemplateModel(ComponentTemplateModel):
class Meta:
abstract = True
- ordering = ('device_type', 'module_type', '_name')
+ ordering = ('device_type', 'module_type', 'name')
constraints = (
models.UniqueConstraint(
fields=('device_type', 'name'),
@@ -203,7 +199,8 @@ class ConsolePortTemplate(ModularComponentTemplateModel):
verbose_name=_('type'),
max_length=50,
choices=ConsolePortTypeChoices,
- blank=True
+ blank=True,
+ null=True
)
component_model = ConsolePort
@@ -237,7 +234,8 @@ class ConsoleServerPortTemplate(ModularComponentTemplateModel):
verbose_name=_('type'),
max_length=50,
choices=ConsolePortTypeChoices,
- blank=True
+ blank=True,
+ null=True
)
component_model = ConsoleServerPort
@@ -272,7 +270,8 @@ class PowerPortTemplate(ModularComponentTemplateModel):
verbose_name=_('type'),
max_length=50,
choices=PowerPortTypeChoices,
- blank=True
+ blank=True,
+ null=True
)
maximum_draw = models.PositiveIntegerField(
verbose_name=_('maximum draw'),
@@ -312,7 +311,9 @@ class PowerPortTemplate(ModularComponentTemplateModel):
if self.maximum_draw is not None and self.allocated_draw is not None:
if self.allocated_draw > self.maximum_draw:
raise ValidationError({
- 'allocated_draw': _("Allocated draw cannot exceed the maximum draw ({maximum_draw}W).").format(maximum_draw=self.maximum_draw)
+ 'allocated_draw': _(
+ "Allocated draw cannot exceed the maximum draw ({maximum_draw}W)."
+ ).format(maximum_draw=self.maximum_draw)
})
def to_yaml(self):
@@ -334,7 +335,8 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
verbose_name=_('type'),
max_length=50,
choices=PowerOutletTypeChoices,
- blank=True
+ blank=True,
+ null=True
)
power_port = models.ForeignKey(
to='dcim.PowerPortTemplate',
@@ -348,6 +350,7 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
max_length=50,
choices=PowerOutletFeedLegChoices,
blank=True,
+ null=True,
help_text=_('Phase (for three-phase feeds)')
)
@@ -364,11 +367,15 @@ class PowerOutletTemplate(ModularComponentTemplateModel):
if self.power_port:
if self.device_type and self.power_port.device_type != self.device_type:
raise ValidationError(
- _("Parent power port ({power_port}) must belong to the same device type").format(power_port=self.power_port)
+ _("Parent power port ({power_port}) must belong to the same device type").format(
+ power_port=self.power_port
+ )
)
if self.module_type and self.power_port.module_type != self.module_type:
raise ValidationError(
- _("Parent power port ({power_port}) must belong to the same module type").format(power_port=self.power_port)
+ _("Parent power port ({power_port}) must belong to the same module type").format(
+ power_port=self.power_port
+ )
)
def instantiate(self, **kwargs):
@@ -434,18 +441,21 @@ class InterfaceTemplate(ModularComponentTemplateModel):
max_length=50,
choices=InterfacePoEModeChoices,
blank=True,
+ null=True,
verbose_name=_('PoE mode')
)
poe_type = models.CharField(
max_length=50,
choices=InterfacePoETypeChoices,
blank=True,
+ null=True,
verbose_name=_('PoE type')
)
rf_role = models.CharField(
max_length=30,
choices=WirelessRoleChoices,
blank=True,
+ null=True,
verbose_name=_('wireless role')
)
@@ -463,11 +473,15 @@ class InterfaceTemplate(ModularComponentTemplateModel):
raise ValidationError({'bridge': _("An interface cannot be bridged to itself.")})
if self.device_type and self.device_type != self.bridge.device_type:
raise ValidationError({
- 'bridge': _("Bridge interface ({bridge}) must belong to the same device type").format(bridge=self.bridge)
+ 'bridge': _(
+ "Bridge interface ({bridge}) must belong to the same device type"
+ ).format(bridge=self.bridge)
})
if self.module_type and self.module_type != self.bridge.module_type:
raise ValidationError({
- 'bridge': _("Bridge interface ({bridge}) must belong to the same module type").format(bridge=self.bridge)
+ 'bridge': _(
+ "Bridge interface ({bridge}) must belong to the same module type"
+ ).format(bridge=self.bridge)
})
if self.rf_role and self.type not in WIRELESS_IFACE_TYPES:
@@ -710,7 +724,9 @@ class DeviceBayTemplate(ComponentTemplateModel):
def clean(self):
if self.device_type and self.device_type.subdevice_role != SubdeviceRoleChoices.ROLE_PARENT:
raise ValidationError(
- _("Subdevice role of device type ({device_type}) must be set to \"parent\" to allow device bays.").format(device_type=self.device_type)
+ _(
+ 'Subdevice role of device type ({device_type}) must be set to "parent" to allow device bays.'
+ ).format(device_type=self.device_type)
)
def to_yaml(self):
@@ -774,7 +790,7 @@ class InventoryItemTemplate(MPTTModel, ComponentTemplateModel):
component_model = InventoryItem
class Meta:
- ordering = ('device_type__id', 'parent__id', '_name')
+ ordering = ('device_type__id', 'parent__id', 'name')
indexes = (
models.Index(fields=('component_type', 'component_id')),
)
diff --git a/netbox/dcim/models/device_components.py b/netbox/dcim/models/device_components.py
index b1f951541..ce9e5607f 100644
--- a/netbox/dcim/models/device_components.py
+++ b/netbox/dcim/models/device_components.py
@@ -5,13 +5,12 @@ from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Sum
-from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
from dcim.choices import *
from dcim.constants import *
-from dcim.fields import MACAddressField, WWNField
+from dcim.fields import WWNField
from netbox.choices import ColorChoices
from netbox.models import OrganizationalModel, NetBoxModel
from utilities.fields import ColorField, NaturalOrderingField
@@ -51,12 +50,8 @@ class ComponentModel(NetBoxModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True
+ max_length=64,
+ db_collation="natural_sort"
)
label = models.CharField(
verbose_name=_('label'),
@@ -72,7 +67,7 @@ class ComponentModel(NetBoxModel):
class Meta:
abstract = True
- ordering = ('device', '_name')
+ ordering = ('device', 'name')
constraints = (
models.UniqueConstraint(
fields=('device', 'name'),
@@ -143,8 +138,9 @@ class CabledObjectModel(models.Model):
cable_end = models.CharField(
verbose_name=_('cable end'),
max_length=1,
+ choices=CableEndChoices,
blank=True,
- choices=CableEndChoices
+ null=True
)
mark_connected = models.BooleanField(
verbose_name=_('mark connected'),
@@ -284,6 +280,7 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
max_length=50,
choices=ConsolePortTypeChoices,
blank=True,
+ null=True,
help_text=_('Physical port type')
)
speed = models.PositiveIntegerField(
@@ -300,9 +297,6 @@ class ConsolePort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
verbose_name = _('console port')
verbose_name_plural = _('console ports')
- def get_absolute_url(self):
- return reverse('dcim:consoleport', kwargs={'pk': self.pk})
-
class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, TrackingModelMixin):
"""
@@ -313,6 +307,7 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint,
max_length=50,
choices=ConsolePortTypeChoices,
blank=True,
+ null=True,
help_text=_('Physical port type')
)
speed = models.PositiveIntegerField(
@@ -329,9 +324,6 @@ class ConsoleServerPort(ModularComponentModel, CabledObjectModel, PathEndpoint,
verbose_name = _('console server port')
verbose_name_plural = _('console server ports')
- def get_absolute_url(self):
- return reverse('dcim:consoleserverport', kwargs={'pk': self.pk})
-
#
# Power components
@@ -346,6 +338,7 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracking
max_length=50,
choices=PowerPortTypeChoices,
blank=True,
+ null=True,
help_text=_('Physical port type')
)
maximum_draw = models.PositiveIntegerField(
@@ -369,9 +362,6 @@ class PowerPort(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracking
verbose_name = _('power port')
verbose_name_plural = _('power ports')
- def get_absolute_url(self):
- return reverse('dcim:powerport', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -464,6 +454,7 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
max_length=50,
choices=PowerOutletTypeChoices,
blank=True,
+ null=True,
help_text=_('Physical port type')
)
power_port = models.ForeignKey(
@@ -478,8 +469,13 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
max_length=50,
choices=PowerOutletFeedLegChoices,
blank=True,
+ null=True,
help_text=_('Phase (for three-phase feeds)')
)
+ color = ColorField(
+ verbose_name=_('color'),
+ blank=True
+ )
clone_fields = ('device', 'module', 'type', 'power_port', 'feed_leg')
@@ -487,9 +483,6 @@ class PowerOutlet(ModularComponentModel, CabledObjectModel, PathEndpoint, Tracki
verbose_name = _('power outlet')
verbose_name_plural = _('power outlets')
- def get_absolute_url(self):
- return reverse('dcim:poweroutlet', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -512,11 +505,6 @@ class BaseInterface(models.Model):
verbose_name=_('enabled'),
default=True
)
- mac_address = MACAddressField(
- null=True,
- blank=True,
- verbose_name=_('MAC address')
- )
mtu = models.PositiveIntegerField(
blank=True,
null=True,
@@ -531,6 +519,7 @@ class BaseInterface(models.Model):
max_length=50,
choices=InterfaceModeChoices,
blank=True,
+ null=True,
help_text=_('IEEE 802.1Q tagging strategy')
)
parent = models.ForeignKey(
@@ -549,10 +538,64 @@ class BaseInterface(models.Model):
blank=True,
verbose_name=_('bridge interface')
)
+ untagged_vlan = models.ForeignKey(
+ to='ipam.VLAN',
+ on_delete=models.SET_NULL,
+ related_name='%(class)ss_as_untagged',
+ null=True,
+ blank=True,
+ verbose_name=_('untagged VLAN')
+ )
+ tagged_vlans = models.ManyToManyField(
+ to='ipam.VLAN',
+ related_name='%(class)ss_as_tagged',
+ blank=True,
+ verbose_name=_('tagged VLANs')
+ )
+ qinq_svlan = models.ForeignKey(
+ to='ipam.VLAN',
+ on_delete=models.SET_NULL,
+ related_name='%(class)ss_svlan',
+ null=True,
+ blank=True,
+ verbose_name=_('Q-in-Q SVLAN')
+ )
+ vlan_translation_policy = models.ForeignKey(
+ to='ipam.VLANTranslationPolicy',
+ on_delete=models.PROTECT,
+ null=True,
+ blank=True,
+ verbose_name=_('VLAN Translation Policy')
+ )
+ primary_mac_address = models.OneToOneField(
+ to='dcim.MACAddress',
+ on_delete=models.SET_NULL,
+ related_name='+',
+ blank=True,
+ null=True,
+ verbose_name=_('primary MAC address')
+ )
class Meta:
abstract = True
+ def clean(self):
+ super().clean()
+
+ # SVLAN can be defined only for Q-in-Q interfaces
+ if self.qinq_svlan and self.mode != InterfaceModeChoices.MODE_Q_IN_Q:
+ raise ValidationError({
+ 'qinq_svlan': _("Only Q-in-Q interfaces may specify a service VLAN.")
+ })
+
+ # Check that the primary MAC address (if any) is assigned to this interface
+ if self.primary_mac_address and self.primary_mac_address.assigned_object != self:
+ raise ValidationError({
+ 'primary_mac_address': _("MAC address {mac_address} is not assigned to this interface.").format(
+ mac_address=self.primary_mac_address
+ )
+ })
+
def save(self, *args, **kwargs):
# Remove untagged VLAN assignment for non-802.1Q interfaces
@@ -577,6 +620,11 @@ class BaseInterface(models.Model):
def count_fhrp_groups(self):
return self.fhrp_group_assignments.count()
+ @cached_property
+ def mac_address(self):
+ if self.primary_mac_address:
+ return self.primary_mac_address.mac_address
+
class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEndpoint, TrackingModelMixin):
"""
@@ -633,12 +681,14 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
max_length=30,
choices=WirelessRoleChoices,
blank=True,
+ null=True,
verbose_name=_('wireless role')
)
rf_channel = models.CharField(
max_length=50,
choices=WirelessChannelChoices,
blank=True,
+ null=True,
verbose_name=_('wireless channel')
)
rf_channel_frequency = models.DecimalField(
@@ -667,12 +717,14 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
max_length=50,
choices=InterfacePoEModeChoices,
blank=True,
+ null=True,
verbose_name=_('PoE mode')
)
poe_type = models.CharField(
max_length=50,
choices=InterfacePoETypeChoices,
blank=True,
+ null=True,
verbose_name=_('PoE type')
)
wireless_link = models.ForeignKey(
@@ -688,20 +740,6 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
blank=True,
verbose_name=_('wireless LANs')
)
- untagged_vlan = models.ForeignKey(
- to='ipam.VLAN',
- on_delete=models.SET_NULL,
- related_name='interfaces_as_untagged',
- null=True,
- blank=True,
- verbose_name=_('untagged VLAN')
- )
- tagged_vlans = models.ManyToManyField(
- to='ipam.VLAN',
- related_name='interfaces_as_tagged',
- blank=True,
- verbose_name=_('tagged VLANs')
- )
vrf = models.ForeignKey(
to='ipam.VRF',
on_delete=models.SET_NULL,
@@ -716,6 +754,12 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
object_id_field='assigned_object_id',
related_query_name='interface'
)
+ mac_addresses = GenericRelation(
+ to='dcim.MACAddress',
+ content_type_field='assigned_object_type',
+ object_id_field='assigned_object_id',
+ related_query_name='interface'
+ )
fhrp_group_assignments = GenericRelation(
to='ipam.FHRPGroupAssignment',
content_type_field='interface_type',
@@ -745,9 +789,6 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
verbose_name = _('interface')
verbose_name_plural = _('interfaces')
- def get_absolute_url(self):
- return reverse('dcim:interface', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -957,6 +998,14 @@ class Interface(ModularComponentModel, BaseInterface, CabledObjectModel, PathEnd
def l2vpn_termination(self):
return self.l2vpn_terminations.first()
+ @cached_property
+ def connected_endpoints(self):
+ # If this is a virtual interface, return the remote endpoint of the connected
+ # virtual circuit, if any.
+ if self.is_virtual and hasattr(self, 'virtual_circuit_termination'):
+ return self.virtual_circuit_termination.peer_terminations
+ return super().connected_endpoints
+
#
# Pass-through ports
@@ -1006,9 +1055,6 @@ class FrontPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
verbose_name = _('front port')
verbose_name_plural = _('front ports')
- def get_absolute_url(self):
- return reverse('dcim:frontport', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -1064,9 +1110,6 @@ class RearPort(ModularComponentModel, CabledObjectModel, TrackingModelMixin):
verbose_name = _('rear port')
verbose_name_plural = _('rear ports')
- def get_absolute_url(self):
- return reverse('dcim:rearport', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -1123,9 +1166,6 @@ class ModuleBay(ModularComponentModel, TrackingModelMixin, MPTTModel):
class MPTTMeta:
order_insertion_by = ('module',)
- def get_absolute_url(self):
- return reverse('dcim:modulebay', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -1164,9 +1204,6 @@ class DeviceBay(ComponentModel, TrackingModelMixin):
verbose_name = _('device bay')
verbose_name_plural = _('device bays')
- def get_absolute_url(self):
- return reverse('dcim:devicebay', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -1210,9 +1247,6 @@ class InventoryItemRole(OrganizationalModel):
verbose_name = _('inventory item role')
verbose_name_plural = _('inventory item roles')
- def get_absolute_url(self):
- return reverse('dcim:inventoryitemrole', args=[self.pk])
-
class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
"""
@@ -1243,6 +1277,12 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
ct_field='component_type',
fk_field='component_id'
)
+ status = models.CharField(
+ verbose_name=_('status'),
+ max_length=50,
+ choices=InventoryItemStatusChoices,
+ default=InventoryItemStatusChoices.STATUS_ACTIVE
+ )
role = models.ForeignKey(
to='dcim.InventoryItemRole',
on_delete=models.PROTECT,
@@ -1284,10 +1324,10 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
objects = TreeManager()
- clone_fields = ('device', 'parent', 'role', 'manufacturer', 'part_id',)
+ clone_fields = ('device', 'parent', 'role', 'manufacturer', 'status', 'part_id')
class Meta:
- ordering = ('device__id', 'parent__id', '_name')
+ ordering = ('device__id', 'parent__id', 'name')
indexes = (
models.Index(fields=('component_type', 'component_id')),
)
@@ -1300,9 +1340,6 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
verbose_name = _('inventory item')
verbose_name_plural = _('inventory items')
- def get_absolute_url(self):
- return reverse('dcim:inventoryitem', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -1333,3 +1370,6 @@ class InventoryItem(MPTTModel, ComponentModel, TrackingModelMixin):
raise ValidationError({
"device": _("Cannot assign inventory item to component on another device")
})
+
+ def get_status_color(self):
+ return InventoryItemStatusChoices.colors.get(self.status)
diff --git a/netbox/dcim/models/devices.py b/netbox/dcim/models/devices.py
index 0f11cb5f3..6ef5ab488 100644
--- a/netbox/dcim/models/devices.py
+++ b/netbox/dcim/models/devices.py
@@ -3,6 +3,7 @@ import yaml
from functools import cached_property
+from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import ValidationError
from django.core.files.storage import default_storage
from django.core.validators import MaxValueValidator, MinValueValidator
@@ -14,24 +15,28 @@ from django.urls import reverse
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
+from core.models import ObjectType
from dcim.choices import *
from dcim.constants import *
+from dcim.fields import MACAddressField
from extras.models import ConfigContextModel, CustomField
from extras.querysets import ConfigContextModelQuerySet
from netbox.choices import ColorChoices
from netbox.config import ConfigItem
from netbox.models import OrganizationalModel, PrimaryModel
+from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
-from utilities.fields import ColorField, CounterCacheField, NaturalOrderingField
+from utilities.fields import ColorField, CounterCacheField
from utilities.tracking import TrackingModelMixin
from .device_components import *
-from .mixins import RenderConfigMixin, WeightMixin
+from .mixins import RenderConfigMixin
__all__ = (
'Device',
'DeviceRole',
'DeviceType',
+ 'MACAddress',
'Manufacturer',
'Module',
'ModuleType',
@@ -54,9 +59,6 @@ class Manufacturer(ContactsMixin, OrganizationalModel):
verbose_name = _('manufacturer')
verbose_name_plural = _('manufacturers')
- def get_absolute_url(self):
- return reverse('dcim:manufacturer', args=[self.pk])
-
class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
"""
@@ -120,6 +122,7 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
max_length=50,
choices=SubdeviceRoleChoices,
blank=True,
+ null=True,
verbose_name=_('parent/child status'),
help_text=_('Parent devices house child devices in device bays. Leave blank '
'if this device type is neither a parent nor a child.')
@@ -128,7 +131,8 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
verbose_name=_('airflow'),
max_length=50,
choices=DeviceAirflowChoices,
- blank=True
+ blank=True,
+ null=True
)
front_image = models.ImageField(
upload_to='devicetype-images',
@@ -217,9 +221,6 @@ class DeviceType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
self._original_front_image = self.__dict__.get('front_image')
self._original_rear_image = self.__dict__.get('rear_image')
- def get_absolute_url(self):
- return reverse('dcim:devicetype', args=[self.pk])
-
@property
def full_name(self):
return f"{self.manufacturer} {self.model}"
@@ -392,7 +393,8 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
verbose_name=_('airflow'),
max_length=50,
choices=ModuleAirflowChoices,
- blank=True
+ blank=True,
+ null=True
)
clone_fields = ('manufacturer', 'weight', 'weight_unit', 'airflow')
@@ -414,9 +416,6 @@ class ModuleType(ImageAttachmentsMixin, PrimaryModel, WeightMixin):
def __str__(self):
return self.model
- def get_absolute_url(self):
- return reverse('dcim:moduletype', args=[self.pk])
-
@property
def full_name(self):
return f"{self.manufacturer} {self.model}"
@@ -497,9 +496,6 @@ class DeviceRole(OrganizationalModel):
verbose_name = _('device role')
verbose_name_plural = _('device roles')
- def get_absolute_url(self):
- return reverse('dcim:devicerole', args=[self.pk])
-
class Platform(OrganizationalModel):
"""
@@ -527,9 +523,6 @@ class Platform(OrganizationalModel):
verbose_name = _('platform')
verbose_name_plural = _('platforms')
- def get_absolute_url(self):
- return reverse('dcim:platform', args=[self.pk])
-
def update_interface_bridges(device, interface_templates, module=None):
"""
@@ -540,7 +533,10 @@ def update_interface_bridges(device, interface_templates, module=None):
interface = Interface.objects.get(device=device, name=interface_template.resolve_name(module=module))
if interface_template.bridge:
- interface.bridge = Interface.objects.get(device=device, name=interface_template.bridge.resolve_name(module=module))
+ interface.bridge = Interface.objects.get(
+ device=device,
+ name=interface_template.bridge.resolve_name(module=module)
+ )
interface.full_clean()
interface.save()
@@ -593,13 +589,8 @@ class Device(
verbose_name=_('name'),
max_length=64,
blank=True,
- null=True
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True,
- null=True
+ null=True,
+ db_collation="natural_sort"
)
serial = models.CharField(
max_length=50,
@@ -646,6 +637,7 @@ class Device(
face = models.CharField(
max_length=50,
blank=True,
+ null=True,
choices=DeviceFaceChoices,
verbose_name=_('rack face')
)
@@ -659,7 +651,8 @@ class Device(
verbose_name=_('airflow'),
max_length=50,
choices=DeviceAirflowChoices,
- blank=True
+ blank=True,
+ null=True
)
primary_ip4 = models.OneToOneField(
to='ipam.IPAddress',
@@ -784,7 +777,7 @@ class Device(
)
class Meta:
- ordering = ('_name', 'pk') # Name may be null
+ ordering = ('name', 'pk') # Name may be null
constraints = (
models.UniqueConstraint(
Lower('name'), 'site', 'tenant',
@@ -823,9 +816,6 @@ class Device(
return f'{self.device_type.manufacturer} {self.device_type.model} ({self.pk})'
return super().__str__()
- def get_absolute_url(self):
- return reverse('dcim:device', args=[self.pk])
-
def clean(self):
super().clean()
@@ -923,7 +913,10 @@ class Device(
})
if self.primary_ip4.assigned_object in vc_interfaces:
pass
- elif self.primary_ip4.nat_inside is not None and self.primary_ip4.nat_inside.assigned_object in vc_interfaces:
+ elif (
+ self.primary_ip4.nat_inside is not None and
+ self.primary_ip4.nat_inside.assigned_object in vc_interfaces
+ ):
pass
else:
raise ValidationError({
@@ -938,7 +931,10 @@ class Device(
})
if self.primary_ip6.assigned_object in vc_interfaces:
pass
- elif self.primary_ip6.nat_inside is not None and self.primary_ip6.nat_inside.assigned_object in vc_interfaces:
+ elif (
+ self.primary_ip6.nat_inside is not None and
+ self.primary_ip6.nat_inside.assigned_object in vc_interfaces
+ ):
pass
else:
raise ValidationError({
@@ -970,10 +966,17 @@ class Device(
})
# A Device can only be assigned to a Cluster in the same Site (or no Site)
- if self.cluster and self.cluster.site is not None and self.cluster.site != self.site:
+ if self.cluster and self.cluster._site is not None and self.cluster._site != self.site:
raise ValidationError({
'cluster': _("The assigned cluster belongs to a different site ({site})").format(
- site=self.cluster.site
+ site=self.cluster._site
+ )
+ })
+
+ if self.cluster and self.cluster._location is not None and self.cluster._location != self.location:
+ raise ValidationError({
+ 'cluster': _("The assigned cluster belongs to a different location ({location})").format(
+ site=self.cluster._location
)
})
@@ -985,9 +988,10 @@ class Device(
if hasattr(self, 'vc_master_for') and self.vc_master_for and self.vc_master_for != self.virtual_chassis:
raise ValidationError({
- 'virtual_chassis': _('Device cannot be removed from virtual chassis {virtual_chassis} because it is currently designated as its master.').format(
- virtual_chassis=self.vc_master_for
- )
+ 'virtual_chassis': _(
+ 'Device cannot be removed from virtual chassis {virtual_chassis} because it is currently '
+ 'designated as its master.'
+ ).format(virtual_chassis=self.vc_master_for)
})
def _instantiate_components(self, queryset, bulk_create=True):
@@ -1199,9 +1203,6 @@ class Module(PrimaryModel, ConfigContextModel):
def __str__(self):
return f'{self.module_bay.name}: {self.module_type} ({self.pk})'
- def get_absolute_url(self):
- return reverse('dcim:module', args=[self.pk])
-
def get_status_color(self):
return ModuleStatusChoices.colors.get(self.status)
@@ -1277,6 +1278,11 @@ class Module(PrimaryModel, ConfigContextModel):
if not disable_replication:
create_instances.append(template_instance)
+ # Set default values for any applicable custom fields
+ if cf_defaults := CustomField.objects.get_defaults_for_model(component_model):
+ for component in create_instances:
+ component.custom_field_data = cf_defaults
+
if component_model is not ModuleBay:
component_model.objects.bulk_create(create_instances)
# Emit the post_save signal for each newly created object
@@ -1328,7 +1334,8 @@ class VirtualChassis(PrimaryModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
+ max_length=64,
+ db_collation="natural_sort"
)
domain = models.CharField(
verbose_name=_('domain'),
@@ -1350,9 +1357,6 @@ class VirtualChassis(PrimaryModel):
def __str__(self):
return self.name
- def get_absolute_url(self):
- return reverse('dcim:virtualchassis', kwargs={'pk': self.pk})
-
def clean(self):
super().clean()
@@ -1393,7 +1397,8 @@ class VirtualDeviceContext(PrimaryModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=64
+ max_length=64,
+ db_collation="natural_sort"
)
status = models.CharField(
verbose_name=_('status'),
@@ -1452,9 +1457,6 @@ class VirtualDeviceContext(PrimaryModel):
def __str__(self):
return self.name
- def get_absolute_url(self):
- return reverse('dcim:virtualdevicecontext', kwargs={'pk': self.pk})
-
def get_status_color(self):
return VirtualDeviceContextStatusChoices.colors.get(self.status)
@@ -1487,3 +1489,61 @@ class VirtualDeviceContext(PrimaryModel):
raise ValidationError({
f'primary_ip{family}': _('Primary IP address must belong to an interface on the assigned device.')
})
+
+
+#
+# Addressing
+#
+
+class MACAddress(PrimaryModel):
+ mac_address = MACAddressField(
+ verbose_name=_('MAC address')
+ )
+ assigned_object_type = models.ForeignKey(
+ to='contenttypes.ContentType',
+ limit_choices_to=MACADDRESS_ASSIGNMENT_MODELS,
+ on_delete=models.PROTECT,
+ related_name='+',
+ blank=True,
+ null=True
+ )
+ assigned_object_id = models.PositiveBigIntegerField(
+ blank=True,
+ null=True
+ )
+ assigned_object = GenericForeignKey(
+ ct_field='assigned_object_type',
+ fk_field='assigned_object_id'
+ )
+
+ class Meta:
+ ordering = ('mac_address',)
+ verbose_name = _('MAC address')
+ verbose_name_plural = _('MAC addresses')
+
+ def __str__(self):
+ return str(self.mac_address)
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ # Denote the original assigned object (if any) for validation in clean()
+ self._original_assigned_object_id = self.__dict__.get('assigned_object_id')
+ self._original_assigned_object_type_id = self.__dict__.get('assigned_object_type_id')
+
+ def clean(self, *args, **kwargs):
+ super().clean()
+ if self._original_assigned_object_id and self._original_assigned_object_type_id:
+ assigned_object = self.assigned_object
+ ct = ObjectType.objects.get_for_id(self._original_assigned_object_type_id)
+ original_assigned_object = ct.get_object_for_this_type(pk=self._original_assigned_object_id)
+
+ if original_assigned_object.primary_mac_address:
+ if not assigned_object:
+ raise ValidationError(
+ _("Cannot unassign MAC Address while it is designated as the primary MAC for an object")
+ )
+ elif original_assigned_object != assigned_object:
+ raise ValidationError(
+ _("Cannot reassign MAC Address while it is designated as the primary MAC for an object")
+ )
diff --git a/netbox/dcim/models/mixins.py b/netbox/dcim/models/mixins.py
index d4a05699c..a0fc15a25 100644
--- a/netbox/dcim/models/mixins.py
+++ b/netbox/dcim/models/mixins.py
@@ -1,56 +1,16 @@
+from django.apps import apps
+from django.contrib.contenttypes.fields import GenericForeignKey
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _
-from dcim.choices import *
-from utilities.conversion import to_grams
+from dcim.constants import LOCATION_SCOPE_TYPES
__all__ = (
+ 'CachedScopeMixin',
'RenderConfigMixin',
- 'WeightMixin',
)
-class WeightMixin(models.Model):
- weight = models.DecimalField(
- verbose_name=_('weight'),
- max_digits=8,
- decimal_places=2,
- blank=True,
- null=True
- )
- weight_unit = models.CharField(
- verbose_name=_('weight unit'),
- max_length=50,
- choices=WeightUnitChoices,
- blank=True,
- )
- # Stores the normalized weight (in grams) for database ordering
- _abs_weight = models.PositiveBigIntegerField(
- blank=True,
- null=True
- )
-
- class Meta:
- abstract = True
-
- def save(self, *args, **kwargs):
-
- # Store the given weight (if any) in grams for use in database ordering
- if self.weight and self.weight_unit:
- self._abs_weight = to_grams(self.weight, self.weight_unit)
- else:
- self._abs_weight = None
-
- super().save(*args, **kwargs)
-
- def clean(self):
- super().clean()
-
- # Validate weight and weight_unit
- if self.weight and not self.weight_unit:
- raise ValidationError(_("Must specify a unit when setting a weight"))
-
-
class RenderConfigMixin(models.Model):
config_template = models.ForeignKey(
to='extras.ConfigTemplate',
@@ -73,3 +33,90 @@ class RenderConfigMixin(models.Model):
return self.role.config_template
if self.platform and self.platform.config_template:
return self.platform.config_template
+
+
+class CachedScopeMixin(models.Model):
+ """
+ Mixin for adding a GenericForeignKey scope to a model that can point to a Region, SiteGroup, Site, or Location.
+ Includes cached fields for each to allow efficient filtering. Appropriate validation must be done in the clean()
+ method as this does not have any as validation is generally model-specific.
+ """
+ scope_type = models.ForeignKey(
+ to='contenttypes.ContentType',
+ on_delete=models.PROTECT,
+ limit_choices_to=models.Q(model__in=LOCATION_SCOPE_TYPES),
+ related_name='+',
+ blank=True,
+ null=True
+ )
+ scope_id = models.PositiveBigIntegerField(
+ blank=True,
+ null=True
+ )
+ scope = GenericForeignKey(
+ ct_field='scope_type',
+ fk_field='scope_id'
+ )
+
+ _location = models.ForeignKey(
+ to='dcim.Location',
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True
+ )
+ _site = models.ForeignKey(
+ to='dcim.Site',
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True
+ )
+ _region = models.ForeignKey(
+ to='dcim.Region',
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True
+ )
+ _site_group = models.ForeignKey(
+ to='dcim.SiteGroup',
+ on_delete=models.CASCADE,
+ blank=True,
+ null=True
+ )
+
+ class Meta:
+ abstract = True
+
+ def clean(self):
+ if self.scope_type and not self.scope:
+ scope_type = self.scope_type.model_class()
+ raise ValidationError({
+ 'scope': _(
+ "Please select a {scope_type}."
+ ).format(scope_type=scope_type._meta.model_name)
+ })
+ super().clean()
+
+ def save(self, *args, **kwargs):
+ # Cache objects associated with the terminating object (for filtering)
+ self.cache_related_objects()
+
+ super().save(*args, **kwargs)
+
+ def cache_related_objects(self):
+ self._region = self._site_group = self._site = self._location = None
+ if self.scope_type:
+ scope_type = self.scope_type.model_class()
+ if scope_type == apps.get_model('dcim', 'region'):
+ self._region = self.scope
+ elif scope_type == apps.get_model('dcim', 'sitegroup'):
+ self._site_group = self.scope
+ elif scope_type == apps.get_model('dcim', 'site'):
+ self._region = self.scope.region
+ self._site_group = self.scope.group
+ self._site = self.scope
+ elif scope_type == apps.get_model('dcim', 'location'):
+ self._region = self.scope.site.region
+ self._site_group = self.scope.site.group
+ self._site = self.scope.site
+ self._location = self.scope
+ cache_related_objects.alters_data = True
diff --git a/netbox/dcim/models/power.py b/netbox/dcim/models/power.py
index 826eaae9c..284cfe832 100644
--- a/netbox/dcim/models/power.py
+++ b/netbox/dcim/models/power.py
@@ -1,7 +1,6 @@
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
-from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from dcim.choices import *
@@ -37,7 +36,8 @@ class PowerPanel(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
prerequisite_models = (
@@ -58,9 +58,6 @@ class PowerPanel(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
def __str__(self):
return self.name
- def get_absolute_url(self):
- return reverse('dcim:powerpanel', args=[self.pk])
-
def clean(self):
super().clean()
@@ -90,7 +87,8 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=100
+ max_length=100,
+ db_collation="natural_sort"
)
status = models.CharField(
verbose_name=_('status'),
@@ -167,9 +165,6 @@ class PowerFeed(PrimaryModel, PathEndpoint, CabledObjectModel):
def __str__(self):
return self.name
- def get_absolute_url(self):
- return reverse('dcim:powerfeed', args=[self.pk])
-
def clean(self):
super().clean()
diff --git a/netbox/dcim/models/racks.py b/netbox/dcim/models/racks.py
index 3aead09ca..7ecbd5d5f 100644
--- a/netbox/dcim/models/racks.py
+++ b/netbox/dcim/models/racks.py
@@ -8,7 +8,6 @@ from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Count
-from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from dcim.choices import *
@@ -16,13 +15,13 @@ from dcim.constants import *
from dcim.svg import RackElevationSVG
from netbox.choices import ColorChoices
from netbox.models import OrganizationalModel, PrimaryModel
+from netbox.models.mixins import WeightMixin
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
from utilities.conversion import to_grams
from utilities.data import array_to_string, drange
-from utilities.fields import ColorField, NaturalOrderingField
+from utilities.fields import ColorField
from .device_components import PowerPort
from .devices import Device, Module
-from .mixins import WeightMixin
from .power import PowerFeed
__all__ = (
@@ -84,7 +83,8 @@ class RackBase(WeightMixin, PrimaryModel):
verbose_name=_('outer unit'),
max_length=50,
choices=RackDimensionUnitChoices,
- blank=True
+ blank=True,
+ null=True
)
mounting_depth = models.PositiveSmallIntegerField(
verbose_name=_('mounting depth'),
@@ -165,9 +165,6 @@ class RackType(RackBase):
def __str__(self):
return self.model
- def get_absolute_url(self):
- return reverse('dcim:racktype', args=[self.pk])
-
@property
def full_name(self):
return f"{self.manufacturer} {self.model}"
@@ -192,7 +189,7 @@ class RackType(RackBase):
# Clear unit if outer width & depth are not set
if self.outer_width is None and self.outer_depth is None:
- self.outer_unit = ''
+ self.outer_unit = None
super().save(*args, **kwargs)
@@ -230,9 +227,6 @@ class RackRole(OrganizationalModel):
verbose_name = _('rack role')
verbose_name_plural = _('rack roles')
- def get_absolute_url(self):
- return reverse('dcim:rackrole', args=[self.pk])
-
class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
"""
@@ -249,6 +243,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
choices=RackFormFactorChoices,
max_length=50,
blank=True,
+ null=True,
verbose_name=_('form factor')
)
rack_type = models.ForeignKey(
@@ -260,12 +255,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
)
name = models.CharField(
verbose_name=_('name'),
- max_length=100
- )
- _name = NaturalOrderingField(
- target_field='name',
max_length=100,
- blank=True
+ db_collation="natural_sort"
)
facility_id = models.CharField(
max_length=50,
@@ -324,7 +315,8 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
verbose_name=_('airflow'),
max_length=50,
choices=RackAirflowChoices,
- blank=True
+ blank=True,
+ null=True
)
# Generic relations
@@ -344,7 +336,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
)
class Meta:
- ordering = ('site', 'location', '_name', 'pk') # (site, location, name) may be non-unique
+ ordering = ('site', 'location', 'name', 'pk') # (site, location, name) may be non-unique
constraints = (
# Name and facility_id must be unique *only* within a Location
models.UniqueConstraint(
@@ -364,9 +356,6 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
return f'{self.name} ({self.facility_id})'
return self.name
- def get_absolute_url(self):
- return reverse('dcim:rack', args=[self.pk])
-
def clean(self):
super().clean()
@@ -385,20 +374,27 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
if not self._state.adding:
mounted_devices = Device.objects.filter(rack=self).exclude(position__isnull=True).order_by('position')
+ effective_u_height = self.rack_type.u_height if self.rack_type else self.u_height
+ effective_starting_unit = self.rack_type.starting_unit if self.rack_type else self.starting_unit
+
# Validate that Rack is tall enough to house the highest mounted Device
if top_device := mounted_devices.last():
- min_height = top_device.position + top_device.device_type.u_height - self.starting_unit
- if self.u_height < min_height:
+ min_height = top_device.position + top_device.device_type.u_height - effective_starting_unit
+ if effective_u_height < min_height:
+ field = 'rack_type' if self.rack_type else 'u_height'
raise ValidationError({
- 'u_height': _("Rack must be at least {min_height}U tall to house currently installed devices.").format(min_height=min_height)
+ field: _(
+ "Rack must be at least {min_height}U tall to house currently installed devices."
+ ).format(min_height=min_height)
})
# Validate that the Rack's starting unit is less than or equal to the position of the lowest mounted Device
if last_device := mounted_devices.first():
- if self.starting_unit > last_device.position:
+ if effective_starting_unit > last_device.position:
+ field = 'rack_type' if self.rack_type else 'starting_unit'
raise ValidationError({
- 'starting_unit': _("Rack unit numbering must begin at {position} or less to house "
- "currently installed devices.").format(position=last_device.position)
+ field: _("Rack unit numbering must begin at {position} or less to house "
+ "currently installed devices.").format(position=last_device.position)
})
# Validate that Rack was assigned a Location of its same site, if applicable
@@ -419,7 +415,7 @@ class Rack(ContactsMixin, ImageAttachmentsMixin, RackBase):
# Clear unit if outer width & depth are not set
if self.outer_width is None and self.outer_depth is None:
- self.outer_unit = ''
+ self.outer_unit = None
super().save(*args, **kwargs)
@@ -699,9 +695,6 @@ class RackReservation(PrimaryModel):
def __str__(self):
return "Reservation for rack {}".format(self.rack)
- def get_absolute_url(self):
- return reverse('dcim:rackreservation', args=[self.pk])
-
def clean(self):
super().clean()
diff --git a/netbox/dcim/models/sites.py b/netbox/dcim/models/sites.py
index c1da807ad..7880a067f 100644
--- a/netbox/dcim/models/sites.py
+++ b/netbox/dcim/models/sites.py
@@ -1,7 +1,6 @@
from django.contrib.contenttypes.fields import GenericRelation
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 timezone_field import TimeZoneField
@@ -9,7 +8,6 @@ from dcim.choices import *
from dcim.constants import *
from netbox.models import NestedGroupModel, PrimaryModel
from netbox.models.features import ContactsMixin, ImageAttachmentsMixin
-from utilities.fields import NaturalOrderingField
__all__ = (
'Location',
@@ -29,6 +27,12 @@ class Region(ContactsMixin, NestedGroupModel):
states, and/or cities. Regions are recursively nested into a hierarchy: all sites belonging to a child region are
also considered to be members of its parent and ancestor region(s).
"""
+ prefixes = GenericRelation(
+ to='ipam.Prefix',
+ content_type_field='scope_type',
+ object_id_field='scope_id',
+ related_query_name='region'
+ )
vlan_groups = GenericRelation(
to='ipam.VLANGroup',
content_type_field='scope_type',
@@ -62,9 +66,6 @@ class Region(ContactsMixin, NestedGroupModel):
verbose_name = _('region')
verbose_name_plural = _('regions')
- def get_absolute_url(self):
- return reverse('dcim:region', args=[self.pk])
-
def get_site_count(self):
return Site.objects.filter(
Q(region=self) |
@@ -82,6 +83,12 @@ class SiteGroup(ContactsMixin, NestedGroupModel):
within corporate sites you might distinguish between offices and data centers. Like regions, site groups can be
nested recursively to form a hierarchy.
"""
+ prefixes = GenericRelation(
+ to='ipam.Prefix',
+ content_type_field='scope_type',
+ object_id_field='scope_id',
+ related_query_name='site_group'
+ )
vlan_groups = GenericRelation(
to='ipam.VLANGroup',
content_type_field='scope_type',
@@ -115,9 +122,6 @@ class SiteGroup(ContactsMixin, NestedGroupModel):
verbose_name = _('site group')
verbose_name_plural = _('site groups')
- def get_absolute_url(self):
- return reverse('dcim:sitegroup', args=[self.pk])
-
def get_site_count(self):
return Site.objects.filter(
Q(group=self) |
@@ -138,12 +142,8 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
verbose_name=_('name'),
max_length=100,
unique=True,
- help_text=_("Full name of the site")
- )
- _name = NaturalOrderingField(
- target_field='name',
- max_length=100,
- blank=True
+ help_text=_("Full name of the site"),
+ db_collation="natural_sort"
)
slug = models.SlugField(
verbose_name=_('slug'),
@@ -189,7 +189,8 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
blank=True
)
time_zone = TimeZoneField(
- blank=True
+ blank=True,
+ null=True
)
physical_address = models.CharField(
verbose_name=_('physical address'),
@@ -221,6 +222,12 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
)
# Generic relations
+ prefixes = GenericRelation(
+ to='ipam.Prefix',
+ content_type_field='scope_type',
+ object_id_field='scope_id',
+ related_query_name='site'
+ )
vlan_groups = GenericRelation(
to='ipam.VLANGroup',
content_type_field='scope_type',
@@ -234,16 +241,13 @@ class Site(ContactsMixin, ImageAttachmentsMixin, PrimaryModel):
)
class Meta:
- ordering = ('_name',)
+ ordering = ('name',)
verbose_name = _('site')
verbose_name_plural = _('sites')
def __str__(self):
return self.name
- def get_absolute_url(self):
- return reverse('dcim:site', args=[self.pk])
-
def get_status_color(self):
return SiteStatusChoices.colors.get(self.status)
@@ -283,6 +287,12 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel):
)
# Generic relations
+ prefixes = GenericRelation(
+ to='ipam.Prefix',
+ content_type_field='scope_type',
+ object_id_field='scope_id',
+ related_query_name='location'
+ )
vlan_groups = GenericRelation(
to='ipam.VLANGroup',
content_type_field='scope_type',
@@ -322,9 +332,6 @@ class Location(ContactsMixin, ImageAttachmentsMixin, NestedGroupModel):
verbose_name = _('location')
verbose_name_plural = _('locations')
- def get_absolute_url(self):
- return reverse('dcim:location', args=[self.pk])
-
def get_status_color(self):
return LocationStatusChoices.colors.get(self.status)
diff --git a/netbox/dcim/search.py b/netbox/dcim/search.py
index 45431cb05..b964421de 100644
--- a/netbox/dcim/search.py
+++ b/netbox/dcim/search.py
@@ -98,19 +98,28 @@ class FrontPortIndex(SearchIndex):
display_attrs = ('device', 'label', 'type', 'description')
+@register_search
+class MACAddressIndex(SearchIndex):
+ model = models.MACAddress
+ fields = (
+ ('mac_address', 100),
+ ('description', 500),
+ )
+ display_attrs = ('assigned_object', 'description')
+
+
@register_search
class InterfaceIndex(SearchIndex):
model = models.Interface
fields = (
('name', 100),
('label', 200),
- ('mac_address', 300),
('wwn', 300),
('description', 500),
('mtu', 2000),
('speed', 2000),
)
- display_attrs = ('device', 'label', 'type', 'mac_address', 'wwn', 'description')
+ display_attrs = ('device', 'label', 'type', 'wwn', 'description')
@register_search
diff --git a/netbox/dcim/signals.py b/netbox/dcim/signals.py
index a51872719..6c213d64c 100644
--- a/netbox/dcim/signals.py
+++ b/netbox/dcim/signals.py
@@ -85,7 +85,8 @@ def update_connected_endpoints(instance, created, raw=False, **kwargs):
if instance._terminations_modified:
a_terminations = []
b_terminations = []
- for t in instance.terminations.all():
+ # Note: instance.terminations.all() is not safe to use here as it might be stale
+ for t in CableTermination.objects.filter(cable=instance):
if t.cable_end == CableEndChoices.SIDE_A:
a_terminations.append(t.termination)
else:
diff --git a/netbox/dcim/svg/cables.py b/netbox/dcim/svg/cables.py
index 4e0f7aea6..58fa27c6b 100644
--- a/netbox/dcim/svg/cables.py
+++ b/netbox/dcim/svg/cables.py
@@ -362,7 +362,7 @@ class CableTraceSVG:
self.cursor += CABLE_HEIGHT
# Connector (a Cable or WirelessLink)
- if links:
+ if links and far_ends:
obj_list = {end.parent_object for end in far_ends}
parent_object_nodes, far_terminations = self.draw_far_objects(obj_list, far_ends)
diff --git a/netbox/dcim/svg/racks.py b/netbox/dcim/svg/racks.py
index 0f73095b5..94dbeeac2 100644
--- a/netbox/dcim/svg/racks.py
+++ b/netbox/dcim/svg/racks.py
@@ -48,6 +48,7 @@ def get_device_description(device):
Name: