diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 594f23f9a..16182af64 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index b1193ae02..0be999b16 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -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 diff --git a/base_requirements.txt b/base_requirements.txt index cbc893aa9..aaa9c7f44 100644 --- a/base_requirements.txt +++ b/base_requirements.txt @@ -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] diff --git a/docs/plugins/development.md b/docs/plugins/development.md index d20f73cb6..d488cad6b 100644 --- a/docs/plugins/development.md +++ b/docs/plugins/development.md @@ -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: diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index 898d77437..c42837b24 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -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 --- diff --git a/netbox/circuits/api/serializers.py b/netbox/circuits/api/serializers.py index 7a827d547..90767d081 100644 --- a/netbox/circuits/api/serializers.py +++ b/netbox/circuits/api/serializers.py @@ -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', ] diff --git a/netbox/circuits/tables.py b/netbox/circuits/tables.py index 69fe3cf1f..b5fdc5440 100644 --- a/netbox/circuits/tables.py +++ b/netbox/circuits/tables.py @@ -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', diff --git a/netbox/dcim/api/serializers.py b/netbox/dcim/api/serializers.py index 4d8638231..527c1e948 100644 --- a/netbox/dcim/api/serializers.py +++ b/netbox/dcim/api/serializers.py @@ -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): diff --git a/netbox/dcim/forms/filtersets.py b/netbox/dcim/forms/filtersets.py index f84b42aa1..6c192f462 100644 --- a/netbox/dcim/forms/filtersets.py +++ b/netbox/dcim/forms/filtersets.py @@ -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'], ] diff --git a/netbox/dcim/svg.py b/netbox/dcim/svg.py index e19e8fa2f..1058d8385 100644 --- a/netbox/dcim/svg.py +++ b/netbox/dcim/svg.py @@ -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: diff --git a/netbox/dcim/tables/cables.py b/netbox/dcim/tables/cables.py index 9b912894b..bea2c0adf 100644 --- a/netbox/dcim/tables/cables.py +++ b/netbox/dcim/tables/cables.py @@ -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', diff --git a/netbox/dcim/tables/devices.py b/netbox/dcim/tables/devices.py index 7bfa09c21..f5ca49187 100644 --- a/netbox/dcim/tables/devices.py +++ b/netbox/dcim/tables/devices.py @@ -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') diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index ecec67f7d..93832d706 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -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', diff --git a/netbox/dcim/tables/power.py b/netbox/dcim/tables/power.py index ac58b64de..c1ea8a34c 100644 --- a/netbox/dcim/tables/power.py +++ b/netbox/dcim/tables/power.py @@ -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', diff --git a/netbox/dcim/tables/racks.py b/netbox/dcim/tables/racks.py index 565966a39..55c6f9ba8 100644 --- a/netbox/dcim/tables/racks.py +++ b/netbox/dcim/tables/racks.py @@ -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') diff --git a/netbox/dcim/tables/sites.py b/netbox/dcim/tables/sites.py index 98c5e3fd3..32bf000ef 100644 --- a/netbox/dcim/tables/sites.py +++ b/netbox/dcim/tables/sites.py @@ -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') diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 6279ea2b7..79fab4a90 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -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', + ] # diff --git a/netbox/extras/forms/customfields.py b/netbox/extras/forms/customfields.py index 0a2299945..8912d0365 100644 --- a/netbox/extras/forms/customfields.py +++ b/netbox/extras/forms/customfields.py @@ -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) diff --git a/netbox/extras/tables.py b/netbox/extras/tables.py index adfccb575..7d60518b2 100644 --- a/netbox/extras/tables.py +++ b/netbox/extras/tables.py @@ -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') diff --git a/netbox/ipam/models/ip.py b/netbox/ipam/models/ip.py index 13ae0f54f..9d6fb5edc 100644 --- a/netbox/ipam/models/ip.py +++ b/netbox/ipam/models/ip.py @@ -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): diff --git a/netbox/ipam/tables/fhrp.py b/netbox/ipam/tables/fhrp.py index a691b945b..f9119126c 100644 --- a/netbox/ipam/tables/fhrp.py +++ b/netbox/ipam/tables/fhrp.py @@ -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') diff --git a/netbox/ipam/tables/ip.py b/netbox/ipam/tables/ip.py index 9914fb22b..b2e4ef958 100644 --- a/netbox/ipam/tables/ip.py +++ b/netbox/ipam/tables/ip.py @@ -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', diff --git a/netbox/ipam/tables/services.py b/netbox/ipam/tables/services.py index 783cb3537..5c3e14b2c 100644 --- a/netbox/ipam/tables/services.py +++ b/netbox/ipam/tables/services.py @@ -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') diff --git a/netbox/ipam/tables/vlans.py b/netbox/ipam/tables/vlans.py index 3454ddff4..d387e24dd 100644 --- a/netbox/ipam/tables/vlans.py +++ b/netbox/ipam/tables/vlans.py @@ -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 '', diff --git a/netbox/ipam/tables/vrfs.py b/netbox/ipam/tables/vrfs.py index 1264368f4..e71fb1fa4 100644 --- a/netbox/ipam/tables/vrfs.py +++ b/netbox/ipam/tables/vrfs.py @@ -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') diff --git a/netbox/netbox/views/generic/bulk_views.py b/netbox/netbox/views/generic/bulk_views.py index d76bc598d..e9b213a95 100644 --- a/netbox/netbox/views/generic/bulk_views.py +++ b/netbox/netbox/views/generic/bulk_views.py @@ -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 = [] diff --git a/netbox/templates/inc/filter_list.html b/netbox/templates/inc/filter_list.html index 1e73fedb2..e6a1e6a28 100644 --- a/netbox/templates/inc/filter_list.html +++ b/netbox/templates/inc/filter_list.html @@ -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 %}
{% render_field field %}
{% endif %} {% endfor %} {% endif %} - {% if filter_form.custom_field_filters %} + {% if filter_form.custom_fields %} {# List all custom field filters #}
- {% for name in filter_form.custom_field_filters %} + {% for name in filter_form.custom_fields %}
{% with field=filter_form|get_item:name %} {% render_field field %} diff --git a/netbox/templates/ipam/asn.html b/netbox/templates/ipam/asn.html index 53afd5ebb..4a1ecda0d 100644 --- a/netbox/templates/ipam/asn.html +++ b/netbox/templates/ipam/asn.html @@ -18,7 +18,7 @@ - + diff --git a/netbox/tenancy/tables.py b/netbox/tenancy/tables.py index f15e67eab..55a0591b5 100644 --- a/netbox/tenancy/tables.py +++ b/netbox/tenancy/tables.py @@ -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') diff --git a/netbox/utilities/forms/forms.py b/netbox/utilities/forms/forms.py index 87fa4ae33..88f837b2b 100644 --- a/netbox/utilities/forms/forms.py +++ b/netbox/utilities/forms/forms.py @@ -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' ) diff --git a/netbox/virtualization/tables.py b/netbox/virtualization/tables.py index 1920c1140..517f0a4b8 100644 --- a/netbox/virtualization/tables.py +++ b/netbox/virtualization/tables.py @@ -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') diff --git a/netbox/wireless/tables.py b/netbox/wireless/tables.py index 67d46f248..650d91554 100644 --- a/netbox/wireless/tables.py +++ b/netbox/wireless/tables.py @@ -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', diff --git a/requirements.txt b/requirements.txt index e0148e74e..83c1b9f2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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
AS Number{{ object.asn }}{{ object.asn_with_asdot }}
RIR