1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Merge v3.1.6

This commit is contained in:
jeremystretch
2022-01-17 11:12:54 -05:00
parent 7767692394
commit 3e3880823b
33 changed files with 225 additions and 129 deletions

View File

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

View File

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

View File

@ -98,10 +98,6 @@ psycopg2-binary
# https://github.com/yaml/pyyaml
PyYAML
# In-memory key/value store used for caching and queuing
# https://github.com/andymccurdy/redis-py
redis
# Social authentication framework
# https://github.com/python-social-auth/social-core
social-auth-core[all]

View File

@ -1,5 +1,8 @@
# Plugin Development
!!! info "Help Improve the NetBox Plugins Framework!"
We're looking for volunteers to help improve NetBox's plugins framework. If you have experience developing plugins, we'd love to hear from you! You can find more information about this initiative [here](https://github.com/netbox-community/netbox/discussions/8338).
This documentation covers the development of custom plugins for NetBox. Plugins are essentially self-contained [Django apps](https://docs.djangoproject.com/en/stable/) which integrate with NetBox to provide custom functionality. Since the development of Django apps is already very well-documented, we'll only be covering the aspects that are specific to NetBox.
Plugins can do a lot, including:

View File

@ -1,16 +1,23 @@
# NetBox v3.1
## v3.1.6 (FUTURE)
## v3.1.7 (FUTURE)
---
## v3.1.6 (2022-01-17)
### Enhancements
* [#8246](https://github.com/netbox-community/netbox/issues/8246) - Show human-friendly values for commit rates in circuits table
* [#8262](https://github.com/netbox-community/netbox/issues/8262) - Add cable count to tenant stats
* [#8265](https://github.com/netbox-community/netbox/issues/8265) - Add Stackwise-n interface types
* [#8293](https://github.com/netbox-community/netbox/issues/8293) - Show 4-byte ASNs in ASDOT notation
* [#8302](https://github.com/netbox-community/netbox/issues/8302) - Linkify role column in device & VM tables
* [#8337](https://github.com/netbox-community/netbox/issues/8337) - Enable sorting object tables by created & updated times
### Bug Fixes
* [#8279](https://github.com/netbox-community/netbox/issues/8279) - Fix display of virtual chassis members in rack elevations
* [#8285](https://github.com/netbox-community/netbox/issues/8285) - Fix `cluster_count` under tenant REST API serializer
* [#8287](https://github.com/netbox-community/netbox/issues/8287) - Correct label in export template form
* [#8301](https://github.com/netbox-community/netbox/issues/8301) - Fix delete button for various object children views
@ -19,6 +26,9 @@
* [#8314](https://github.com/netbox-community/netbox/issues/8314) - Prevent custom fields with default values from appearing as applied filters erroneously
* [#8317](https://github.com/netbox-community/netbox/issues/8317) - Fix CSV import of multi-select custom field values
* [#8319](https://github.com/netbox-community/netbox/issues/8319) - Custom URL fields should honor `ALLOWED_URL_SCHEMES` config parameter
* [#8342](https://github.com/netbox-community/netbox/issues/8342) - Restore `created` & `last_updated` fields missing from several REST API serializers
* [#8357](https://github.com/netbox-community/netbox/issues/8357) - Add missing tags field to location filter form
* [#8358](https://github.com/netbox-community/netbox/issues/8358) - Fix inconsistent styling of custom fields on filter & bulk edit forms
---

View File

@ -100,5 +100,5 @@ class CircuitTerminationSerializer(ValidatedModelSerializer, LinkTerminationSeri
fields = [
'id', 'url', 'display', 'circuit', 'term_side', 'site', 'provider_network', 'port_speed', 'upstream_speed',
'xconnect_id', 'pp_info', 'description', 'mark_connected', 'cable', 'link_peer', 'link_peer_type',
'_occupied',
'_occupied', 'created', 'last_updated',
]

View File

@ -66,7 +66,7 @@ class ProviderTable(BaseTable):
model = Provider
fields = (
'pk', 'id', 'name', 'asn', 'account', 'portal_url', 'noc_contact', 'admin_contact', 'circuit_count',
'comments', 'tags',
'comments', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'asn', 'account', 'circuit_count')
@ -90,7 +90,9 @@ class ProviderNetworkTable(BaseTable):
class Meta(BaseTable.Meta):
model = ProviderNetwork
fields = ('pk', 'id', 'name', 'provider', 'service_id', 'description', 'comments', 'tags')
fields = (
'pk', 'id', 'name', 'provider', 'service_id', 'description', 'comments', 'created', 'last_updated', 'tags',
)
default_columns = ('pk', 'name', 'provider', 'service_id', 'description')
@ -112,7 +114,9 @@ class CircuitTypeTable(BaseTable):
class Meta(BaseTable.Meta):
model = CircuitType
fields = ('pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'circuit_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'circuit_count', 'description', 'slug')
@ -149,7 +153,7 @@ class CircuitTable(BaseTable):
model = Circuit
fields = (
'pk', 'id', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'install_date',
'commit_rate', 'description', 'comments', 'tags',
'commit_rate', 'description', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'cid', 'provider', 'type', 'status', 'tenant', 'termination_a', 'termination_z', 'description',

View File

@ -221,7 +221,7 @@ class RackReservationSerializer(PrimaryModelSerializer):
class Meta:
model = RackReservation
fields = [
'id', 'url', 'display', 'rack', 'units', 'created', 'user', 'tenant', 'description', 'tags',
'id', 'url', 'display', 'rack', 'units', 'created', 'last_updated', 'user', 'tenant', 'description', 'tags',
'custom_fields',
]
@ -913,7 +913,7 @@ class CableSerializer(PrimaryModelSerializer):
fields = [
'id', 'url', 'display', 'termination_a_type', 'termination_a_id', 'termination_a', 'termination_b_type',
'termination_b_id', 'termination_b', 'type', 'status', 'tenant', 'label', 'color', 'length', 'length_unit',
'tags', 'custom_fields',
'tags', 'custom_fields', 'created', 'last_updated',
]
def _get_termination(self, obj, side):
@ -1007,7 +1007,10 @@ class VirtualChassisSerializer(PrimaryModelSerializer):
class Meta:
model = VirtualChassis
fields = ['id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count']
fields = [
'id', 'url', 'display', 'name', 'domain', 'master', 'tags', 'custom_fields', 'member_count',
'created', 'last_updated',
]
#
@ -1026,7 +1029,10 @@ class PowerPanelSerializer(PrimaryModelSerializer):
class Meta:
model = PowerPanel
fields = ['id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count']
fields = [
'id', 'url', 'display', 'site', 'location', 'name', 'tags', 'custom_fields', 'powerfeed_count',
'created', 'last_updated',
]
class PowerFeedSerializer(PrimaryModelSerializer, LinkTerminationSerializer, ConnectedEndpointSerializer):

View File

@ -157,7 +157,7 @@ class SiteFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
class LocationFilterForm(TenancyFilterForm, CustomFieldModelFilterForm):
model = Location
field_groups = [
['q'],
['q', 'tag'],
['region_id', 'site_group_id', 'site_id', 'parent_id'],
['tenant_group_id', 'tenant_id'],
]

View File

@ -19,7 +19,12 @@ __all__ = (
def get_device_name(device):
return device.name or str(device.device_type)
if device.virtual_chassis:
return f'{device.virtual_chassis.name}:{device.vc_position}'
elif device.name:
return device.name
else:
return str(device.device_type)
class RackElevationSVG:

View File

@ -56,7 +56,7 @@ class CableTable(BaseTable):
model = Cable
fields = (
'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',
'status', 'type', 'tenant', 'color', 'length', 'tags',
'status', 'type', 'tenant', 'color', 'length', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'id', 'label', 'termination_a_parent', 'termination_a', 'termination_b_parent', 'termination_b',

View File

@ -99,7 +99,7 @@ class DeviceRoleTable(BaseTable):
model = DeviceRole
fields = (
'pk', 'id', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description', 'slug', 'tags',
'actions',
'actions', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'device_count', 'vm_count', 'color', 'vm_role', 'description')
@ -131,7 +131,7 @@ class PlatformTable(BaseTable):
model = Platform
fields = (
'pk', 'id', 'name', 'manufacturer', 'device_count', 'vm_count', 'slug', 'napalm_driver', 'napalm_args',
'description', 'tags', 'actions',
'description', 'tags', 'actions', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'manufacturer', 'device_count', 'vm_count', 'napalm_driver', 'description',
@ -205,7 +205,8 @@ class DeviceTable(BaseTable):
fields = (
'pk', 'id', 'name', 'status', 'tenant', 'device_role', 'manufacturer', 'device_type', 'platform', 'serial',
'asset_tag', 'site', 'location', 'rack', 'position', 'face', 'primary_ip', 'airflow', 'primary_ip4',
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags',
'primary_ip6', 'cluster', 'virtual_chassis', 'vc_position', 'vc_priority', 'comments', 'tags', 'created',
'last_updated',
)
default_columns = (
'pk', 'name', 'status', 'tenant', 'site', 'location', 'rack', 'device_role', 'manufacturer', 'device_type',
@ -311,7 +312,7 @@ class ConsolePortTable(ModularDeviceComponentTable, PathEndpointTable):
model = ConsolePort
fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
@ -353,7 +354,7 @@ class ConsoleServerPortTable(ModularDeviceComponentTable, PathEndpointTable):
model = ConsoleServerPort
fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'speed', 'description',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'type', 'speed', 'description')
@ -396,7 +397,8 @@ class PowerPortTable(ModularDeviceComponentTable, PathEndpointTable):
model = PowerPort
fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'mark_connected',
'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
'maximum_draw', 'allocated_draw', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'type', 'maximum_draw', 'allocated_draw', 'description')
@ -444,7 +446,8 @@ class PowerOutletTable(ModularDeviceComponentTable, PathEndpointTable):
model = PowerOutlet
fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'description', 'power_port',
'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags',
'feed_leg', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'tags', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'type', 'power_port', 'feed_leg', 'description')
@ -524,6 +527,7 @@ class InterfaceTable(ModularDeviceComponentTable, BaseInterfaceTable, PathEndpoi
'mode', 'mac_address', 'wwn', 'rf_role', 'rf_channel', 'rf_channel_frequency', 'rf_channel_width',
'tx_power', 'description', 'mark_connected', 'cable', 'cable_color', 'wireless_link', 'wireless_lans',
'link_peer', 'connection', 'tags', 'vrf', 'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans',
'created', 'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'enabled', 'type', 'description')
@ -594,6 +598,7 @@ class FrontPortTable(ModularDeviceComponentTable, CableTerminationTable):
fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'rear_port',
'rear_port_position', 'description', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'device', 'label', 'type', 'color', 'rear_port', 'rear_port_position', 'description',
@ -641,7 +646,7 @@ class RearPortTable(ModularDeviceComponentTable, CableTerminationTable):
model = RearPort
fields = (
'pk', 'id', 'name', 'device', 'module_bay', 'module', 'label', 'type', 'color', 'positions', 'description',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags',
'mark_connected', 'cable', 'cable_color', 'link_peer', 'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'type', 'color', 'description')
@ -691,7 +696,11 @@ class DeviceBayTable(DeviceComponentTable):
class Meta(DeviceComponentTable.Meta):
model = DeviceBay
fields = ('pk', 'id', 'name', 'device', 'label', 'status', 'installed_device', 'description', 'tags')
fields = (
'pk', 'id', 'name', 'device', 'label', 'status', 'installed_device', 'description', 'tags',
'created', 'last_updated',
)
default_columns = ('pk', 'name', 'device', 'label', 'status', 'installed_device', 'description')
@ -774,7 +783,7 @@ class InventoryItemTable(DeviceComponentTable):
model = InventoryItem
fields = (
'pk', 'id', 'name', 'device', 'component', 'label', 'role', 'manufacturer', 'part_id', 'serial',
'asset_tag', 'description', 'discovered', 'tags',
'asset_tag', 'description', 'discovered', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'device', 'label', 'role', 'manufacturer', 'part_id', 'serial', 'asset_tag',
@ -847,5 +856,5 @@ class VirtualChassisTable(BaseTable):
class Meta(BaseTable.Meta):
model = VirtualChassis
fields = ('pk', 'id', 'name', 'domain', 'master', 'member_count', 'tags')
fields = ('pk', 'id', 'name', 'domain', 'master', 'member_count', 'tags', 'created', 'last_updated',)
default_columns = ('pk', 'name', 'domain', 'master', 'member_count')

View File

@ -53,7 +53,7 @@ class ManufacturerTable(BaseTable):
model = Manufacturer
fields = (
'pk', 'id', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug',
'actions',
'actions', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'devicetype_count', 'inventoryitem_count', 'platform_count', 'description', 'slug',
@ -87,7 +87,7 @@ class DeviceTypeTable(BaseTable):
model = DeviceType
fields = (
'pk', 'id', 'model', 'manufacturer', 'slug', 'part_number', 'u_height', 'is_full_depth', 'subdevice_role',
'airflow', 'comments', 'instance_count', 'tags',
'airflow', 'comments', 'instance_count', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'model', 'manufacturer', 'part_number', 'u_height', 'is_full_depth', 'instance_count',

View File

@ -33,7 +33,7 @@ class PowerPanelTable(BaseTable):
class Meta(BaseTable.Meta):
model = PowerPanel
fields = ('pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'tags')
fields = ('pk', 'id', 'name', 'site', 'location', 'powerfeed_count', 'tags', 'created', 'last_updated',)
default_columns = ('pk', 'name', 'site', 'location', 'powerfeed_count')
@ -72,7 +72,7 @@ class PowerFeedTable(CableTerminationTable):
fields = (
'pk', 'id', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase',
'max_utilization', 'mark_connected', 'cable', 'cable_color', 'link_peer', 'connection', 'available_power',
'comments', 'tags',
'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'power_panel', 'rack', 'status', 'type', 'supply', 'voltage', 'amperage', 'phase', 'cable',

View File

@ -30,7 +30,10 @@ class RackRoleTable(BaseTable):
class Meta(BaseTable.Meta):
model = RackRole
fields = ('pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'rack_count', 'color', 'description', 'slug', 'tags', 'actions', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'rack_count', 'color', 'description')
@ -86,8 +89,9 @@ class RackTable(BaseTable):
class Meta(BaseTable.Meta):
model = Rack
fields = (
'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag', 'type',
'width', 'outer_width', 'outer_depth', 'u_height', 'comments', 'device_count', 'get_utilization', 'get_power_utilization', 'tags',
'pk', 'id', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'serial', 'asset_tag',
'type', 'width', 'outer_width', 'outer_depth', 'u_height', 'comments', 'device_count', 'get_utilization',
'get_power_utilization', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'site', 'location', 'status', 'facility_id', 'tenant', 'role', 'u_height', 'device_count',
@ -125,6 +129,6 @@ class RackReservationTable(BaseTable):
model = RackReservation
fields = (
'pk', 'id', 'reservation', 'site', 'rack', 'unit_list', 'user', 'created', 'tenant', 'description', 'tags',
'actions',
'actions', 'created', 'last_updated',
)
default_columns = ('pk', 'reservation', 'site', 'rack', 'unit_list', 'user', 'description')

View File

@ -35,7 +35,9 @@ class RegionTable(BaseTable):
class Meta(BaseTable.Meta):
model = Region
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'site_count', 'description')
@ -59,7 +61,9 @@ class SiteGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = SiteGroup
fields = ('pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'slug', 'site_count', 'description', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'site_count', 'description')
@ -96,7 +100,7 @@ class SiteTable(BaseTable):
fields = (
'pk', 'id', 'name', 'slug', 'status', 'facility', 'region', 'group', 'tenant', 'asn_count', 'time_zone',
'description', 'physical_address', 'shipping_address', 'latitude', 'longitude', 'comments', 'tags',
'actions',
'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'status', 'facility', 'region', 'group', 'tenant', 'description')
@ -135,6 +139,6 @@ class LocationTable(BaseTable):
model = Location
fields = (
'pk', 'id', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description', 'slug', 'tags',
'actions',
'actions', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'site', 'tenant', 'rack_count', 'device_count', 'description')

View File

@ -63,7 +63,7 @@ class WebhookSerializer(ValidatedModelSerializer):
fields = [
'id', 'url', 'display', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url',
'enabled', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
'conditions', 'ssl_verification', 'ca_file_path',
'conditions', 'ssl_verification', 'ca_file_path', 'created', 'last_updated',
]
@ -84,7 +84,8 @@ class CustomFieldSerializer(ValidatedModelSerializer):
model = CustomField
fields = [
'id', 'url', 'display', 'content_types', 'type', 'name', 'label', 'description', 'required', 'filter_logic',
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices',
'default', 'weight', 'validation_minimum', 'validation_maximum', 'validation_regex', 'choices', 'created',
'last_updated',
]
@ -102,7 +103,7 @@ class CustomLinkSerializer(ValidatedModelSerializer):
model = CustomLink
fields = [
'id', 'url', 'display', 'content_type', 'name', 'enabled', 'link_text', 'link_url', 'weight', 'group_name',
'button_class', 'new_window',
'button_class', 'new_window', 'created', 'last_updated',
]
@ -120,7 +121,7 @@ class ExportTemplateSerializer(ValidatedModelSerializer):
model = ExportTemplate
fields = [
'id', 'url', 'display', 'content_type', 'name', 'description', 'template_code', 'mime_type',
'file_extension', 'as_attachment',
'file_extension', 'as_attachment', 'created', 'last_updated',
]
@ -134,7 +135,9 @@ class TagSerializer(ValidatedModelSerializer):
class Meta:
model = Tag
fields = ['id', 'url', 'display', 'name', 'slug', 'color', 'description', 'tagged_items']
fields = [
'id', 'url', 'display', 'name', 'slug', 'color', 'description', 'tagged_items', 'created', 'last_updated',
]
#

View File

@ -4,7 +4,7 @@ from django.db.models import Q
from extras.choices import *
from extras.models import *
from utilities.forms import BootstrapMixin, BulkEditForm, CSVModelForm, FilterForm
from utilities.forms import BootstrapMixin, BulkEditBaseForm, CSVModelForm
__all__ = (
'CustomFieldModelCSVForm',
@ -34,6 +34,9 @@ class CustomFieldsMixin:
raise NotImplementedError(f"{self.__class__.__name__} must specify a model class.")
return ContentType.objects.get_for_model(self.model)
def _get_custom_fields(self, content_type):
return CustomField.objects.filter(content_types=content_type)
def _get_form_field(self, customfield):
return customfield.to_form_field()
@ -41,10 +44,7 @@ class CustomFieldsMixin:
"""
Append form fields for all CustomFields assigned to this object type.
"""
content_type = self._get_content_type()
# Append form fields; assign initial values if modifying and existing object
for customfield in CustomField.objects.filter(content_types=content_type):
for customfield in self._get_custom_fields(self._get_content_type()):
field_name = f'cf_{customfield.name}'
self.fields[field_name] = self._get_form_field(customfield)
@ -89,40 +89,37 @@ class CustomFieldModelCSVForm(CSVModelForm, CustomFieldModelForm):
return customfield.to_form_field(for_csv_import=True)
class CustomFieldModelBulkEditForm(BulkEditForm):
class CustomFieldModelBulkEditForm(BootstrapMixin, CustomFieldsMixin, BulkEditBaseForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _get_form_field(self, customfield):
return customfield.to_form_field(set_initial=False, enforce_required=False)
self.custom_fields = []
self.obj_type = ContentType.objects.get_for_model(self.model)
# Add all applicable CustomFields to the form
custom_fields = CustomField.objects.filter(content_types=self.obj_type)
for cf in custom_fields:
def _append_customfield_fields(self):
"""
Append form fields for all CustomFields assigned to this object type.
"""
for customfield in self._get_custom_fields(self._get_content_type()):
# Annotate non-required custom fields as nullable
if not cf.required:
self.nullable_fields.append(cf.name)
self.fields[cf.name] = cf.to_form_field(set_initial=False, enforce_required=False)
# Annotate this as a custom field
self.custom_fields.append(cf.name)
if not customfield.required:
self.nullable_fields.append(customfield.name)
self.fields[customfield.name] = self._get_form_field(customfield)
# Annotate the field in the list of CustomField form fields
self.custom_fields[customfield.name] = customfield
class CustomFieldModelFilterForm(FilterForm):
class CustomFieldModelFilterForm(BootstrapMixin, CustomFieldsMixin, forms.Form):
q = forms.CharField(
required=False,
label='Search'
)
def __init__(self, *args, **kwargs):
self.obj_type = ContentType.objects.get_for_model(self.model)
super().__init__(*args, **kwargs)
# Add all applicable CustomFields to the form
self.custom_field_filters = []
custom_fields = CustomField.objects.filter(content_types=self.obj_type).exclude(
def _get_custom_fields(self, content_type):
return CustomField.objects.filter(content_types=content_type).exclude(
Q(filter_logic=CustomFieldFilterLogicChoices.FILTER_DISABLED) |
Q(type=CustomFieldTypeChoices.TYPE_JSON)
)
for cf in custom_fields:
field_name = f'cf_{cf.name}'
self.fields[field_name] = cf.to_form_field(set_initial=False, enforce_required=False)
self.custom_field_filters.append(field_name)
def _get_form_field(self, customfield):
return customfield.to_form_field(set_initial=False, enforce_required=False)

View File

@ -58,7 +58,7 @@ class CustomFieldTable(BaseTable):
model = CustomField
fields = (
'pk', 'id', 'name', 'content_types', 'label', 'type', 'required', 'weight', 'default',
'description', 'filter_logic', 'choices',
'description', 'filter_logic', 'choices', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'content_types', 'label', 'type', 'required', 'description')
@ -80,7 +80,7 @@ class CustomLinkTable(BaseTable):
model = CustomLink
fields = (
'pk', 'id', 'name', 'content_type', 'enabled', 'link_text', 'link_url', 'weight', 'group_name',
'button_class', 'new_window',
'button_class', 'new_window', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'content_type', 'enabled', 'group_name', 'button_class', 'new_window')
@ -101,6 +101,7 @@ class ExportTemplateTable(BaseTable):
model = ExportTemplate
fields = (
'pk', 'id', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'content_type', 'description', 'mime_type', 'file_extension', 'as_attachment',
@ -135,7 +136,7 @@ class WebhookTable(BaseTable):
model = Webhook
fields = (
'pk', 'id', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
'payload_url', 'secret', 'ssl_validation', 'ca_file_path',
'payload_url', 'secret', 'ssl_validation', 'ca_file_path', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
@ -156,7 +157,7 @@ class TagTable(BaseTable):
class Meta(BaseTable.Meta):
model = Tag
fields = ('pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'actions')
fields = ('pk', 'id', 'name', 'items', 'slug', 'color', 'description', 'created', 'last_updated', 'actions')
default_columns = ('pk', 'name', 'items', 'slug', 'color', 'description')
@ -193,7 +194,8 @@ class ConfigContextTable(BaseTable):
model = ConfigContext
fields = (
'pk', 'id', 'name', 'weight', 'is_active', 'description', 'regions', 'sites', 'roles',
'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants',
'platforms', 'cluster_types', 'cluster_groups', 'clusters', 'tenant_groups', 'tenants', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'weight', 'is_active', 'description')

View File

@ -125,11 +125,30 @@ class ASN(PrimaryModel):
verbose_name_plural = 'ASNs'
def __str__(self):
return f'AS{self.asn}'
return f'AS{self.asn_with_asdot}'
def get_absolute_url(self):
return reverse('ipam:asn', args=[self.pk])
@property
def asn_asdot(self):
"""
Return ASDOT notation for AS numbers greater than 16 bits.
"""
if self.asn > 65535:
return f'{self.asn // 65536}.{self.asn % 65536}'
return self.asn
@property
def asn_with_asdot(self):
"""
Return both plain and ASDOT notation, where applicable.
"""
if self.asn > 65535:
return f'{self.asn} ({self.asn // 65536}.{self.asn % 65536})'
else:
return self.asn
@extras_features('custom_fields', 'custom_links', 'export_templates', 'tags', 'webhooks')
class Aggregate(GetAvailablePrefixesMixin, PrimaryModel):

View File

@ -38,7 +38,7 @@ class FHRPGroupTable(BaseTable):
model = FHRPGroup
fields = (
'pk', 'group_id', 'protocol', 'auth_type', 'auth_key', 'description', 'ip_addresses', 'interface_count',
'tags',
'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'group_id', 'protocol', 'auth_type', 'description', 'ip_addresses', 'interface_count')

View File

@ -91,7 +91,10 @@ class RIRTable(BaseTable):
class Meta(BaseTable.Meta):
model = RIR
fields = ('pk', 'id', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'slug', 'is_private', 'aggregate_count', 'description', 'tags', 'created',
'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'is_private', 'aggregate_count', 'description')
@ -102,8 +105,10 @@ class RIRTable(BaseTable):
class ASNTable(BaseTable):
pk = ToggleColumn()
asn = tables.Column(
accessor=tables.A('asn_asdot'),
linkify=True
)
site_count = LinkedCountColumn(
viewname='dcim:site_list',
url_params={'asn_id': 'pk'},
@ -112,7 +117,7 @@ class ASNTable(BaseTable):
class Meta(BaseTable.Meta):
model = ASN
fields = ('pk', 'asn', 'rir', 'site_count', 'tenant', 'description', 'actions')
fields = ('pk', 'asn', 'rir', 'site_count', 'tenant', 'description', 'created', 'last_updated', 'actions')
default_columns = ('pk', 'asn', 'rir', 'site_count', 'sites', 'tenant')
@ -144,7 +149,10 @@ class AggregateTable(BaseTable):
class Meta(BaseTable.Meta):
model = Aggregate
fields = ('pk', 'id', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags')
fields = (
'pk', 'id', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description', 'tags',
'created', 'last_updated',
)
default_columns = ('pk', 'prefix', 'rir', 'tenant', 'child_count', 'utilization', 'date_added', 'description')
@ -173,7 +181,10 @@ class RoleTable(BaseTable):
class Meta(BaseTable.Meta):
model = Role
fields = ('pk', 'id', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'slug', 'prefix_count', 'vlan_count', 'description', 'weight', 'tags', 'created',
'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'prefix_count', 'vlan_count', 'description')
@ -260,8 +271,8 @@ class PrefixTable(BaseTable):
class Meta(BaseTable.Meta):
model = Prefix
fields = (
'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan_group',
'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'tags',
'pk', 'id', 'prefix', 'prefix_flat', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site',
'vlan_group', 'vlan', 'role', 'is_pool', 'mark_utilized', 'description', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'prefix', 'status', 'children', 'vrf', 'utilization', 'tenant', 'site', 'vlan', 'role', 'description',
@ -302,7 +313,7 @@ class IPRangeTable(BaseTable):
model = IPRange
fields = (
'pk', 'id', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
'utilization', 'tags',
'utilization', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'start_address', 'end_address', 'size', 'vrf', 'status', 'role', 'tenant', 'description',
@ -360,7 +371,7 @@ class IPAddressTable(BaseTable):
model = IPAddress
fields = (
'pk', 'id', 'address', 'vrf', 'status', 'role', 'tenant', 'nat_inside', 'assigned', 'dns_name', 'description',
'tags',
'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'address', 'vrf', 'status', 'role', 'tenant', 'assigned', 'dns_name', 'description',

View File

@ -45,5 +45,8 @@ class ServiceTable(BaseTable):
class Meta(BaseTable.Meta):
model = Service
fields = ('pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags')
fields = (
'pk', 'id', 'name', 'parent', 'protocol', 'ports', 'ipaddresses', 'description', 'tags', 'created',
'last_updated',
)
default_columns = ('pk', 'name', 'parent', 'protocol', 'ports', 'description')

View File

@ -85,7 +85,7 @@ class VLANGroupTable(BaseTable):
model = VLANGroup
fields = (
'pk', 'id', 'name', 'scope_type', 'scope', 'min_vid', 'max_vid', 'vlan_count', 'slug', 'description',
'tags', 'actions',
'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'scope_type', 'scope', 'vlan_count', 'description')
@ -127,7 +127,10 @@ class VLANTable(BaseTable):
class Meta(BaseTable.Meta):
model = VLAN
fields = ('pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags')
fields = (
'pk', 'id', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description', 'tags',
'created', 'last_updated',
)
default_columns = ('pk', 'vid', 'name', 'site', 'group', 'prefixes', 'tenant', 'status', 'role', 'description')
row_attrs = {
'class': lambda record: 'success' if not isinstance(record, VLAN) else '',

View File

@ -47,7 +47,8 @@ class VRFTable(BaseTable):
class Meta(BaseTable.Meta):
model = VRF
fields = (
'pk', 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets', 'tags',
'pk', 'id', 'name', 'rd', 'tenant', 'enforce_unique', 'description', 'import_targets', 'export_targets',
'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'rd', 'tenant', 'description')
@ -68,5 +69,5 @@ class RouteTargetTable(BaseTable):
class Meta(BaseTable.Meta):
model = RouteTarget
fields = ('pk', 'id', 'name', 'tenant', 'description', 'tags')
fields = ('pk', 'id', 'name', 'tenant', 'description', 'tags', 'created', 'last_updated',)
default_columns = ('pk', 'name', 'tenant', 'description')

View File

@ -287,7 +287,7 @@ class BulkEditView(GetReturnURLMixin, ObjectPermissionRequiredMixin, View):
def _update_objects(self, form, request):
custom_fields = getattr(form, 'custom_fields', [])
standard_fields = [
field for field in form.fields if field not in custom_fields + ['pk']
field for field in form.fields if field not in list(custom_fields) + ['pk']
]
nullified_fields = request.POST.getlist('_nullify')
updated_objects = []

View File

@ -24,17 +24,17 @@
{% else %}
{# List all non-customfield filters as declared in the form class #}
{% for field in filter_form.visible_fields %}
{% if not filter_form.custom_field_filters or field.name not in filter_form.custom_field_filters %}
{% if not filter_form.custom_fields or field.name not in filter_form.custom_fields %}
<div class="col col-12">
{% render_field field %}
</div>
{% endif %}
{% endfor %}
{% endif %}
{% if filter_form.custom_field_filters %}
{% if filter_form.custom_fields %}
{# List all custom field filters #}
<hr class="card-divider mt-0" />
{% for name in filter_form.custom_field_filters %}
{% for name in filter_form.custom_fields %}
<div class="col col-12">
{% with field=filter_form|get_item:name %}
{% render_field field %}

View File

@ -18,7 +18,7 @@
<table class="table table-hover attr-table">
<tr>
<td>AS Number</td>
<td>{{ object.asn }}</td>
<td>{{ object.asn_with_asdot }}</td>
</tr>
<tr>
<td>RIR</td>

View File

@ -62,7 +62,9 @@ class TenantGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = TenantGroup
fields = ('pk', 'id', 'name', 'tenant_count', 'description', 'slug', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'tenant_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'tenant_count', 'description')
@ -81,7 +83,7 @@ class TenantTable(BaseTable):
class Meta(BaseTable.Meta):
model = Tenant
fields = ('pk', 'id', 'name', 'slug', 'group', 'description', 'comments', 'tags')
fields = ('pk', 'id', 'name', 'slug', 'group', 'description', 'comments', 'tags', 'created', 'last_updated',)
default_columns = ('pk', 'name', 'group', 'description')
@ -105,7 +107,9 @@ class ContactGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = ContactGroup
fields = ('pk', 'name', 'contact_count', 'description', 'slug', 'tags', 'actions')
fields = (
'pk', 'name', 'contact_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'contact_count', 'description')
@ -117,7 +121,7 @@ class ContactRoleTable(BaseTable):
class Meta(BaseTable.Meta):
model = ContactRole
fields = ('pk', 'name', 'description', 'slug', 'actions')
fields = ('pk', 'name', 'description', 'slug', 'created', 'last_updated', 'actions')
default_columns = ('pk', 'name', 'description')
@ -142,7 +146,10 @@ class ContactTable(BaseTable):
class Meta(BaseTable.Meta):
model = Contact
fields = ('pk', 'name', 'group', 'title', 'phone', 'email', 'address', 'comments', 'assignment_count', 'tags')
fields = (
'pk', 'name', 'group', 'title', 'phone', 'email', 'address', 'comments', 'assignment_count', 'tags',
'created', 'last_updated',
)
default_columns = ('pk', 'name', 'group', 'assignment_count', 'title', 'phone', 'email')

View File

@ -3,7 +3,6 @@ import re
import yaml
from django import forms
from django.utils.translation import gettext as _
from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSelect
@ -11,6 +10,7 @@ from .widgets import APISelect, APISelectMultiple, ClearableFileInput, StaticSel
__all__ = (
'BootstrapMixin',
'BulkEditForm',
'BulkEditBaseForm',
'BulkRenameForm',
'ConfirmationForm',
'CSVModelForm',
@ -75,11 +75,10 @@ class ConfirmationForm(BootstrapMixin, ReturnURLForm):
confirm = forms.BooleanField(required=True, widget=forms.HiddenInput(), initial=True)
class BulkEditForm(BootstrapMixin, forms.Form):
class BulkEditBaseForm(forms.Form):
"""
Base form for editing multiple objects in bulk
"""
def __init__(self, model, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model = model
@ -90,6 +89,10 @@ class BulkEditForm(BootstrapMixin, forms.Form):
self.nullable_fields = self.Meta.nullable_fields
class BulkEditForm(BootstrapMixin, BulkEditBaseForm):
pass
class BulkRenameForm(BootstrapMixin, forms.Form):
"""
An extendable form to be used for renaming objects in bulk.
@ -185,10 +188,7 @@ class FilterForm(BootstrapMixin, forms.Form):
"""
q = forms.CharField(
required=False,
widget=forms.TextInput(
attrs={'placeholder': _('All fields')}
),
label=_('Search')
label='Search'
)

View File

@ -44,7 +44,9 @@ class ClusterTypeTable(BaseTable):
class Meta(BaseTable.Meta):
model = ClusterType
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'created', 'last_updated', 'tags', 'actions',
)
default_columns = ('pk', 'name', 'cluster_count', 'description')
@ -66,7 +68,9 @@ class ClusterGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = ClusterGroup
fields = ('pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'actions')
fields = (
'pk', 'id', 'name', 'slug', 'cluster_count', 'description', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'cluster_count', 'description')
@ -108,7 +112,10 @@ class ClusterTable(BaseTable):
class Meta(BaseTable.Meta):
model = Cluster
fields = ('pk', 'id', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'device_count', 'vm_count', 'tags')
fields = (
'pk', 'id', 'name', 'type', 'group', 'tenant', 'site', 'comments', 'device_count', 'vm_count', 'tags',
'created', 'last_updated',
)
default_columns = ('pk', 'name', 'type', 'group', 'tenant', 'site', 'device_count', 'vm_count')
@ -149,8 +156,8 @@ class VirtualMachineTable(BaseTable):
class Meta(BaseTable.Meta):
model = VirtualMachine
fields = (
'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk', 'primary_ip4',
'primary_ip6', 'primary_ip', 'comments', 'tags',
'pk', 'id', 'name', 'status', 'cluster', 'role', 'tenant', 'platform', 'vcpus', 'memory', 'disk',
'primary_ip4', 'primary_ip6', 'primary_ip', 'comments', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'name', 'status', 'cluster', 'role', 'tenant', 'vcpus', 'memory', 'disk', 'primary_ip',
@ -177,7 +184,7 @@ class VMInterfaceTable(BaseInterfaceTable):
model = VMInterface
fields = (
'pk', 'id', 'name', 'virtual_machine', 'enabled', 'mac_address', 'mtu', 'mode', 'description', 'tags',
'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans',
'ip_addresses', 'fhrp_groups', 'untagged_vlan', 'tagged_vlans', 'created', 'last_updated',
)
default_columns = ('pk', 'name', 'virtual_machine', 'enabled', 'description')

View File

@ -27,7 +27,9 @@ class WirelessLANGroupTable(BaseTable):
class Meta(BaseTable.Meta):
model = WirelessLANGroup
fields = ('pk', 'name', 'wirelesslan_count', 'description', 'slug', 'tags', 'actions')
fields = (
'pk', 'name', 'wirelesslan_count', 'description', 'slug', 'tags', 'created', 'last_updated', 'actions',
)
default_columns = ('pk', 'name', 'wirelesslan_count', 'description')
@ -50,7 +52,7 @@ class WirelessLANTable(BaseTable):
model = WirelessLAN
fields = (
'pk', 'ssid', 'group', 'description', 'vlan', 'interface_count', 'auth_type', 'auth_cipher', 'auth_psk',
'tags',
'tags', 'created', 'last_updated',
)
default_columns = ('pk', 'ssid', 'group', 'description', 'vlan', 'auth_type', 'interface_count')
@ -99,7 +101,7 @@ class WirelessLinkTable(BaseTable):
model = WirelessLink
fields = (
'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'description',
'auth_type', 'auth_cipher', 'auth_psk', 'tags',
'auth_type', 'auth_cipher', 'auth_psk', 'tags', 'created', 'last_updated',
)
default_columns = (
'pk', 'id', 'status', 'device_a', 'interface_a', 'device_b', 'interface_b', 'ssid', 'auth_type',

View File

@ -1,5 +1,5 @@
Django==3.2.11
django-cors-headers==3.10.1
django-cors-headers==3.11.0
django-debug-toolbar==3.2.4
django-filter==21.1
django-graphiql-debug-toolbar==0.2.0
@ -10,7 +10,7 @@ django-redis==5.2.0
django-rq==2.5.1
django-tables2==2.4.1
django-taggit==2.0.0
django-timezone-field==4.2.1
django-timezone-field==4.2.3
djangorestframework==3.12.4
drf-yasg[validation]==1.20.0
graphene_django==2.15.0
@ -18,7 +18,7 @@ gunicorn==20.1.0
Jinja2==3.0.3
Markdown==3.3.6
markdown-include==0.6.0
mkdocs-material==8.1.4
mkdocs-material==8.1.7
netaddr==0.8.0
Pillow==8.4.0
psycopg2-binary==2.9.3